diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 1cb0c6488..2e0fa2329 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -995,6 +995,7 @@ Singleton { property bool dankIslandHighContrast: false property bool dankIslandMediaClockVisible: true property bool dankIslandHomeNotificationBadge: true + property bool dankIslandNotificationBadgeClearOnOpen: false property bool dankIslandNotificationExpand: false property string dankIslandHomeMediaSlot: "left" property string dankIslandHomeStatusSlot: "hidden" diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index f9de2b255..475c4af91 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -535,6 +535,7 @@ var SPEC = { dankIslandHighContrast: { def: false }, dankIslandMediaClockVisible: { def: true }, dankIslandHomeNotificationBadge: { def: true }, + dankIslandNotificationBadgeClearOnOpen: { def: false }, dankIslandNotificationExpand: { def: false }, dankIslandHomeMediaSlot: { def: "left" }, dankIslandHomeStatusSlot: { def: "hidden" }, diff --git a/quickshell/Modules/DankIsland/Activities/HomeCompact.qml b/quickshell/Modules/DankIsland/Activities/HomeCompact.qml index 9cdf531cb..0b2dd595c 100644 --- a/quickshell/Modules/DankIsland/Activities/HomeCompact.qml +++ b/quickshell/Modules/DankIsland/Activities/HomeCompact.qml @@ -20,6 +20,7 @@ Item { readonly property real iconSize: root.textSize + Theme.spacingXS readonly property real statusIconSize: root.textSize + Theme.spacingXXS readonly property real groupSpacing: root.controller.homeSlotMargin + readonly property real edgePad: Math.max(root.groupSpacing, (root.width - compactRow.width) / 2) readonly property bool weatherSlotEnabled: root.controller.homeWeatherSlot !== "hidden" readonly property var groupIds: root.groupsForSide("left").concat(["clock"]).concat(root.groupsForSide("right")) property bool weatherRefHeld: false @@ -86,7 +87,10 @@ Item { id: item required property string groupId + required property int slotIndex + readonly property real leadPad: item.slotIndex === 0 ? root.edgePad : root.groupSpacing / 2 + readonly property real trailPad: item.slotIndex === root.groupIds.length - 1 ? root.edgePad : root.groupSpacing / 2 readonly property bool isClock: item.groupId === "clock" readonly property bool isMedia: item.groupId === "media" readonly property bool isWeather: item.groupId === "weather" @@ -228,7 +232,10 @@ Item { IslandSlotHoverArea { id: groupArea - anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter + x: -item.leadPad + width: parent.width + item.leadPad + item.trailPad + height: root.height enabled: !item.isClock controller: root.controller onClicked: { @@ -265,7 +272,9 @@ Item { GroupItem { required property var modelData + required property int index groupId: String(modelData) + slotIndex: index } } } diff --git a/quickshell/Modules/DankIsland/Activities/MediaCompact.qml b/quickshell/Modules/DankIsland/Activities/MediaCompact.qml index 7df7d05ef..612c125d4 100644 --- a/quickshell/Modules/DankIsland/Activities/MediaCompact.qml +++ b/quickshell/Modules/DankIsland/Activities/MediaCompact.qml @@ -18,6 +18,8 @@ Item { readonly property real textGutter: Theme.spacingXS readonly property real cavaWidth: 20 readonly property real minTextWidth: 48 + readonly property real clockLeadPad: Theme.spacingXS / 2 + readonly property real clockTrailPad: Theme.spacingS readonly property real clockPillHeight: root.dense ? Math.max(22, root.artworkSize - Theme.spacingXS) : 32 readonly property string timeText: systemClock.date.toLocaleTimeString(I18n.locale(), SettingsData.getEffectiveTimeFormat()) readonly property bool clockVisible: SettingsData.dankIslandMediaClockVisible @@ -155,7 +157,10 @@ Item { IslandSlotHoverArea { id: clockArea - anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter + x: -root.clockLeadPad + width: parent.width + root.clockLeadPad + root.clockTrailPad + height: root.height controller: root.controller onClicked: root.controller.requestActivity("home", false, false) } diff --git a/quickshell/Modules/DankIsland/Activities/NotificationCompact.qml b/quickshell/Modules/DankIsland/Activities/NotificationCompact.qml index 7a424ad04..9ffd9e86c 100644 --- a/quickshell/Modules/DankIsland/Activities/NotificationCompact.qml +++ b/quickshell/Modules/DankIsland/Activities/NotificationCompact.qml @@ -15,7 +15,7 @@ Item { readonly property string headerText: root.notificationModel.appName + (root.notificationModel.timeText ? " · " + root.notificationModel.timeText : "") readonly property string summaryText: root.dense && root.notificationModel.appName ? root.notificationModel.appName + " · " + root.notificationModel.summary : root.notificationModel.summary readonly property real criticalWidth: root.notificationModel.critical ? 3 + Theme.spacingS : 0 - readonly property real measuredWidth: Theme.spacingS * 3 + root.criticalWidth + root.iconSize + Math.max(root.dense ? 0 : headerMetrics.width, summaryMetrics.width) + readonly property real measuredWidth: Theme.spacingS * 3 + root.criticalWidth + root.iconSize + Theme.spacingXS + Math.max(root.dense ? 0 : headerLabel.implicitWidth, summaryLabel.implicitWidth) function pushMeasuredWidth() { root.controller.setNotificationContentWidth(root.measuredWidth); @@ -24,22 +24,6 @@ Item { onMeasuredWidthChanged: root.pushMeasuredWidth() Component.onCompleted: root.pushMeasuredWidth() - StyledTextMetrics { - id: headerMetrics - - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.Medium - text: root.headerText - } - - StyledTextMetrics { - id: summaryMetrics - - font.pixelSize: root.dense ? Theme.fontSizeSmall : Theme.fontSizeMedium - font.weight: Font.DemiBold - text: root.summaryText - } - Row { anchors { fill: parent @@ -73,6 +57,8 @@ Item { spacing: 1 StyledText { + id: headerLabel + width: parent.width visible: !root.dense text: root.headerText @@ -84,6 +70,8 @@ Item { } StyledText { + id: summaryLabel + width: parent.width text: root.summaryText color: Theme.surfaceText diff --git a/quickshell/Modules/DankIsland/DankIslandHostWindow.qml b/quickshell/Modules/DankIsland/DankIslandHostWindow.qml index 6156c5908..836747723 100644 --- a/quickshell/Modules/DankIsland/DankIslandHostWindow.qml +++ b/quickshell/Modules/DankIsland/DankIslandHostWindow.qml @@ -159,7 +159,7 @@ PanelWindow { controlCenterMaxHeight: root.maxActivityHeight notificationCenterMaxHeight: root.maxActivityHeight notificationExpandAllowed: SettingsData.dankIslandNotificationExpand - unreadNotificationCount: NotificationService.notifications.length + unreadNotificationCount: SettingsData.dankIslandNotificationBadgeClearOnOpen ? NotificationService.unreadCount : NotificationService.notifications.length hoverOpenDelay: Math.max(0, Math.min(1000, SettingsData.dankIslandHoverOpenDelay)) hoverCloseDelay: Math.max(0, Math.min(1000, SettingsData.dankIslandHoverCloseDelay)) } diff --git a/quickshell/Modules/DankIsland/IslandController.qml b/quickshell/Modules/DankIsland/IslandController.qml index 1417c921d..c2105007c 100644 --- a/quickshell/Modules/DankIsland/IslandController.qml +++ b/quickshell/Modules/DankIsland/IslandController.qml @@ -66,7 +66,7 @@ QtObject { readonly property real mediaCompactMaxWidth: 360 property real notificationContentWidth: 0 readonly property real notificationCompactMinWidth: compactDense ? 200 : 240 - readonly property real notificationCompactMaxWidth: compactDense ? 320 : 360 + readonly property real notificationCompactMaxWidth: mediaCompactMaxWidth signal sessionStarted(string activityId) diff --git a/quickshell/Modules/Settings/DankIslandTab.qml b/quickshell/Modules/Settings/DankIslandTab.qml index ac9162691..736385bfb 100644 --- a/quickshell/Modules/Settings/DankIslandTab.qml +++ b/quickshell/Modules/Settings/DankIslandTab.qml @@ -275,15 +275,6 @@ Item { onToggled: checked => SettingsData.set("dankIslandHighContrast", checked) } - SettingsToggleRow { - settingKey: "dankIslandNotificationExpand" - tags: ["island", "notifications", "expand", "arrival", "size"] - text: I18n.tr("Expand Notifications", "island settings: expanded notification toggle") - description: I18n.tr("Expand notifications by default instead of click or hover", "island settings: expanded notification description") - checked: SettingsData.dankIslandNotificationExpand - onToggled: checked => SettingsData.set("dankIslandNotificationExpand", checked) - } - SettingsToggleRow { settingKey: "dankIslandMediaClockVisible" tags: ["island", "media", "clock", "compact", "time"] @@ -293,15 +284,6 @@ Item { onToggled: checked => SettingsData.set("dankIslandMediaClockVisible", checked) } - SettingsToggleRow { - settingKey: "dankIslandHomeNotificationBadge" - tags: ["island", "home", "notifications", "badge", "unread", "count"] - text: I18n.tr("Show Badge", "island settings: unread notification count on the home face") - description: I18n.tr("Unread notification count beside the clock", "island settings: notification badge description") - checked: SettingsData.dankIslandHomeNotificationBadge - onToggled: checked => SettingsData.set("dankIslandHomeNotificationBadge", checked) - } - SettingsButtonGroupRow { settingKey: "dankIslandBatteryStyle" tags: ["island", "battery", "gauge", "solid", "outline", "appearance"] @@ -317,6 +299,42 @@ Item { } } + SettingsCard { + width: parent.width + iconName: "notifications" + title: I18n.tr("Notifications", "island settings: notifications card title") + settingKey: "dankIslandNotifications" + enabled: SettingsData.dankIslandEnabled + + SettingsToggleRow { + settingKey: "dankIslandNotificationExpand" + tags: ["island", "notifications", "expand", "arrival", "size"] + text: I18n.tr("Expand Notifications", "island settings: expanded notification toggle") + description: I18n.tr("Expand notifications by default instead of click or hover", "island settings: expanded notification description") + checked: SettingsData.dankIslandNotificationExpand + onToggled: checked => SettingsData.set("dankIslandNotificationExpand", checked) + } + + SettingsToggleRow { + settingKey: "dankIslandHomeNotificationBadge" + tags: ["island", "home", "notifications", "badge", "unread", "count"] + text: I18n.tr("Show Badge", "island settings: unread notification count on the home face") + description: I18n.tr("Unread notification count beside the clock", "island settings: notification badge description") + checked: SettingsData.dankIslandHomeNotificationBadge + onToggled: checked => SettingsData.set("dankIslandHomeNotificationBadge", checked) + } + + SettingsToggleRow { + settingKey: "dankIslandNotificationBadgeClearOnOpen" + tags: ["island", "home", "notifications", "badge", "unread", "clear", "dismiss", "open"] + text: I18n.tr("Clear Badge on Open", "island settings: clear the notification badge when the center opens") + description: I18n.tr("Clears the badge on open but keeps notifications active", "island settings: clear badge on open description") + checked: SettingsData.dankIslandNotificationBadgeClearOnOpen + enabled: SettingsData.dankIslandHomeNotificationBadge + onToggled: checked => SettingsData.set("dankIslandNotificationBadgeClearOnOpen", checked) + } + } + SettingsCard { width: parent.width iconName: "widgets" diff --git a/quickshell/Services/NotificationService.qml b/quickshell/Services/NotificationService.qml index 55fd396ad..1fb050d36 100644 --- a/quickshell/Services/NotificationService.qml +++ b/quickshell/Services/NotificationService.qml @@ -17,6 +17,23 @@ Singleton { readonly property list allWrappers: [] readonly property list popups: allWrappers.filter(n => n && n.popup) + property var seenNotifications: [] + readonly property int unreadCount: { + const seen = root.seenNotifications; + if (seen.length === 0) + return root.notifications.length; + let count = 0; + for (const wrapper of root.notifications) { + if (seen.indexOf(wrapper) === -1) + count++; + } + return count; + } + + function markNotificationsSeen() { + root.seenNotifications = root.notifications.slice(); + } + property var historyList: [] readonly property string historyFile: Paths.strip(Paths.cache) + "/notification_history.json" readonly property string imageCacheDir: Paths.strip(Paths.cache) + "/notification_images" @@ -548,6 +565,7 @@ Singleton { function onOverlayOpen() { popupsDisabled = true; + markNotificationsSeen(); addGate.stop(); addGateBusy = false; @@ -564,6 +582,7 @@ Singleton { function onOverlayClose() { popupsDisabled = false; + markNotificationsSeen(); processQueue(); } diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index 13d42fd35..c4be94081 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -11991,6 +11991,33 @@ "icon": "touch_app", "description": "Click expands only on an intentional press. Hybrid peeks the current compact face on hover" }, + { + "section": "dankIslandNotificationBadgeClearOnOpen", + "label": "Clear Badge on Open", + "tabIndex": 46, + "category": "Dank Island", + "keywords": [ + "active", + "activities", + "alert", + "alerts", + "badge", + "clear", + "clears", + "dank", + "dismiss", + "dynamic island", + "home", + "island", + "keeps", + "notif", + "notifications", + "notifs", + "open", + "unread" + ], + "description": "Clears the badge on open but keeps notifications active" + }, { "section": "dankIslandHomeCompactTight", "label": "Compact Clock Pill", @@ -12369,6 +12396,31 @@ "icon": "animation", "description": "Apply island geometry changes immediately without spring overshoot" }, + { + "section": "dankIslandNotifications", + "label": "Notifications", + "tabIndex": 46, + "category": "Dank Island", + "keywords": [ + "activities", + "alert", + "alerts", + "arrival", + "click", + "dank", + "default", + "dynamic island", + "expand", + "hover", + "island", + "notif", + "notifications", + "notifs", + "size" + ], + "icon": "notifications", + "description": "Expand notifications by default instead of click or hover" + }, { "section": "dankIslandHoverOpenDelay", "label": "Open Delay",