NotificationHistory Updates

Add read/unread bell icon
Edit style of the Panel
Small fixes
This commit is contained in:
ly-sec 2025-07-17 16:56:51 +02:00
parent cf26fe52d9
commit dbb5a9160c
6 changed files with 416 additions and 409 deletions

View file

@ -9,11 +9,11 @@ import qs.Settings
import qs.Services import qs.Services
import qs.Components import qs.Components
import qs.Widgets import qs.Widgets
import qs.Widgets.Notification
import qs.Widgets.Sidebar import qs.Widgets.Sidebar
import qs.Widgets.Sidebar.Panel import qs.Widgets.Sidebar.Panel
import qs.Helpers import qs.Helpers
import QtQuick.Controls import QtQuick.Controls
import qs.Widgets.Notification
Scope { Scope {
id: rootScope id: rootScope
@ -83,40 +83,9 @@ Scope {
anchors.rightMargin: 18 anchors.rightMargin: 18
spacing: 12 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 { NotificationHistory {
id: notificationHistoryWin id: notificationHistoryWin
anchors.verticalCenter: parent.verticalCenter
} }
Brightness { Brightness {
@ -158,28 +127,25 @@ Scope {
} }
} }
Background {} Background {}
Overview {} Overview {}
} }
PanelWindow { PanelWindow {
id: topLeftPanel id: topCornerPanel
anchors.top: true anchors.top: true
anchors.left: true anchors.left: true
anchors.right: true
color: "transparent" color: "transparent"
screen: modelData screen: modelData
margins.top: 36 margins.top: 36
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
visible: true visible: true
WlrLayershell.layer: WlrLayer.Background
aboveWindows: false
WlrLayershell.namespace: "swww-daemon"
implicitHeight: 24 implicitHeight: 24
Corners { Corners {
id: topLeftCorner id: topleftCorner
position: "bottomleft" position: "bottomleft"
size: 1.3 size: 1.3
fillColor: (Theme.backgroundPrimary !== undefined && Theme.backgroundPrimary !== null) ? Theme.backgroundPrimary : "#222" fillColor: (Theme.backgroundPrimary !== undefined && Theme.backgroundPrimary !== null) ? Theme.backgroundPrimary : "#222"
@ -187,25 +153,9 @@ Scope {
offsetY: 0 offsetY: 0
anchors.top: parent.top 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 { Corners {
id: topRightCorner id: toprightCorner
position: "bottomright" position: "bottomright"
size: 1.3 size: 1.3
fillColor: (Theme.backgroundPrimary !== undefined && Theme.backgroundPrimary !== null) ? Theme.backgroundPrimary : "#222" fillColor: (Theme.backgroundPrimary !== undefined && Theme.backgroundPrimary !== null) ? Theme.backgroundPrimary : "#222"
@ -223,9 +173,6 @@ Scope {
screen: modelData screen: modelData
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
visible: true visible: true
WlrLayershell.layer: WlrLayer.Background
aboveWindows: false
WlrLayershell.namespace: "swww-daemon"
implicitHeight: 24 implicitHeight: 24
@ -241,17 +188,14 @@ Scope {
} }
PanelWindow { PanelWindow {
id: bottomRightPanel id: bottomRightCornerPanel
anchors.bottom: true anchors.bottom: true
anchors.right: true anchors.right: true
color: "transparent" color: "transparent"
screen: modelData screen: modelData
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
visible: true visible: true
WlrLayershell.layer: WlrLayer.Background
aboveWindows: false
WlrLayershell.namespace: "swww-daemon"
implicitHeight: 24 implicitHeight: 24
Corners { Corners {
@ -264,6 +208,10 @@ Scope {
anchors.top: parent.top anchors.top: parent.top
} }
} }
Loader {
id: tabViewerLoader
}
} }
} }
@ -271,4 +219,4 @@ Scope {
// This alias exposes the visual bar's visibility to the outside world // This alias exposes the visual bar's visibility to the outside world
property alias visible: barRootItem.visible property alias visible: barRootItem.visible
} }

View file

@ -3,6 +3,7 @@ import Quickshell.Io
IpcHandler { IpcHandler {
property var appLauncherPanel property var appLauncherPanel
property var lockScreen property var lockScreen
property var tabViewer
target: "globalIPC" target: "globalIPC"

View file

@ -89,7 +89,7 @@ Singleton {
id: i, id: i,
idx: ws.id, idx: ws.id,
name: ws.name || "", name: ws.name || "",
output: ws.monitor?.name || "", output: ws.monitor.name || "",
isActive: ws.active === true, isActive: ws.active === true,
isFocused: ws.focused === true, isFocused: ws.focused === true,
isUrgent: ws.urgent === true isUrgent: ws.urgent === true

View file

@ -1,28 +1,28 @@
{ {
"backgroundPrimary": "#111211", "backgroundPrimary": "#0E0F10",
"backgroundSecondary": "#1D1E1D", "backgroundSecondary": "#1A1B1C",
"backgroundTertiary": "#292A29", "backgroundTertiary": "#262728",
"surface": "#242524", "surface": "#212223",
"surfaceVariant": "#353635", "surfaceVariant": "#323334",
"textPrimary": "#F4E9E3", "textPrimary": "#F0F1E0",
"textSecondary": "#DCD2CC", "textSecondary": "#D8D9CA",
"textDisabled": "#928C88", "textDisabled": "#909186",
"accentPrimary": "#7D8079", "accentPrimary": "#A3A485",
"accentSecondary": "#979994", "accentSecondary": "#B5B69D",
"accentTertiary": "#646661", "accentTertiary": "#82836A",
"error": "#939849", "error": "#A5A9ED",
"warning": "#ABAF72", "warning": "#B9BCF1",
"highlight": "#B1B3AF", "highlight": "#C8C8B6",
"rippleEffect": "#8A8D86", "rippleEffect": "#ACAD91",
"onAccent": "#111211", "onAccent": "#0E0F10",
"outline": "#585958", "outline": "#565758",
"shadow": "#111211", "shadow": "#0E0F10",
"overlay": "#111211" "overlay": "#0E0F10"
} }

View file

@ -1,357 +1,399 @@
import QtQuick import QtQuick 2.15
import QtQuick.Controls import QtQuick.Controls 2.15
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.Components import qs.Components
import qs.Settings import qs.Settings
import QtQuick.Layouts import QtQuick.Layouts 1.15
PanelWindow { Item {
id: notificationHistoryWin id: root
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
property string configDir: Quickshell.configDir property string configDir: Quickshell.configDir
property string historyFilePath: configDir + "/notification_history.json" 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 { // Bell icon/button
id: historyModel // Holds notification objects Item {
} id: bell
width: 22; height: 22
FileView { Text {
id: historyFileView anchors.centerIn: parent
path: historyFilePath text: root.hasUnread ? "notifications_unread" : "notifications"
blockLoading: true font.family: mouseAreaBell.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
printErrors: true font.pixelSize: 16
watchChanges: true font.weight: root.hasUnread ? Font.Bold : Font.Normal
color: mouseAreaBell.containsMouse ? Theme.accentPrimary : (root.hasUnread ? Theme.accentPrimary : Theme.textDisabled)
JsonAdapter {
id: historyAdapter
property var notifications: [] // Array of notification objects
} }
MouseArea {
onFileChanged: { id: mouseAreaBell
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 {
anchors.fill: parent anchors.fill: parent
anchors.margins: 16 hoverEnabled: true
spacing: 8 cursorShape: Qt.PointingHandCursor
onClicked: notificationHistoryWin.visible = !notificationHistoryWin.visible
}
}
RowLayout { // The popup window
spacing: 4 PanelWindow {
anchors.top: parent.top id: notificationHistoryWin
anchors.topMargin: 16 width: 400
anchors.left: parent.left property int maxPopupHeight: 500
anchors.right: parent.right property int minPopupHeight: 230
anchors.leftMargin: 16 property int contentHeight: headerRow.height + historyList.contentHeight + 56
anchors.rightMargin: 16 height: Math.max(Math.min(contentHeight, maxPopupHeight), minPopupHeight)
Text { color: "transparent"
text: "Notification History" visible: false
font.pixelSize: 18 screen: Quickshell.primaryScreen
font.bold: true focusable: true
color: Theme.textPrimary anchors.top: true
Layout.alignment: Qt.AlignVCenter anchors.right: true
} margins.top: 4
Item { Layout.fillWidth: true } margins.right: 4
Rectangle {
id: clearAllButton property int maxHistory: 100
width: 90 property bool hasUnread: false
height: 32 signal unreadChanged(bool hasUnread)
radius: 20
color: clearAllMouseArea.containsMouse ? Theme.accentPrimary : Theme.surfaceVariant ListModel {
border.color: Theme.accentPrimary id: historyModel
border.width: 1 }
Layout.alignment: Qt.AlignVCenter
Row { FileView {
anchors.centerIn: parent id: historyFileView
spacing: 6 path: root.historyFilePath
Text { blockLoading: true
text: "delete_sweep" printErrors: true
font.family: "Material Symbols Outlined" watchChanges: true
font.pixelSize: 14
color: clearAllMouseArea.containsMouse ? Theme.onAccent : Theme.accentPrimary JsonAdapter {
verticalAlignment: Text.AlignVCenter id: historyAdapter
} property var notifications: []
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 { onFileChanged: historyFileView.reload()
width: parent.width onLoaded: notificationHistoryWin.loadHistory()
height: 0 onLoadFailed: function(error) {
color: "transparent" if (error.includes("No such file")) {
visible: true historyAdapter.notifications = []
} writeAdapter()
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
} }
}
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 { Rectangle {
id: listContainer width: parent.width
anchors.fill: parent height: 0
anchors.topMargin: 12
anchors.bottomMargin: 12
color: "transparent" color: "transparent"
clip: true visible: true
ListView { }
id: historyList
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 anchors.fill: parent
spacing: 12 color: Theme.surface
model: historyModel radius: 20
delegate: Item { z: 0
height: notificationCard.implicitHeight + 12 }
Rectangle { Rectangle {
id: notificationCard id: listContainer
width: parent.width - 24 anchors.fill: parent
anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 12
color: Theme.backgroundPrimary anchors.bottomMargin: 12
radius: 16 color: "transparent"
border.color: Theme.outline clip: true
border.width: 1 Column {
anchors.top: parent.top anchors.fill: parent
anchors.bottom: parent.bottom spacing: 0
anchors.margins: 0 Item { id: topSpacer; height: (parent.height - historyList.height) / 2 }
implicitHeight: contentColumn.implicitHeight + 20 ListView {
Column { id: historyList
id: contentColumn width: parent.width
anchors.fill: parent height: Math.min(contentHeight, parent.height)
anchors.margins: 14 spacing: 12
spacing: 6 model: historyModel.count > 0 ? historyModel : placeholderModel
RowLayout { clip: true
id: headerRow delegate: Item {
spacing: 8 width: parent.width
anchors.left: parent.left height: notificationCard.implicitHeight + 12
anchors.right: parent.right Rectangle {
anchors.rightMargin: 0 id: notificationCard
Rectangle { width: parent.width - 24
id: iconBackground anchors.horizontalCenter: parent.horizontalCenter
width: 28 color: Theme.backgroundPrimary
height: 28 radius: 16
radius: 20 anchors.top: parent.top
color: Theme.accentPrimary anchors.bottom: parent.bottom
border.color: Qt.darker(Theme.accentPrimary, 1.2) anchors.margins: 0
border.width: 1.2 implicitHeight: contentColumn.implicitHeight + 20
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 { Column {
id: appInfoColumn id: contentColumn
spacing: 0 anchors.fill: parent
Layout.alignment: Qt.AlignVCenter anchors.margins: 14
Text { spacing: 6
text: model.appName || "Unknown App" RowLayout {
font.bold: true id: headerRow
color: Theme.textPrimary spacing: 8
font.family: Theme.fontFamily Rectangle {
font.pixelSize: Theme.fontSizeSmall id: iconBackground
verticalAlignment: Text.AlignVCenter 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 {
text: formatTimestamp(model.timestamp) text: model.summary || (model.isPlaceholder ? "You're all caught up!" : "")
color: Theme.textSecondary color: Theme.textSecondary
font.family: Theme.fontFamily font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeCaption font.pixelSize: Theme.fontSizeBody
verticalAlignment: Text.AlignVCenter 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
}
}
}
} }
} }
} }

View file

@ -15,6 +15,7 @@ Scope {
id: root id: root
property alias appLauncherPanel: appLauncherPanel property alias appLauncherPanel: appLauncherPanel
property var notificationHistoryWin: notificationHistoryWin
function updateVolume(vol) { function updateVolume(vol) {
volume = vol; volume = vol;
@ -30,6 +31,7 @@ Scope {
Bar { Bar {
id: bar id: bar
shell: root shell: root
property var notificationHistoryWin: notificationHistoryWin
} }
Applauncher { Applauncher {
@ -47,6 +49,15 @@ Scope {
console.log("Notification received:", notification.appName); console.log("Notification received:", notification.appName);
notification.tracked = true; notification.tracked = true;
notificationPopup.addNotification(notification); 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 barVisible: bar.visible
} }
// Notification History Window
NotificationHistory {
id: notificationHistoryWin
}
property var defaultAudioSink: Pipewire.defaultAudioSink property var defaultAudioSink: Pipewire.defaultAudioSink
property int volume: defaultAudioSink && defaultAudioSink.audio && defaultAudioSink.audio.volume ? Math.round(defaultAudioSink.audio.volume * 100) : 0 property int volume: defaultAudioSink && defaultAudioSink.audio && defaultAudioSink.audio.volume ? Math.round(defaultAudioSink.audio.volume * 100) : 0