island: add notification counter

This commit is contained in:
bbedward
2026-08-23 20:25:58 -04:00
parent d4069a778b
commit 4cf375f2fd
11 changed files with 227 additions and 185 deletions
+2 -1
View File
@@ -994,9 +994,10 @@ Singleton {
property string dankIslandPalette: "default"
property bool dankIslandHighContrast: false
property bool dankIslandMediaClockVisible: true
property bool dankIslandHomeNotificationBadge: true
property bool dankIslandNotificationExpand: false
property string dankIslandHomeMediaSlot: "left"
property string dankIslandHomeStatusSlot: "right"
property string dankIslandHomeStatusSlot: "hidden"
property string dankIslandHomeWeatherSlot: "hidden"
property bool dankIslandHomeCompactTight: false
property string dankIslandBatteryStyle: "solid"
+2 -1
View File
@@ -534,9 +534,10 @@ var SPEC = {
dankIslandPalette: { def: "default" },
dankIslandHighContrast: { def: false },
dankIslandMediaClockVisible: { def: true },
dankIslandHomeNotificationBadge: { def: true },
dankIslandNotificationExpand: { def: false },
dankIslandHomeMediaSlot: { def: "left" },
dankIslandHomeStatusSlot: { def: "right" },
dankIslandHomeStatusSlot: { def: "hidden" },
dankIslandHomeWeatherSlot: { def: "hidden" },
dankIslandHomeCompactTight: { def: false },
dankIslandBatteryStyle: { def: "solid" },
@@ -15,22 +15,28 @@ Item {
readonly property string timeText: systemClock.date.toLocaleTimeString(I18n.locale(), SettingsData.getEffectiveTimeFormat())
readonly property string dateText: systemClock.date.toLocaleDateString(I18n.locale(), SettingsData.getEffectiveDateFormat("ddd MMM d"))
readonly property bool tight: root.controller.homeCompactTight
readonly property real slotSize: root.tight ? 24 : 32
readonly property real iconSize: root.tight ? 16 : Theme.iconSizeSmall
readonly property real itemSpacing: root.tight ? Theme.spacingXS : Theme.spacingS
readonly property real dotSize: root.tight ? 3 : 4
readonly property real slotSize: root.tight ? Theme.iconSize : Theme.iconSizeLarge
readonly property real textSize: root.tight ? Theme.fontSizeSmall : Theme.fontSizeMedium
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 bool weatherSlotEnabled: root.controller.homeWeatherSlot !== "hidden"
readonly property var clusterIds: root.itemsForSide("left").concat(["clock"]).concat(root.itemsForSide("right"))
readonly property var groupIds: root.groupsForSide("left").concat(["clock"]).concat(root.groupsForSide("right"))
property bool weatherRefHeld: false
function itemsForSide(side) {
function groupsForSide(side) {
const ids = [];
if (root.controller.homeMediaSlot === side)
const media = root.controller.homeMediaSlot === side;
if (media && side === "left")
ids.push("media");
if (root.controller.homeWeatherSlot === side)
if (root.controller.homeWeatherSlot === side && WeatherService.weather.available)
ids.push("weather");
if (root.controller.homeStatusSlot === side)
ids.push("status");
if (side === "right" && root.controller.homeNotificationBadge)
ids.push("notifications");
if (media && side === "right")
ids.push("media");
return ids;
}
@@ -66,16 +72,6 @@ Item {
precision: SettingsData.showSeconds ? SystemClock.Seconds : SystemClock.Minutes
}
component ClusterDot: Rectangle {
property bool unread: false
anchors.verticalCenter: parent.verticalCenter
width: root.dotSize + (unread ? 1 : 0)
height: width
radius: height / 2
color: unread ? Theme.secondary : Theme.primary
}
component HoverBackdrop: Rectangle {
property bool hovered: false
@@ -86,184 +82,173 @@ Item {
color: hovered ? Theme.surfaceTextHover : "transparent"
}
component ClusterItem: Row {
component GroupItem: Item {
id: item
required property string itemId
property int itemIndex: 0
required property string groupId
readonly property bool isClock: item.itemId === "clock"
readonly property bool isMedia: item.itemId === "media"
readonly property bool isWeather: item.itemId === "weather"
readonly property bool isStatus: item.itemId === "status"
readonly property bool isClock: item.groupId === "clock"
readonly property bool isMedia: item.groupId === "media"
readonly property bool isWeather: item.groupId === "weather"
readonly property bool isStatus: item.groupId === "status"
readonly property bool isNotifications: item.groupId === "notifications"
readonly property bool usesBattery: item.isStatus && BatteryService.batteryAvailable
spacing: root.itemSpacing
width: {
if (item.isMedia)
return root.iconSize;
if (item.isWeather)
return weatherRow.implicitWidth;
if (item.isNotifications)
return notificationRow.implicitWidth;
if (item.isStatus)
return item.usesBattery ? batteryMeter.width : root.iconSize;
return clockRow.implicitWidth;
}
height: root.slotSize
ClusterDot {
visible: item.itemIndex > 0
HoverBackdrop {
visible: item.isMedia || item.isWeather || item.isNotifications || (item.isStatus && !item.usesBattery)
hovered: groupArea.containsMouse
}
Item {
width: clockRow.implicitWidth
height: root.slotSize
visible: item.isClock
Row {
id: clockRow
anchors.verticalCenter: parent.verticalCenter
spacing: root.itemSpacing
NumericText {
anchors.verticalCenter: parent.verticalCenter
isMonospace: false
text: root.timeText
reserveText: root.timeText.replace(/\d/g, "0")
width: Math.ceil(Math.max(implicitWidth, reservedWidth))
horizontalAlignment: Text.AlignHCenter
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeSmall
}
ClusterDot {
id: unreadDot
unread: root.controller.homeNotificationBadge
IslandSlotHoverArea {
anchors.centerIn: parent
width: parent.width + Theme.spacingXS
height: parent.height + Theme.spacingXS
enabled: unreadDot.unread
controller: root.controller
onClicked: root.controller.requestNotificationCenter(false)
}
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: root.dateText
color: Theme.surfaceTextMedium
font.pixelSize: Theme.fontSizeSmall
}
}
}
Item {
AudioVisualization {
anchors.centerIn: parent
width: root.iconSize + Theme.spacingXS
height: root.slotSize
visible: item.isMedia
HoverBackdrop {
hovered: mediaArea.containsMouse
}
AudioVisualization {
anchors.centerIn: parent
width: root.iconSize + Theme.spacingXS
height: width
maxBarHeight: Math.max(3, height - 2)
idleIconName: "graphic_eq"
visible: root.controller.mediaAvailable
}
DankIcon {
anchors.centerIn: parent
visible: !root.controller.mediaAvailable
name: "search"
size: root.iconSize
color: Theme.surfaceTextMedium
}
IslandSlotHoverArea {
id: mediaArea
anchors.fill: parent
controller: root.controller
onClicked: {
if (root.controller.mediaAvailable) {
root.controller.requestActivity("media", false, false);
return;
}
root.controller.requestLauncher("", "", false);
}
}
height: width
maxBarHeight: Math.max(3, height - 2)
idleIconName: "graphic_eq"
visible: item.isMedia && root.controller.mediaAvailable
}
Item {
width: weatherRow.implicitWidth
height: root.slotSize
visible: item.isWeather
DankIcon {
anchors.centerIn: parent
visible: item.isMedia && !root.controller.mediaAvailable
name: "search"
size: root.iconSize
color: Theme.surfaceTextMedium
}
HoverBackdrop {
hovered: weatherArea.containsMouse
}
Row {
id: clockRow
Row {
id: weatherRow
anchors.verticalCenter: parent.verticalCenter
visible: item.isClock
spacing: Theme.spacingS
NumericText {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXXS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
size: root.iconSize
color: Theme.primary
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: WeatherService.currentTempText()
color: Theme.surfaceTextSecondary
font.pixelSize: Theme.fontSizeSmall
}
isMonospace: false
text: root.timeText
reserveText: root.timeText.replace(/\d/g, "0")
width: Math.ceil(Math.max(implicitWidth, reservedWidth))
color: Theme.surfaceText
font.pixelSize: root.textSize
}
IslandSlotHoverArea {
id: weatherArea
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: Theme.spacingXS
height: Theme.spacingXS
radius: height / 2
color: Theme.primary
}
anchors.fill: parent
controller: root.controller
onClicked: root.controller.requestWeather(false)
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: root.dateText
color: Theme.surfaceTextMedium
font.pixelSize: root.textSize
}
}
Item {
width: item.usesBattery ? batteryMeter.width : root.iconSize
height: root.slotSize
visible: item.isStatus
Row {
id: notificationRow
HoverBackdrop {
hovered: statusArea.containsMouse && !item.usesBattery
}
BatteryMeter {
id: batteryMeter
anchors.centerIn: parent
visible: item.usesBattery
thickness: root.tight ? 12 : 14
fontSize: Theme.fontSizeSmall
hovered: statusArea.containsMouse
outlined: SettingsData.dankIslandBatteryStyle === "outline"
}
anchors.verticalCenter: parent.verticalCenter
visible: item.isNotifications
spacing: Theme.spacingXXS
DankIcon {
anchors.centerIn: parent
visible: !item.usesBattery
name: "tune"
size: root.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
name: "notifications"
size: root.statusIconSize
color: Theme.secondary
}
IslandSlotHoverArea {
id: statusArea
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: root.controller.unreadNotificationCount
color: Theme.surfaceTextSecondary
font.pixelSize: root.textSize
}
}
anchors.fill: parent
controller: root.controller
onClicked: root.controller.requestControlCenter("", false)
Row {
id: weatherRow
anchors.verticalCenter: parent.verticalCenter
visible: item.isWeather
spacing: Theme.spacingXXS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
size: root.statusIconSize
color: Theme.surfaceTextSecondary
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: WeatherService.currentTempText()
color: Theme.surfaceTextSecondary
font.pixelSize: root.textSize
}
}
BatteryMeter {
id: batteryMeter
anchors.centerIn: parent
visible: item.usesBattery
thickness: root.statusIconSize
fontSize: root.textSize
hovered: groupArea.containsMouse
outlined: SettingsData.dankIslandBatteryStyle === "outline"
}
DankIcon {
anchors.centerIn: parent
visible: item.isStatus && !item.usesBattery
name: "tune"
size: root.iconSize
color: Theme.surfaceText
}
IslandSlotHoverArea {
id: groupArea
anchors.fill: parent
enabled: !item.isClock
controller: root.controller
onClicked: {
if (item.isMedia && root.controller.mediaAvailable) {
root.controller.requestActivity("media", false, false);
return;
}
if (item.isMedia) {
root.controller.requestLauncher("", "", false);
return;
}
if (item.isWeather) {
root.controller.requestWeather(false);
return;
}
if (item.isNotifications) {
root.controller.requestNotificationCenter(false);
return;
}
root.controller.requestControlCenter("", false);
}
}
}
@@ -273,16 +258,14 @@ Item {
anchors.centerIn: parent
height: root.slotSize
spacing: root.itemSpacing
spacing: root.groupSpacing
Repeater {
model: root.clusterIds
model: root.groupIds
ClusterItem {
GroupItem {
required property var modelData
required property int index
itemId: String(modelData)
itemIndex: index
groupId: String(modelData)
}
}
}
@@ -115,6 +115,7 @@ Item {
text: root.mediaModel.artist
color: Theme.surfaceTextSecondary
font.pixelSize: Math.max(9, Theme.fontSizeSmall - 2)
wrapMode: Text.NoWrap
elide: Text.ElideRight
}
}
@@ -79,6 +79,7 @@ Item {
color: Theme.surfaceTextSecondary
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
wrapMode: Text.NoWrap
elide: Text.ElideRight
}
@@ -88,6 +89,7 @@ Item {
color: Theme.surfaceText
font.pixelSize: root.dense ? Theme.fontSizeSmall : Theme.fontSizeMedium
font.weight: Font.DemiBold
wrapMode: Text.NoWrap
elide: Text.ElideRight
}
}
@@ -235,6 +235,8 @@ Item {
id: contentHost
controller: root.controller
islandX: root.currentVisualX
hostWidth: root.width
springTimeConstantMs: root.springTimeConstantMs
morphProgress: root.morphProgress
expanded: root.controller.expanded
@@ -13,6 +13,8 @@ Item {
clip: true
required property var controller
required property real islandX
required property real hostWidth
required property real springTimeConstantMs
required property real morphProgress
required property bool expanded
@@ -148,7 +150,13 @@ Item {
}
component CompactFace: Loader {
anchors.fill: parent
required property string activity
readonly property var target: root.controller.compactTargetFor(activity)
x: Math.round((root.hostWidth - target.width) / 2 + target.offsetX) - Math.round(root.islandX)
y: Math.round((parent.height - height) / 2)
width: target.width
height: target.height
asynchronous: false
visible: opacity > 0.001
enabled: opacity >= 0.5
@@ -166,6 +174,7 @@ Item {
CompactFace {
active: true
activity: "home"
sourceComponent: root.homeCompactComponent
opacity: root.compactOpacity("home")
}
@@ -182,6 +191,7 @@ Item {
CompactFace {
active: root.mediaSurfaceActive
activity: "media"
sourceComponent: root.mediaCompactComponent
opacity: root.compactOpacity("media")
}
@@ -198,6 +208,7 @@ Item {
CompactFace {
active: root.surfaceActive("launcher")
activity: "launcher"
sourceComponent: root.launcherCompactComponent
opacity: root.compactOpacity("launcher")
}
@@ -214,6 +225,7 @@ Item {
CompactFace {
active: root.surfaceActive("controlcenter")
activity: "controlcenter"
sourceComponent: root.controlCenterCompactComponent
opacity: root.compactOpacity("controlcenter")
}
@@ -228,6 +240,7 @@ Item {
CompactFace {
active: root.surfaceActive("wallpaper")
activity: "wallpaper"
sourceComponent: root.wallpaperCompactComponent
opacity: root.compactOpacity("wallpaper")
}
@@ -244,6 +257,7 @@ Item {
CompactFace {
active: root.surfaceActive("weather")
activity: "weather"
sourceComponent: root.weatherCompactComponent
opacity: root.compactOpacity("weather")
}
@@ -260,6 +274,7 @@ Item {
CompactFace {
active: root.surfaceActive("notificationcenter")
activity: "notificationcenter"
sourceComponent: root.notificationCenterCompactComponent
opacity: root.compactOpacity("notificationcenter")
}
@@ -276,6 +291,7 @@ Item {
CompactFace {
active: root.systemSurfaceActive
activity: "volume"
sourceComponent: root.systemCompactComponent
opacity: Math.max(root.compactOpacity("volume"), root.compactOpacity("brightness"))
}
@@ -290,6 +306,7 @@ Item {
CompactFace {
active: root.surfaceActive("notification")
activity: "notification"
sourceComponent: root.notificationCompactComponent
opacity: root.compactOpacity("notification")
}
@@ -59,10 +59,8 @@ QtObject {
readonly property real homeCompactFaceHeight: homeCompactTight ? Math.max(16, Math.min(32, compactHeight - 8)) : compactFaceHeight
property int unreadNotificationCount: 0
readonly property bool homeNotificationBadge: unreadNotificationCount > 0
readonly property real homeSlotMargin: homeCompactTight ? Theme.spacingXS : Theme.spacingS
readonly property real homeSlotSpacing: homeCompactTight ? Theme.spacingXXS : Theme.spacingXS
readonly property real homeClusterGap: homeCompactTight ? 6 : 10
readonly property bool homeNotificationBadge: SettingsData.dankIslandHomeNotificationBadge && unreadNotificationCount > 0
readonly property real homeSlotMargin: homeCompactTight ? Theme.spacingS : Theme.spacingM
property real homeContentWidth: 200
property real mediaContentWidth: 360
readonly property real mediaCompactMaxWidth: 360
@@ -273,7 +271,7 @@ QtObject {
}
readonly property string homeMediaSlot: resolvedHomeSlot(SettingsData.dankIslandHomeMediaSlot, "left")
readonly property string homeStatusSlot: resolvedHomeSlot(SettingsData.dankIslandHomeStatusSlot, "right")
readonly property string homeStatusSlot: resolvedHomeSlot(SettingsData.dankIslandHomeStatusSlot, "hidden")
readonly property string homeWeatherSlot: SettingsData.weatherEnabled ? resolvedHomeSlot(SettingsData.dankIslandHomeWeatherSlot, "hidden") : "hidden"
readonly property real homeCompactWidth: Math.ceil(homeSlotMargin * 2 + Math.max(homeContentWidth, 1))
+11 -2
View File
@@ -190,10 +190,10 @@ Item {
text: I18n.tr("Battery / Control Center", "island settings: battery or control center slot row")
description: BatteryService.batteryAvailable ? I18n.tr("Battery gauge opens Control Center", "island settings: status slot description with battery") : I18n.tr("Tools icon opens Control Center", "island settings: status slot description without battery")
model: [I18n.tr("Left", "island settings: battery or control center slot left of the clock"), I18n.tr("Right", "island settings: battery or control center slot right of the clock"), I18n.tr("Hidden", "island settings: battery or control center slot hidden")]
currentIndex: root.valueIndex(root.homeSlotValues, SettingsData.dankIslandHomeStatusSlot, "right")
currentIndex: root.valueIndex(root.homeSlotValues, SettingsData.dankIslandHomeStatusSlot, "hidden")
onSelectionChanged: (index, selected) => {
if (selected)
SettingsData.set("dankIslandHomeStatusSlot", root.homeSlotValues[index] ?? "right");
SettingsData.set("dankIslandHomeStatusSlot", root.homeSlotValues[index] ?? "hidden");
}
}
@@ -293,6 +293,15 @@ 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"]
+2
View File
@@ -28,6 +28,8 @@ ShellRoot {
DC.Log.backend = Log;
DC.Host.session = SessionService;
DC.Host.cache = CacheData;
// Build the polkit agent here, outside incubation: first-touching it from a Connections target during DMSShell's async load crashed QQmlConnections::connectSignalsToMethods.
void PolkitService.agent;
}
Loader {
@@ -12549,6 +12549,32 @@
"icon": "widgets",
"description": "Place independent widgets to left and right of island"
},
{
"section": "dankIslandHomeNotificationBadge",
"label": "Show Badge",
"tabIndex": 46,
"category": "Dank Island",
"keywords": [
"activities",
"alert",
"badge",
"beside",
"clock",
"count",
"dank",
"dynamic island",
"home",
"island",
"notif",
"notification",
"notifications",
"show",
"time",
"unread",
"watch"
],
"description": "Unread notification count beside the clock"
},
{
"section": "dankIslandSatellitesEnabled",
"label": "Show Satellite Widgets",