From dbb5a9160c368102f0dd5240bf80fc275063918b Mon Sep 17 00:00:00 2001 From: ly-sec Date: Thu, 17 Jul 2025 16:56:51 +0200 Subject: [PATCH] NotificationHistory Updates Add read/unread bell icon Edit style of the Panel Small fixes --- Bar/Bar.qml | 80 +-- Helpers/IPCHandlers.qml | 1 + Services/WorkspaceManager.qml | 2 +- Settings/Theme.json | 38 +- Widgets/Notification/NotificationHistory.qml | 688 ++++++++++--------- shell.qml | 16 + 6 files changed, 416 insertions(+), 409 deletions(-) diff --git a/Bar/Bar.qml b/Bar/Bar.qml index 948a443..ebd120e 100644 --- a/Bar/Bar.qml +++ b/Bar/Bar.qml @@ -9,11 +9,11 @@ import qs.Settings import qs.Services import qs.Components import qs.Widgets -import qs.Widgets.Notification import qs.Widgets.Sidebar import qs.Widgets.Sidebar.Panel import qs.Helpers import QtQuick.Controls +import qs.Widgets.Notification Scope { id: rootScope @@ -83,40 +83,9 @@ Scope { anchors.rightMargin: 18 spacing: 12 - Item { - id: notificationBellButton - width: 22 - height: 22 - anchors.verticalCenter: parent.verticalCenter - z: 1 - - Rectangle { - id: bellBg - width: 22 - height: 22 - radius: 11 - color: mouseAreaBell.containsMouse ? Theme.accentPrimary : "transparent" - visible: mouseAreaBell.containsMouse - anchors.centerIn: parent - } - Text { - anchors.centerIn: parent - text: "notifications" - font.family: mouseAreaBell.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined" - font.pixelSize: 16 - color: mouseAreaBell.containsMouse ? Theme.backgroundPrimary : Theme.textPrimary - } - MouseArea { - id: mouseAreaBell - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: notificationHistoryWin.visible = !notificationHistoryWin.visible - } - } - NotificationHistory { id: notificationHistoryWin + anchors.verticalCenter: parent.verticalCenter } Brightness { @@ -158,28 +127,25 @@ Scope { } } - - Background {} Overview {} } PanelWindow { - id: topLeftPanel + id: topCornerPanel anchors.top: true anchors.left: true + anchors.right: true color: "transparent" screen: modelData margins.top: 36 WlrLayershell.exclusionMode: ExclusionMode.Ignore visible: true - WlrLayershell.layer: WlrLayer.Background - aboveWindows: false - WlrLayershell.namespace: "swww-daemon" + implicitHeight: 24 Corners { - id: topLeftCorner + id: topleftCorner position: "bottomleft" size: 1.3 fillColor: (Theme.backgroundPrimary !== undefined && Theme.backgroundPrimary !== null) ? Theme.backgroundPrimary : "#222" @@ -187,25 +153,9 @@ Scope { offsetY: 0 anchors.top: parent.top } - } - - PanelWindow { - id: topRightPanel - anchors.top: true - anchors.right: true - color: "transparent" - screen: modelData - margins.top: 36 - WlrLayershell.exclusionMode: ExclusionMode.Ignore - visible: true - WlrLayershell.layer: WlrLayer.Background - aboveWindows: false - WlrLayershell.namespace: "swww-daemon" - - implicitHeight: 24 Corners { - id: topRightCorner + id: toprightCorner position: "bottomright" size: 1.3 fillColor: (Theme.backgroundPrimary !== undefined && Theme.backgroundPrimary !== null) ? Theme.backgroundPrimary : "#222" @@ -223,9 +173,6 @@ Scope { screen: modelData WlrLayershell.exclusionMode: ExclusionMode.Ignore visible: true - WlrLayershell.layer: WlrLayer.Background - aboveWindows: false - WlrLayershell.namespace: "swww-daemon" implicitHeight: 24 @@ -241,17 +188,14 @@ Scope { } PanelWindow { - id: bottomRightPanel + id: bottomRightCornerPanel anchors.bottom: true anchors.right: true color: "transparent" screen: modelData WlrLayershell.exclusionMode: ExclusionMode.Ignore visible: true - WlrLayershell.layer: WlrLayer.Background - aboveWindows: false - WlrLayershell.namespace: "swww-daemon" - + implicitHeight: 24 Corners { @@ -264,6 +208,10 @@ Scope { anchors.top: parent.top } } + + Loader { + id: tabViewerLoader + } } } @@ -271,4 +219,4 @@ Scope { // This alias exposes the visual bar's visibility to the outside world property alias visible: barRootItem.visible -} \ No newline at end of file +} diff --git a/Helpers/IPCHandlers.qml b/Helpers/IPCHandlers.qml index 9e72395..d3923aa 100644 --- a/Helpers/IPCHandlers.qml +++ b/Helpers/IPCHandlers.qml @@ -3,6 +3,7 @@ import Quickshell.Io IpcHandler { property var appLauncherPanel property var lockScreen + property var tabViewer target: "globalIPC" diff --git a/Services/WorkspaceManager.qml b/Services/WorkspaceManager.qml index f2f7bcb..80f6ab2 100644 --- a/Services/WorkspaceManager.qml +++ b/Services/WorkspaceManager.qml @@ -89,7 +89,7 @@ Singleton { id: i, idx: ws.id, name: ws.name || "", - output: ws.monitor?.name || "", + output: ws.monitor.name || "", isActive: ws.active === true, isFocused: ws.focused === true, isUrgent: ws.urgent === true diff --git a/Settings/Theme.json b/Settings/Theme.json index f00a3f5..c20e7b1 100644 --- a/Settings/Theme.json +++ b/Settings/Theme.json @@ -1,28 +1,28 @@ { - "backgroundPrimary": "#111211", - "backgroundSecondary": "#1D1E1D", - "backgroundTertiary": "#292A29", + "backgroundPrimary": "#0E0F10", + "backgroundSecondary": "#1A1B1C", + "backgroundTertiary": "#262728", - "surface": "#242524", - "surfaceVariant": "#353635", + "surface": "#212223", + "surfaceVariant": "#323334", - "textPrimary": "#F4E9E3", - "textSecondary": "#DCD2CC", - "textDisabled": "#928C88", + "textPrimary": "#F0F1E0", + "textSecondary": "#D8D9CA", + "textDisabled": "#909186", - "accentPrimary": "#7D8079", - "accentSecondary": "#979994", - "accentTertiary": "#646661", + "accentPrimary": "#A3A485", + "accentSecondary": "#B5B69D", + "accentTertiary": "#82836A", - "error": "#939849", - "warning": "#ABAF72", + "error": "#A5A9ED", + "warning": "#B9BCF1", - "highlight": "#B1B3AF", - "rippleEffect": "#8A8D86", + "highlight": "#C8C8B6", + "rippleEffect": "#ACAD91", - "onAccent": "#111211", - "outline": "#585958", + "onAccent": "#0E0F10", + "outline": "#565758", - "shadow": "#111211", - "overlay": "#111211" + "shadow": "#0E0F10", + "overlay": "#0E0F10" } diff --git a/Widgets/Notification/NotificationHistory.qml b/Widgets/Notification/NotificationHistory.qml index 6a22371..11758c5 100644 --- a/Widgets/Notification/NotificationHistory.qml +++ b/Widgets/Notification/NotificationHistory.qml @@ -1,357 +1,399 @@ -import QtQuick -import QtQuick.Controls +import QtQuick 2.15 +import QtQuick.Controls 2.15 import Quickshell import Quickshell.Io import qs.Components import qs.Settings -import QtQuick.Layouts +import QtQuick.Layouts 1.15 -PanelWindow { - id: notificationHistoryWin - width: 400 - height: 500 - color: "transparent" - visible: false - screen: Quickshell.primaryScreen - focusable: true - anchors.top: true - anchors.right: true - margins.top: 4 - margins.right: 4 - - property int maxHistory: 100 +Item { + id: root property string configDir: Quickshell.configDir property string historyFilePath: configDir + "/notification_history.json" + property bool hasUnread: notificationHistoryWin.hasUnread && !notificationHistoryWin.visible + function addToHistory(notification) { notificationHistoryWin.addToHistory(notification) } + width: 22; height: 22 - ListModel { - id: historyModel // Holds notification objects - } - - FileView { - id: historyFileView - path: historyFilePath - blockLoading: true - printErrors: true - watchChanges: true - - JsonAdapter { - id: historyAdapter - property var notifications: [] // Array of notification objects + // Bell icon/button + Item { + id: bell + width: 22; height: 22 + Text { + anchors.centerIn: parent + text: root.hasUnread ? "notifications_unread" : "notifications" + font.family: mouseAreaBell.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined" + font.pixelSize: 16 + font.weight: root.hasUnread ? Font.Bold : Font.Normal + color: mouseAreaBell.containsMouse ? Theme.accentPrimary : (root.hasUnread ? Theme.accentPrimary : Theme.textDisabled) } - - onFileChanged: { - reload() // Reload if file changes on disk - } - - onLoaded: { - loadHistory() // Populate model after loading - } - - onLoadFailed: function(error) { - console.error("Failed to load history file:", error) - if (error.includes("No such file")) { - historyAdapter.notifications = [] // Create new file if missing - writeAdapter() - } - } - - onSaved: {} - onSaveFailed: function(error) { - console.error("Failed to save history:", error) - } - - Component.onCompleted: { - if (path) reload() - } - } - - function loadHistory() { - if (historyAdapter.notifications) { - historyModel.clear() - const notifications = historyAdapter.notifications - const count = Math.min(notifications.length, maxHistory) - for (let i = 0; i < count; i++) { - if (typeof notifications[i] === 'object' && notifications[i] !== null) { - historyModel.append(notifications[i]) - } - } - } - } - - function saveHistory() { - const historyArray = [] - const count = Math.min(historyModel.count, maxHistory) - for (let i = 0; i < count; ++i) { - let obj = historyModel.get(i) - if (typeof obj === 'object' && obj !== null) { - historyArray.push({ - id: obj.id, - appName: obj.appName, - summary: obj.summary, - body: obj.body, - timestamp: obj.timestamp - }) - } - } - historyAdapter.notifications = historyArray - Qt.callLater(function() { - historyFileView.writeAdapter() - }) - } - - function addToHistory(notification) { - if (!notification.id) notification.id = Date.now() - if (!notification.timestamp) notification.timestamp = new Date().toISOString() - for (let i = 0; i < historyModel.count; ++i) { - if (historyModel.get(i).id === notification.id) { - historyModel.remove(i) - break - } - } - historyModel.insert(0, notification) - if (historyModel.count > maxHistory) historyModel.remove(maxHistory) - saveHistory() - } - - function clearHistory() { - historyModel.clear() - historyAdapter.notifications = [] - historyFileView.writeAdapter() - } - - function formatTimestamp(ts) { - if (!ts) return ""; - var date = typeof ts === "number" ? new Date(ts) : new Date(Date.parse(ts)); - var y = date.getFullYear(); - var m = (date.getMonth()+1).toString().padStart(2,'0'); - var d = date.getDate().toString().padStart(2,'0'); - var h = date.getHours().toString().padStart(2,'0'); - var min = date.getMinutes().toString().padStart(2,'0'); - return `${y}-${m}-${d} ${h}:${min}`; - } - - Rectangle { - width: notificationHistoryWin.width - height: notificationHistoryWin.height - anchors.fill: parent - color: Theme.backgroundPrimary - radius: 20 - - Column { + MouseArea { + id: mouseAreaBell anchors.fill: parent - anchors.margins: 16 - spacing: 8 + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: notificationHistoryWin.visible = !notificationHistoryWin.visible + } + } - RowLayout { - spacing: 4 - anchors.top: parent.top - anchors.topMargin: 16 - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 - Text { - text: "Notification History" - font.pixelSize: 18 - font.bold: true - color: Theme.textPrimary - Layout.alignment: Qt.AlignVCenter - } - Item { Layout.fillWidth: true } - Rectangle { - id: clearAllButton - width: 90 - height: 32 - radius: 20 - color: clearAllMouseArea.containsMouse ? Theme.accentPrimary : Theme.surfaceVariant - border.color: Theme.accentPrimary - border.width: 1 - Layout.alignment: Qt.AlignVCenter - Row { - anchors.centerIn: parent - spacing: 6 - Text { - text: "delete_sweep" - font.family: "Material Symbols Outlined" - font.pixelSize: 14 - color: clearAllMouseArea.containsMouse ? Theme.onAccent : Theme.accentPrimary - verticalAlignment: Text.AlignVCenter - } - Text { - text: "Clear" - font.pixelSize: Theme.fontSizeSmall - color: clearAllMouseArea.containsMouse ? Theme.onAccent : Theme.accentPrimary - font.bold: true - verticalAlignment: Text.AlignVCenter - } - } - MouseArea { - id: clearAllMouseArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: notificationHistoryWin.clearHistory() - } - } + // The popup window + PanelWindow { + id: notificationHistoryWin + width: 400 + property int maxPopupHeight: 500 + property int minPopupHeight: 230 + property int contentHeight: headerRow.height + historyList.contentHeight + 56 + height: Math.max(Math.min(contentHeight, maxPopupHeight), minPopupHeight) + color: "transparent" + visible: false + screen: Quickshell.primaryScreen + focusable: true + anchors.top: true + anchors.right: true + margins.top: 4 + margins.right: 4 + + property int maxHistory: 100 + property bool hasUnread: false + signal unreadChanged(bool hasUnread) + + ListModel { + id: historyModel + } + + FileView { + id: historyFileView + path: root.historyFilePath + blockLoading: true + printErrors: true + watchChanges: true + + JsonAdapter { + id: historyAdapter + property var notifications: [] } - Rectangle { - width: parent.width - height: 0 - color: "transparent" - visible: true - } - - Rectangle { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 56 - anchors.bottom: parent.bottom - color: Theme.surfaceVariant - radius: 20 - - Rectangle { - anchors.fill: parent - color: Theme.backgroundPrimary - radius: 20 - border.width: 1 - border.color: Theme.surfaceVariant - z: 0 + onFileChanged: historyFileView.reload() + onLoaded: notificationHistoryWin.loadHistory() + onLoadFailed: function(error) { + if (error.includes("No such file")) { + historyAdapter.notifications = [] + writeAdapter() } + } + Component.onCompleted: if (path) reload() + } + + function updateHasUnread() { + var unread = false; + for (let i = 0; i < historyModel.count; ++i) { + if (historyModel.get(i).read === false) { + unread = true; + break; + } + } + if (hasUnread !== unread) { + hasUnread = unread; + unreadChanged(hasUnread); + } + } + + function loadHistory() { + if (historyAdapter.notifications) { + historyModel.clear() + const notifications = historyAdapter.notifications + const count = Math.min(notifications.length, maxHistory) + for (let i = 0; i < count; i++) { + let n = notifications[i] + if (typeof n === 'object' && n !== null) { + if (n.read === undefined) n.read = false; + // Mark as read if window is open + if (visible) n.read = true; + historyModel.append(n) + } + } + updateHasUnread(); + } + } + + function saveHistory() { + const historyArray = [] + const count = Math.min(historyModel.count, maxHistory) + for (let i = 0; i < count; ++i) { + let obj = historyModel.get(i) + if (typeof obj === 'object' && obj !== null) { + historyArray.push({ + id: obj.id, + appName: obj.appName, + summary: obj.summary, + body: obj.body, + timestamp: obj.timestamp, + read: obj.read === undefined ? false : obj.read + }) + } + } + historyAdapter.notifications = historyArray + Qt.callLater(function() { + historyFileView.writeAdapter() + }) + updateHasUnread(); + } + + function addToHistory(notification) { + if (!notification.id) notification.id = Date.now() + if (!notification.timestamp) notification.timestamp = new Date().toISOString() + + // Mark as read if window is open + notification.read = visible + + // Remove duplicate by id + for (let i = 0; i < historyModel.count; ++i) { + if (historyModel.get(i).id === notification.id) { + historyModel.remove(i) + break + } + } + + historyModel.insert(0, notification) + + if (historyModel.count > maxHistory) historyModel.remove(maxHistory) + saveHistory() + } + + function clearHistory() { + historyModel.clear() + historyAdapter.notifications = [] + historyFileView.writeAdapter() + } + + function formatTimestamp(ts) { + if (!ts) return ""; + var date = typeof ts === "number" ? new Date(ts) : new Date(Date.parse(ts)); + var y = date.getFullYear(); + var m = (date.getMonth()+1).toString().padStart(2,'0'); + var d = date.getDate().toString().padStart(2,'0'); + var h = date.getHours().toString().padStart(2,'0'); + var min = date.getMinutes().toString().padStart(2,'0'); + return `${y}-${m}-${d} ${h}:${min}`; + } + + onVisibleChanged: { + if (visible) { + // Mark all as read when popup is opened + let changed = false; + for (let i = 0; i < historyModel.count; ++i) { + if (historyModel.get(i).read === false) { + historyModel.setProperty(i, 'read', true); + changed = true; + } + } + if (changed) saveHistory(); + } + } + + Rectangle { + width: notificationHistoryWin.width + height: notificationHistoryWin.height + anchors.fill: parent + color: Theme.backgroundPrimary + radius: 20 + + Column { + anchors.fill: parent + anchors.margins: 16 + spacing: 8 + + RowLayout { + id: headerRow + spacing: 4 + anchors.top: parent.top + anchors.topMargin: 4 + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + Text { + text: "Notification History" + font.pixelSize: 18 + font.bold: true + color: Theme.textPrimary + Layout.alignment: Qt.AlignVCenter + } + Item { Layout.fillWidth: true } + Rectangle { + id: clearAllButton + width: 90 + height: 32 + radius: 16 + color: clearAllMouseArea.containsMouse ? Theme.accentPrimary : Theme.surfaceVariant + border.color: Theme.accentPrimary + border.width: 1 + Layout.alignment: Qt.AlignVCenter + Row { + anchors.centerIn: parent + spacing: 6 + Text { + text: "delete_sweep" + font.family: "Material Symbols Outlined" + font.pixelSize: 14 + color: clearAllMouseArea.containsMouse ? Theme.onAccent : Theme.accentPrimary + verticalAlignment: Text.AlignVCenter + } + Text { + text: "Clear" + font.pixelSize: Theme.fontSizeSmall + color: clearAllMouseArea.containsMouse ? Theme.onAccent : Theme.accentPrimary + font.bold: true + verticalAlignment: Text.AlignVCenter + } + } + MouseArea { + id: clearAllMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: notificationHistoryWin.clearHistory() + } + } + } + Rectangle { - id: listContainer - anchors.fill: parent - anchors.topMargin: 12 - anchors.bottomMargin: 12 + width: parent.width + height: 0 color: "transparent" - clip: true - ListView { - id: historyList + visible: true + } + + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 56 + anchors.bottom: parent.bottom + color: Theme.surfaceVariant + radius: 20 + + Rectangle { anchors.fill: parent - spacing: 12 - model: historyModel - delegate: Item { - height: notificationCard.implicitHeight + 12 - Rectangle { - id: notificationCard - width: parent.width - 24 - anchors.horizontalCenter: parent.horizontalCenter - color: Theme.backgroundPrimary - radius: 16 - border.color: Theme.outline - border.width: 1 - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.margins: 0 - implicitHeight: contentColumn.implicitHeight + 20 - Column { - id: contentColumn - anchors.fill: parent - anchors.margins: 14 - spacing: 6 - RowLayout { - id: headerRow - spacing: 8 - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 0 - Rectangle { - id: iconBackground - width: 28 - height: 28 - radius: 20 - color: Theme.accentPrimary - border.color: Qt.darker(Theme.accentPrimary, 1.2) - border.width: 1.2 - Layout.alignment: Qt.AlignVCenter - Text { - anchors.centerIn: parent - text: model.appName ? model.appName.charAt(0).toUpperCase() : "?" - font.family: Theme.fontFamily - font.pixelSize: 15 - font.bold: true - color: Theme.backgroundPrimary - } - } + color: Theme.surface + radius: 20 + z: 0 + } + Rectangle { + id: listContainer + anchors.fill: parent + anchors.topMargin: 12 + anchors.bottomMargin: 12 + color: "transparent" + clip: true + Column { + anchors.fill: parent + spacing: 0 + Item { id: topSpacer; height: (parent.height - historyList.height) / 2 } + ListView { + id: historyList + width: parent.width + height: Math.min(contentHeight, parent.height) + spacing: 12 + model: historyModel.count > 0 ? historyModel : placeholderModel + clip: true + delegate: Item { + width: parent.width + height: notificationCard.implicitHeight + 12 + Rectangle { + id: notificationCard + width: parent.width - 24 + anchors.horizontalCenter: parent.horizontalCenter + color: Theme.backgroundPrimary + radius: 16 + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.margins: 0 + implicitHeight: contentColumn.implicitHeight + 20 Column { - id: appInfoColumn - spacing: 0 - Layout.alignment: Qt.AlignVCenter - Text { - text: model.appName || "Unknown App" - font.bold: true - color: Theme.textPrimary - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSmall - verticalAlignment: Text.AlignVCenter + id: contentColumn + anchors.fill: parent + anchors.margins: 14 + spacing: 6 + RowLayout { + id: headerRow + spacing: 8 + Rectangle { + id: iconBackground + width: 28 + height: 28 + radius: 20 + color: Theme.accentPrimary + border.color: Qt.darker(Theme.accentPrimary, 1.2) + border.width: 1.2 + Layout.alignment: Qt.AlignVCenter + Text { + anchors.centerIn: parent + text: model.appName ? model.appName.charAt(0).toUpperCase() : "?" + font.family: Theme.fontFamily + font.pixelSize: 15 + font.bold: true + color: Theme.backgroundPrimary + } + } + Column { + id: appInfoColumn + spacing: 0 + Layout.alignment: Qt.AlignVCenter + Text { + text: model.appName || "No Notifications" + font.bold: true + color: Theme.textPrimary + font.family: Theme.fontFamily + font.pixelSize: Theme.fontSizeSmall + verticalAlignment: Text.AlignVCenter + } + Text { + visible: !model.isPlaceholder + text: model.timestamp ? notificationHistoryWin.formatTimestamp(model.timestamp) : "" + color: Theme.textDisabled + font.family: Theme.fontFamily + font.pixelSize: Theme.fontSizeCaption + verticalAlignment: Text.AlignVCenter + } + } + Item { Layout.fillWidth: true } } Text { - text: formatTimestamp(model.timestamp) + text: model.summary || (model.isPlaceholder ? "You're all caught up!" : "") color: Theme.textSecondary font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeCaption - verticalAlignment: Text.AlignVCenter + font.pixelSize: Theme.fontSizeBody + width: parent.width + wrapMode: Text.Wrap + } + Text { + text: model.body || (model.isPlaceholder ? "No notifications to show." : "") + color: Theme.textDisabled + font.family: Theme.fontFamily + font.pixelSize: Theme.fontSizeBody + width: parent.width + wrapMode: Text.Wrap } } - Item { Layout.fillWidth: true } - Rectangle { - id: deleteButton - width: 24 - height: 24 - radius: 12 - color: deleteMouseArea.containsMouse ? Theme.accentPrimary : Theme.surfaceVariant - border.color: Theme.accentPrimary - border.width: 1 - Layout.alignment: Qt.AlignVCenter - z: 2 - Row { - anchors.centerIn: parent - spacing: 0 - Text { - text: "close" - font.family: "Material Symbols Outlined" - font.pixelSize: 16 - color: deleteMouseArea.containsMouse ? Theme.onAccent : Theme.error - verticalAlignment: Text.AlignVCenter - } - } - MouseArea { - id: deleteMouseArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - historyModel.remove(index) - saveHistory() - } - } - } - } - Text { - text: model.summary || "" - color: Theme.textSecondary - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeBody - width: parent.width - wrapMode: Text.Wrap - } - Text { - text: model.body || "" - color: Theme.textDisabled - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeBody - width: parent.width - wrapMode: Text.Wrap } } } } } } - } - Rectangle { width: 1; height: 24; color: "transparent" } + Rectangle { width: 1; height: 24; color: "transparent" } + + ListModel { + id: placeholderModel + ListElement { + appName: "" + summary: "" + body: "" + isPlaceholder: true + } + } + } } } } \ No newline at end of file diff --git a/shell.qml b/shell.qml index 93ecfcf..42676f7 100644 --- a/shell.qml +++ b/shell.qml @@ -15,6 +15,7 @@ Scope { id: root property alias appLauncherPanel: appLauncherPanel + property var notificationHistoryWin: notificationHistoryWin function updateVolume(vol) { volume = vol; @@ -30,6 +31,7 @@ Scope { Bar { id: bar shell: root + property var notificationHistoryWin: notificationHistoryWin } Applauncher { @@ -47,6 +49,15 @@ Scope { console.log("Notification received:", notification.appName); notification.tracked = true; notificationPopup.addNotification(notification); + if (notificationHistoryWin) { + notificationHistoryWin.addToHistory({ + id: notification.id, + appName: notification.appName || "Notification", + summary: notification.summary || "", + body: notification.body || "", + timestamp: Date.now() + }); + } } } @@ -55,6 +66,11 @@ Scope { barVisible: bar.visible } + // Notification History Window + NotificationHistory { + id: notificationHistoryWin + } + property var defaultAudioSink: Pipewire.defaultAudioSink property int volume: defaultAudioSink && defaultAudioSink.audio && defaultAudioSink.audio.volume ? Math.round(defaultAudioSink.audio.volume * 100) : 0