diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index a1bb15f6..dde99a64 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -1,6 +1,7 @@ import QtQuick import Quickshell pragma Singleton +pragma ComponentBehavior: Bound Singleton { property QtObject m3colors diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index f225d973..57b82174 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -1,6 +1,7 @@ import QtQuick import Quickshell pragma Singleton +pragma ComponentBehavior: Bound Singleton { property QtObject appearance: QtObject { diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml new file mode 100644 index 00000000..096125fe --- /dev/null +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -0,0 +1,138 @@ +import "root:/modules/common" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Quickshell.Services.Notifications +import "./notification_utils.js" as NotificationUtils + +WrapperRectangle { + id: root + property var notificationObject + property bool expanded: true + + Layout.fillWidth: true + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3secondaryContainer : Appearance.colors.colLayer2 + radius: Appearance.rounding.normal + RowLayout { + anchors.fill: parent + Rectangle { + id: iconRectangle + implicitWidth: 47 + implicitHeight: 47 + Layout.leftMargin: 10 + Layout.topMargin: 10 + Layout.bottomMargin: 10 + Layout.alignment: Qt.AlignTop + radius: Appearance.rounding.full + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3secondary : Appearance.m3colors.m3secondaryContainer + MaterialSymbol { + visible: notificationObject.appIcon == "" + text: NotificationUtils.guessMessageType(notificationObject.summary) + anchors.fill: parent + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3onSecondary : Appearance.m3colors.m3onSecondaryContainer + font.pixelSize: 27 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + IconImage { + visible: notificationObject.appIcon != "" + anchors.centerIn: parent + implicitSize: 33 + asynchronous: true + source: Quickshell.iconPath(notificationObject.appIcon) + } + } + ColumnLayout { + spacing: 0 + RowLayout { + Layout.topMargin: 10 + Layout.leftMargin: 10 + Layout.rightMargin: 10 + Layout.fillWidth: true + StyledText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colOnLayer2 + text: notificationObject.summary + wrapMode: expanded ? Text.Wrap : Text.NoWrap + elide: Text.ElideRight + } + Item { Layout.fillWidth: true } + StyledText { + id: notificationTimeText + Layout.fillWidth: false + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignLeft + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.m3colors.m3outline + text: NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) + + Connections { + target: DateTime + function onTimeChanged() { + notificationTimeText.text = NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) + } + } + } + Button { + Layout.alignment: Qt.AlignVCenter + id: expandButton + implicitWidth: 22 + implicitHeight: 22 + + onClicked: { + root.expanded = !root.expanded + } + PointingHandInteraction{} + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (expandButton.down) ? Appearance.colors.colLayer2Active : (expandButton.hovered ? Appearance.colors.colLayer2Hover : Appearance.transparentize(Appearance.colors.colLayer2, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + + } + contentItem: MaterialSymbol { + anchors.centerIn: parent + text: expanded ? "keyboard_arrow_up" : "keyboard_arrow_down" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colOnLayer2 + } + + } + + } + RowLayout { + StyledText { + Layout.fillWidth: true + Layout.bottomMargin: 10 + Layout.leftMargin: 10 + Layout.rightMargin: 10 + wrapMode: expanded ? Text.Wrap : Text.NoWrap + elide: Text.ElideRight + font.pixelSize: Appearance.font.pixelSize.small + horizontalAlignment: Text.AlignLeft + color: Appearance.m3colors.m3outline + textFormat: Text.MarkdownText + text: notificationObject.body + } + } + } + } +} diff --git a/.config/quickshell/modules/common/widgets/notification_utils.js b/.config/quickshell/modules/common/widgets/notification_utils.js new file mode 100644 index 00000000..52cc9d5b --- /dev/null +++ b/.config/quickshell/modules/common/widgets/notification_utils.js @@ -0,0 +1,62 @@ +function guessMessageType(summary) { + const keywordsToTypes = { + 'reboot': 'restart_alt', + 'recording': 'screen_record', + 'battery': 'power', + 'power': 'power', + 'screenshot': 'screenshot_monitor', + 'welcome': 'waving_hand', + 'time': 'scheduleb', + 'installed': 'download', + 'update': 'update', + 'ai response': 'neurology', + '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 'chat'; +} + +// const getFriendlyNotifTimeString = (timeObject) => { +// const messageTime = GLib.DateTime.new_from_unix_local(timeObject); +// const oneMinuteAgo = GLib.DateTime.new_now_local().add_seconds(-60); +// if (messageTime.compare(oneMinuteAgo) > 0) +// return getString('Now'); +// else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year()) +// return messageTime.format(userOptions.time.format); +// else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year() - 1) +// return getString('Yesterday'); +// else +// return messageTime.format(userOptions.time.dateFormat); +// } + +const getFriendlyNotifTimeString = (timeObject) => { + const messageTime = new Date(timeObject * 1000); + const now = new Date(); + const oneMinuteAgo = new Date(now.getTime() - 60000); + + if (messageTime > oneMinuteAgo) { + return 'Now'; + } + else if (messageTime.toDateString() === now.toDateString()) { + return messageTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + } + else if (messageTime.toDateString() === new Date(now.getTime() - 86400000).toDateString()) { + return 'Yesterday'; + } + else { + return messageTime.toLocaleDateString(); + } +}; diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index 0c452829..53295916 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -12,7 +12,7 @@ Rectangle { id: root radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 - // height: collapsed ? collapsedBottomWidgetGroupRow.height : bottomWidgetGroupRow.height + clip: true implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : bottomWidgetGroupRow.implicitHeight property int selectedTab: 0 property bool collapsed: false diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index fcc4ddf4..ca96c34b 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -3,6 +3,7 @@ import "root:/modules/common/widgets" import "root:/services" import "./calendar" import "./todo" +import "./notifications" import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -103,7 +104,7 @@ Rectangle { currentIndex: currentTab onCurrentIndexChanged: currentTab = currentIndex - Item{} + NotificationList {} Item{} } } diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index ca93bef4..01c835e5 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -93,7 +93,7 @@ Scope { Layout.fillHeight: false spacing: 10 Layout.margins: 10 - Layout.bottomMargin: 5 + Layout.bottomMargin: 0 CustomIcon { width: 25 diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml index 51b505e5..a1925f64 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml @@ -46,6 +46,7 @@ Item { Layout.fillWidth: true spacing: 5 CalendarHeaderButton { + clip: true buttonText: `${monthShift != 0 ? "• " : ""}${viewingDate.toLocaleDateString(Qt.locale(), "MMMM yyyy")}` tooltipText: (monthShift === 0) ? "" : "Jump to current month" onClicked: { diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml new file mode 100644 index 00000000..e921c931 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -0,0 +1,33 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets + +Item { + Flickable { // Scrollable window + id: flickable + anchors.fill: parent + contentHeight: columnLayout.height + clip: true + + ColumnLayout { // Scrollable window content + anchors.left: parent.left + anchors.right: parent.right + id: columnLayout + + Repeater { + model: Notifications.list + + delegate: NotificationWidget { + notificationObject: modelData + } + + } + + } + + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml index 03eda884..16ef56e4 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml @@ -12,7 +12,7 @@ QuickToggleButton { MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton | Qt.LeftButton - onClicked: { + onClicked: (mouse) => { if (mouse.button === Qt.LeftButton) { toggleBluetooth.running = true } diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml index cab9aa22..8c916aac 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml @@ -18,7 +18,7 @@ QuickToggleButton { MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton | Qt.LeftButton - onClicked: { + onClicked: (mouse) =>{ if (mouse.button === Qt.LeftButton) { toggleNetwork.running = true } diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index 16e77346..89f92dee 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -14,7 +14,7 @@ Item { property int todoListItemPadding: 8 property int listBottomPadding: 80 - Flickable { // Scrolled window + Flickable { id: flickable anchors.fill: parent contentHeight: columnLayout.height diff --git a/.config/quickshell/services/Audio.qml b/.config/quickshell/services/Audio.qml index 292efbf4..4bfe0e88 100644 --- a/.config/quickshell/services/Audio.qml +++ b/.config/quickshell/services/Audio.qml @@ -2,6 +2,7 @@ import QtQuick import Quickshell import Quickshell.Services.Pipewire pragma Singleton +pragma ComponentBehavior: Bound Singleton { id: root diff --git a/.config/quickshell/services/Bluetooth.qml b/.config/quickshell/services/Bluetooth.qml index fb654c41..fed333bb 100644 --- a/.config/quickshell/services/Bluetooth.qml +++ b/.config/quickshell/services/Bluetooth.qml @@ -1,4 +1,5 @@ pragma Singleton +pragma ComponentBehavior: Bound import Quickshell; import Quickshell.Io; diff --git a/.config/quickshell/services/Brightness.qml b/.config/quickshell/services/Brightness.qml index 57ad389d..a11c1fa4 100644 --- a/.config/quickshell/services/Brightness.qml +++ b/.config/quickshell/services/Brightness.qml @@ -1,6 +1,7 @@ import Quickshell import Quickshell.Io pragma Singleton +pragma ComponentBehavior: Bound Singleton { id: root diff --git a/.config/quickshell/services/DateTime.qml b/.config/quickshell/services/DateTime.qml index e3c1c08c..32b89ff9 100644 --- a/.config/quickshell/services/DateTime.qml +++ b/.config/quickshell/services/DateTime.qml @@ -3,6 +3,7 @@ import QtQuick import Quickshell import Quickshell.Io pragma Singleton +pragma ComponentBehavior: Bound Singleton { property string time: Qt.formatDateTime(clock.date, "hh:mm") @@ -14,7 +15,6 @@ Singleton { SystemClock { id: clock - precision: SystemClock.Minutes } diff --git a/.config/quickshell/services/Network.qml b/.config/quickshell/services/Network.qml index 9a3a3034..1a840258 100644 --- a/.config/quickshell/services/Network.qml +++ b/.config/quickshell/services/Network.qml @@ -1,4 +1,5 @@ pragma Singleton +pragma ComponentBehavior: Bound import Quickshell; import Quickshell.Io; diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml new file mode 100644 index 00000000..db2eadc5 --- /dev/null +++ b/.config/quickshell/services/Notifications.qml @@ -0,0 +1,32 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Services.Notifications + +Singleton { + id: root + property alias list: notifServer.trackedNotifications + + NotificationServer { + id: notifServer + actionIconsSupported: true + actionsSupported: true + bodyHyperlinksSupported: true + bodyImagesSupported: true + bodyMarkupSupported: true + bodySupported: true + imageSupported: true + keepOnReload: true + persistenceSupported: true + + onNotification: (notification) => { + notification.tracked = true; + if(!notification.time) { + notification.time = new Date(); + } + // root.list = [...root.list, notification]; + } + } +} diff --git a/.config/quickshell/services/ResourceUsage.qml b/.config/quickshell/services/ResourceUsage.qml index 239217a6..e30b36c8 100644 --- a/.config/quickshell/services/ResourceUsage.qml +++ b/.config/quickshell/services/ResourceUsage.qml @@ -1,5 +1,7 @@ -import "root:/modules/common" pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common" import QtQuick import Quickshell import Quickshell.Io diff --git a/.config/quickshell/services/SystemInfo.qml b/.config/quickshell/services/SystemInfo.qml index 3db1237c..2572b42d 100644 --- a/.config/quickshell/services/SystemInfo.qml +++ b/.config/quickshell/services/SystemInfo.qml @@ -1,7 +1,9 @@ +pragma Singleton +pragma ComponentBehavior: Bound + import QtQuick import Quickshell import Quickshell.Io -pragma Singleton Singleton { property string distroName: "Unknown" diff --git a/.config/quickshell/services/Todo.qml b/.config/quickshell/services/Todo.qml index 0dc06287..6267768c 100644 --- a/.config/quickshell/services/Todo.qml +++ b/.config/quickshell/services/Todo.qml @@ -1,4 +1,5 @@ pragma Singleton +pragma ComponentBehavior: Bound import Quickshell; import Quickshell.Io;