forked from Shinonome/dots-hyprland
waffles: notifications, kind of
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
pragma Singleton
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
/**
|
||||
* @param { string } summary
|
||||
* @returns { string }
|
||||
*/
|
||||
function findSuitableMaterialSymbol(summary = "") {
|
||||
const defaultType = 'chat';
|
||||
if (summary.length === 0) return defaultType;
|
||||
|
||||
const keywordsToTypes = {
|
||||
'reboot': 'restart_alt',
|
||||
'record': 'screen_record',
|
||||
'battery': 'power',
|
||||
'power': 'power',
|
||||
'screenshot': 'screenshot_monitor',
|
||||
'welcome': 'waving_hand',
|
||||
'time': 'scheduleb',
|
||||
'installed': 'download',
|
||||
'configuration reloaded': 'reset_wrench',
|
||||
'unable': 'question_mark',
|
||||
"couldn't": 'question_mark',
|
||||
'config': 'reset_wrench',
|
||||
'update': 'update',
|
||||
'ai response': 'neurology',
|
||||
'control': 'settings',
|
||||
'upsca': 'compare',
|
||||
'music': 'queue_music',
|
||||
'install': 'deployed_code_update',
|
||||
'input': 'keyboard_alt',
|
||||
'preedit': 'keyboard_alt',
|
||||
'startswith:file': 'folder_copy', // Declarative startsWith check
|
||||
};
|
||||
|
||||
const lowerSummary = summary.toLowerCase();
|
||||
|
||||
for (const [keyword, type] of Object.entries(keywordsToTypes)) {
|
||||
if (keyword.startsWith('startswith:')) {
|
||||
const startsWithKeyword = keyword.replace('startswith:', '');
|
||||
if (lowerSummary.startsWith(startsWithKeyword)) {
|
||||
return type;
|
||||
}
|
||||
} else if (lowerSummary.includes(keyword)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { number | string | Date } timestamp
|
||||
* @returns { string }
|
||||
*/
|
||||
function getFriendlyNotifTimeString(timestamp) {
|
||||
if (!timestamp) return '';
|
||||
const messageTime = new Date(timestamp);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - messageTime.getTime();
|
||||
|
||||
// Less than 1 minute
|
||||
if (diffMs < 60000)
|
||||
return 'Now';
|
||||
|
||||
// Same day - show relative time
|
||||
if (messageTime.toDateString() === now.toDateString()) {
|
||||
const diffMinutes = Math.floor(diffMs / 60000);
|
||||
const diffHours = Math.floor(diffMs / 3600000);
|
||||
|
||||
if (diffHours > 0) {
|
||||
return `${diffHours}h`;
|
||||
} else {
|
||||
return `${diffMinutes}m`;
|
||||
}
|
||||
}
|
||||
|
||||
// Yesterday
|
||||
if (messageTime.toDateString() === new Date(now.getTime() - 86400000).toDateString())
|
||||
return 'Yesterday';
|
||||
|
||||
// Older dates
|
||||
return Qt.formatDateTime(messageTime, "MMMM dd");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import qs.modules.common
|
||||
import "notification_utils.js" as NotificationUtils
|
||||
import qs.modules.common.functions
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import "notification_utils.js" as NotificationUtils
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
@@ -136,7 +135,7 @@ MouseArea { // Notification group area
|
||||
}
|
||||
|
||||
clip: true
|
||||
implicitHeight: expanded ?
|
||||
implicitHeight: root.expanded ?
|
||||
row.implicitHeight + padding * 2 :
|
||||
Math.min(80, row.implicitHeight + padding * 2)
|
||||
|
||||
@@ -157,8 +156,8 @@ MouseArea { // Notification group area
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: false
|
||||
image: root?.multipleNotifications ? "" : notificationGroup?.notifications[0]?.image ?? ""
|
||||
appIcon: notificationGroup?.appIcon
|
||||
summary: notificationGroup?.notifications[root.notificationCount - 1]?.summary
|
||||
appIcon: root.notificationGroup?.appIcon
|
||||
summary: root.notificationGroup?.notifications[root.notificationCount - 1]?.summary
|
||||
urgency: root.notifications.some(n => n.urgency === NotificationUrgency.Critical.toString()) ?
|
||||
NotificationUrgency.Critical : NotificationUrgency.Normal
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
|
||||
/**
|
||||
* @param { string } summary
|
||||
* @returns { string }
|
||||
*/
|
||||
function findSuitableMaterialSymbol(summary = "") {
|
||||
const defaultType = 'chat';
|
||||
if(summary.length === 0) return defaultType;
|
||||
|
||||
const keywordsToTypes = {
|
||||
'reboot': 'restart_alt',
|
||||
'record': 'screen_record',
|
||||
'battery': 'power',
|
||||
'power': 'power',
|
||||
'screenshot': 'screenshot_monitor',
|
||||
'welcome': 'waving_hand',
|
||||
'time': 'scheduleb',
|
||||
'installed': 'download',
|
||||
'configuration reloaded': 'reset_wrench',
|
||||
'unable': 'question_mark',
|
||||
"couldn't": 'question_mark',
|
||||
'config': 'reset_wrench',
|
||||
'update': 'update',
|
||||
'ai response': 'neurology',
|
||||
'control': 'settings',
|
||||
'upsca': 'compare',
|
||||
'music': 'queue_music',
|
||||
'install': 'deployed_code_update',
|
||||
'input': 'keyboard_alt',
|
||||
'preedit': 'keyboard_alt',
|
||||
'startswith:file': 'folder_copy', // Declarative startsWith check
|
||||
};
|
||||
|
||||
const lowerSummary = summary.toLowerCase();
|
||||
|
||||
for (const [keyword, type] of Object.entries(keywordsToTypes)) {
|
||||
if (keyword.startsWith('startswith:')) {
|
||||
const startsWithKeyword = keyword.replace('startswith:', '');
|
||||
if (lowerSummary.startsWith(startsWithKeyword)) {
|
||||
return type;
|
||||
}
|
||||
} else if (lowerSummary.includes(keyword)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { number | string | Date } timestamp
|
||||
* @returns { string }
|
||||
*/
|
||||
const getFriendlyNotifTimeString = (timestamp) => {
|
||||
if (!timestamp) return '';
|
||||
const messageTime = new Date(timestamp);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - messageTime.getTime();
|
||||
|
||||
// Less than 1 minute
|
||||
if (diffMs < 60000)
|
||||
return 'Now';
|
||||
|
||||
// Same day - show relative time
|
||||
if (messageTime.toDateString() === now.toDateString()) {
|
||||
const diffMinutes = Math.floor(diffMs / 60000);
|
||||
const diffHours = Math.floor(diffMs / 3600000);
|
||||
|
||||
if (diffHours > 0) {
|
||||
return `${diffHours}h`;
|
||||
} else {
|
||||
return `${diffMinutes}m`;
|
||||
}
|
||||
}
|
||||
|
||||
// Yesterday
|
||||
if (messageTime.toDateString() === new Date(now.getTime() - 86400000).toDateString())
|
||||
return 'Yesterday';
|
||||
|
||||
// Older dates
|
||||
return Qt.formatDateTime(messageTime, "MMMM dd");
|
||||
};
|
||||
@@ -11,8 +11,8 @@ WButton {
|
||||
colBackground: Looks.colors.bg2
|
||||
colBackgroundHover: Looks.colors.bg2Hover
|
||||
colBackgroundActive: Looks.colors.bg2Active
|
||||
border.color: Looks.colors.bg2Border
|
||||
property color colBorder: Looks.colors.bg2Border
|
||||
property color colBorderToggled: Looks.colors.accent
|
||||
border.color: checked ? colBorderToggled : colBorder
|
||||
border.width: root.pressed ? 2 : 1
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ Item {
|
||||
property real radius: Looks.radius.large
|
||||
property alias border: borderRect
|
||||
property alias borderColor: borderRect.border.color
|
||||
property alias borderWidth: borderRect.border.width
|
||||
|
||||
implicitWidth: borderRect.implicitWidth
|
||||
implicitHeight: borderRect.implicitHeight
|
||||
|
||||
@@ -54,35 +54,19 @@ FooterRectangle {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SmallBorderedIconButton {
|
||||
leftPadding: 12
|
||||
rightPadding: 12
|
||||
implicitWidth: focusButtonContent.implicitWidth + leftPadding + rightPadding
|
||||
SmallBorderedIconAndTextButton {
|
||||
iconName: TimerService.pomodoroRunning ? "stop" : "play"
|
||||
text: TimerService.pomodoroRunning ? Translation.tr("End session") : Translation.tr("Focus")
|
||||
|
||||
onClicked: {
|
||||
if (TimerService.pomodoroRunning) {
|
||||
TimerService.togglePomodoro()
|
||||
TimerService.resetPomodoro()
|
||||
TimerService.togglePomodoro();
|
||||
TimerService.resetPomodoro();
|
||||
} else {
|
||||
TimerService.togglePomodoro()
|
||||
TimerService.togglePomodoro();
|
||||
Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "sidebarRight", "toggle"]);
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Row {
|
||||
id: focusButtonContent
|
||||
spacing: 4
|
||||
FluentIcon {
|
||||
icon: TimerService.pomodoroRunning ? "stop" : "play"
|
||||
filled: true
|
||||
implicitSize: 14
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
WText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: TimerService.pomodoroRunning ? Translation.tr("End session") : Translation.tr("Focus")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-4
@@ -19,16 +19,40 @@ WBarAttachedPanelContent {
|
||||
|
||||
property bool collapsed: false
|
||||
|
||||
contentItem: Column {
|
||||
contentItem: ColumnLayout {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: root.barAtBottom ? undefined : parent.top
|
||||
bottom: root.barAtBottom ? parent.bottom : undefined
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
spacing: 12
|
||||
|
||||
Item {
|
||||
id: notificationArea
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: notificationPane.implicitWidth
|
||||
|
||||
WPane {
|
||||
id: notificationPane
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
contentItem: NotificationPaneContent {
|
||||
implicitWidth: calendarColumnLayout.implicitWidth
|
||||
implicitHeight: Notifications.list.length > 0 ? (notificationArea.height - notificationPane.borderWidth * 2) : 230
|
||||
|
||||
Behavior on implicitHeight {
|
||||
animation: Looks.transition.enter.createObject(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WPane {
|
||||
contentItem: ColumnLayout {
|
||||
id: calendarColumnLayout
|
||||
spacing: 0
|
||||
DateHeader {
|
||||
Layout.fillWidth: true
|
||||
@@ -37,7 +61,9 @@ WBarAttachedPanelContent {
|
||||
}
|
||||
}
|
||||
|
||||
WPanelSeparator { visible: !root.collapsed }
|
||||
WPanelSeparator {
|
||||
visible: !root.collapsed
|
||||
}
|
||||
|
||||
CalendarWidget {
|
||||
Layout.fillWidth: true
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.waffle.looks
|
||||
|
||||
WBorderlessButton {
|
||||
id: headerButton
|
||||
Layout.fillWidth: false
|
||||
implicitWidth: 16
|
||||
implicitHeight: 16
|
||||
color: "transparent"
|
||||
|
||||
contentItem: Item {
|
||||
FluentIcon {
|
||||
anchors.centerIn: parent
|
||||
implicitSize: 16
|
||||
icon: headerButton.icon.name
|
||||
color: headerButton.hovered && !headerButton.pressed ? Looks.colors.fg : Looks.colors.fg1
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.waffle.looks
|
||||
|
||||
BodyRectangle {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
implicitHeight: 230
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
|
||||
spacing: 12
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 12
|
||||
Layout.rightMargin: 12
|
||||
Layout.topMargin: 8
|
||||
|
||||
spacing: 8
|
||||
|
||||
WText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideRight
|
||||
text: Translation.tr("Notifications")
|
||||
font.pixelSize: Looks.font.pixelSize.large
|
||||
}
|
||||
|
||||
SmallBorderedIconButton {
|
||||
icon.name: "alert-snooze"
|
||||
checked: Notifications.silent
|
||||
onClicked: {
|
||||
Notifications.silent = !Notifications.silent;
|
||||
}
|
||||
}
|
||||
|
||||
SmallBorderedIconAndTextButton {
|
||||
visible: Notifications.list.length > 0
|
||||
iconVisible: false
|
||||
text: Translation.tr("Clear all")
|
||||
onClicked: {
|
||||
Notifications.discardAllNotifications();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
animateAppearance: false
|
||||
clip: true
|
||||
|
||||
model: Notifications.appNameList
|
||||
delegate: WNotificationGroup {
|
||||
required property int index
|
||||
required property var modelData
|
||||
width: ListView.view.width
|
||||
notificationGroup: Notifications.groupsByAppName[modelData]
|
||||
}
|
||||
|
||||
EmptyPlaceholder {
|
||||
visible: Notifications.list.length === 0
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component EmptyPlaceholder: WText {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: Translation.tr("No new notifications")
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import QtQuick
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.waffle.looks
|
||||
|
||||
SmallBorderedIconButton {
|
||||
id: root
|
||||
|
||||
property bool iconVisible: true
|
||||
property string iconName: ""
|
||||
property bool iconFilled: true
|
||||
|
||||
leftPadding: 12
|
||||
rightPadding: 12
|
||||
implicitWidth: focusButtonContent.implicitWidth + leftPadding + rightPadding
|
||||
|
||||
contentItem: Row {
|
||||
id: focusButtonContent
|
||||
spacing: 4
|
||||
|
||||
FluentIcon {
|
||||
visible: root.iconVisible
|
||||
icon: root.iconName
|
||||
filled: root.iconFilled
|
||||
implicitSize: 14
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
WText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.text
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -13,6 +13,7 @@ WBorderedButton {
|
||||
anchors.centerIn: parent
|
||||
implicitSize: 12
|
||||
icon: root.icon.name
|
||||
color: root.fgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import org.kde.kirigami as Kirigami
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.waffle.looks
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string icon: ""
|
||||
property real implicitSize: 16
|
||||
implicitWidth: implicitSize
|
||||
implicitHeight: implicitSize
|
||||
|
||||
Kirigami.Icon {
|
||||
anchors.fill: parent
|
||||
implicitWidth: root.implicitSize
|
||||
implicitHeight: root.implicitSize
|
||||
|
||||
source: root.icon || fallback
|
||||
fallback: `${Looks.iconsPath}/apps.svg`
|
||||
roundToIconSize: false
|
||||
isMask: !root.icon
|
||||
color: Looks.colors.fg
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.waffle.looks
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
|
||||
required property var notificationGroup
|
||||
readonly property var notifications: notificationGroup?.notifications ?? []
|
||||
|
||||
implicitWidth: contentLayout.implicitWidth
|
||||
implicitHeight: contentLayout.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
anchors.fill: parent
|
||||
spacing: 4
|
||||
|
||||
GroupHeader {
|
||||
id: notifHeader
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 11
|
||||
}
|
||||
|
||||
ListView {
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: notifHeader.implicitWidth
|
||||
implicitHeight: contentHeight
|
||||
interactive: false
|
||||
spacing: 4
|
||||
model: ScriptModel {
|
||||
values: root.notifications.slice().reverse()
|
||||
}
|
||||
delegate: WSingleNotification {
|
||||
required property var modelData
|
||||
width: ListView.view.width
|
||||
notification: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component GroupHeader: MouseArea {
|
||||
id: headerMouseArea
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
|
||||
implicitWidth: appHeader.implicitWidth
|
||||
implicitHeight: appHeader.implicitHeight
|
||||
|
||||
RowLayout {
|
||||
id: appHeader
|
||||
anchors.fill: parent
|
||||
spacing: 7
|
||||
|
||||
WNotificationAppIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: root.notificationGroup?.appIcon ?? ""
|
||||
}
|
||||
|
||||
WText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideRight
|
||||
text: root.notificationGroup?.appName ?? ""
|
||||
}
|
||||
|
||||
// NotificationHeaderButton { // TODO: More notification functionality needed so we can have this button
|
||||
// visible: headerMouseArea.containsMouse
|
||||
// Layout.leftMargin: 25
|
||||
// Layout.rightMargin: 25
|
||||
// icon.name: "more-horizontal"
|
||||
// }
|
||||
|
||||
NotificationHeaderButton {
|
||||
visible: headerMouseArea.containsMouse
|
||||
Layout.rightMargin: 3
|
||||
icon.name: "dismiss"
|
||||
onClicked: {
|
||||
root.notifications.forEach(notif => {
|
||||
Qt.callLater(() => {
|
||||
Notifications.discardNotification(notif.notificationId);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.waffle.looks
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
|
||||
required property var notification
|
||||
property bool expanded: false
|
||||
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
implicitWidth: contentItem.implicitWidth
|
||||
|
||||
Rectangle {
|
||||
id: contentItem
|
||||
anchors.fill: parent
|
||||
color: Looks.colors.bgPanelBody
|
||||
radius: Looks.radius.medium
|
||||
property real padding: 12
|
||||
implicitHeight: notificationContent.implicitHeight + padding * 2
|
||||
implicitWidth: notificationContent.implicitWidth + padding * 2
|
||||
border.width: 1
|
||||
border.color: Looks.applyContentTransparency(Looks.colors.ambientShadow)
|
||||
|
||||
ColumnLayout {
|
||||
id: notificationContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: contentItem.padding
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
WText {
|
||||
text: NotificationUtils.getFriendlyNotifTimeString(root.notification?.time)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
WText {
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
text: root.notification.summary
|
||||
}
|
||||
WText {
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: root.expanded ? 100 : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user