diff --git a/Modules/AppLauncher.qml b/Modules/AppLauncher.qml index 33c41bce..d6917ea1 100644 --- a/Modules/AppLauncher.qml +++ b/Modules/AppLauncher.qml @@ -221,6 +221,7 @@ PanelWindow { } Connections { + target: AppSearchService function onApplicationsChanged() { console.log("AppLauncher: DesktopEntries.applicationsChanged signal received"); // Update categories when applications change @@ -231,8 +232,6 @@ PanelWindow { })); updateFilteredModel(); } - - target: DesktopEntries } Connections { diff --git a/Modules/ClipboardHistory.qml b/Modules/ClipboardHistory.qml index f9185685..117ad178 100644 --- a/Modules/ClipboardHistory.qml +++ b/Modules/ClipboardHistory.qml @@ -868,15 +868,6 @@ PanelWindow { else console.warn("ClipboardHistory: Failed to load clipboard history"); } - // Handle keyboard shortcuts - Keys.onPressed: (event) => { - if (event.key === Qt.Key_Escape) - clipboardHistory.hide(); - - } - Component.onCompleted: { - focus = true; - } stdout: SplitParser { splitMarker: "\n" diff --git a/Modules/ControlCenter/NetworkTab.qml b/Modules/ControlCenter/NetworkTab.qml index b5918d76..61cb0e7f 100644 --- a/Modules/ControlCenter/NetworkTab.qml +++ b/Modules/ControlCenter/NetworkTab.qml @@ -123,7 +123,7 @@ Item { radius: 6 anchors.verticalCenter: parent.verticalCenter z: 10 - opacity: networkTab.changingNetworkPreference ? 0.6 : 1 + opacity: NetworkService.changingPreference ? 0.6 : 1 visible: NetworkService.networkStatus !== "ethernet" && NetworkService.wifiAvailable && NetworkService.wifiEnabled Row { @@ -133,17 +133,17 @@ Item { DankIcon { id: ethernetPreferenceIcon - name: networkTab.changingNetworkPreference ? "sync" : "" + name: NetworkService.changingPreference ? "sync" : "" size: Theme.fontSizeSmall color: networkTab.networkStatus === "ethernet" ? Theme.background : Theme.primary - visible: networkTab.changingNetworkPreference + visible: NetworkService.changingPreference anchors.verticalCenter: parent.verticalCenter - rotation: networkTab.changingNetworkPreference ? ethernetPreferenceIcon.rotation : 0 + rotation: NetworkService.changingPreference ? ethernetPreferenceIcon.rotation : 0 RotationAnimation { target: ethernetPreferenceIcon property: "rotation" - running: networkTab.changingNetworkPreference + running: NetworkService.changingPreference from: 0 to: 360 duration: 1000 @@ -153,7 +153,7 @@ Item { } Text { - text: networkTab.changingNetworkPreference ? "Switching..." : (networkTab.networkStatus === "ethernet" ? "" : "Prefer over WiFi") + text: NetworkService.changingPreference ? "Switching..." : (networkTab.networkStatus === "ethernet" ? "" : "Prefer over WiFi") font.pixelSize: Theme.fontSizeSmall color: networkTab.networkStatus === "ethernet" ? Theme.background : Theme.primary anchors.verticalCenter: parent.verticalCenter @@ -167,7 +167,7 @@ Item { hoverEnabled: true cursorShape: Qt.PointingHandCursor propagateComposedEvents: false - enabled: !networkTab.changingNetworkPreference + enabled: !NetworkService.changingPreference onClicked: { console.log("*** ETHERNET PREFERENCE BUTTON CLICKED ***"); if (networkTab.networkStatus !== "ethernet") { @@ -356,7 +356,7 @@ Item { anchors.right: parent.right anchors.rightMargin: Theme.spacingL + 48 + Theme.spacingM anchors.verticalCenter: parent.verticalCenter - opacity: networkTab.changingNetworkPreference ? 0.6 : 1 + opacity: NetworkService.changingPreference ? 0.6 : 1 visible: NetworkService.networkStatus !== "wifi" && NetworkService.ethernetConnected && NetworkService.wifiEnabled Row { @@ -366,17 +366,17 @@ Item { DankIcon { id: wifiPreferenceIcon - name: networkTab.changingNetworkPreference ? "sync" : "" + name: NetworkService.changingPreference ? "sync" : "" size: Theme.fontSizeSmall color: networkTab.networkStatus === "wifi" ? Theme.background : Theme.primary - visible: networkTab.changingNetworkPreference + visible: NetworkService.changingPreference anchors.verticalCenter: parent.verticalCenter - rotation: networkTab.changingNetworkPreference ? wifiPreferenceIcon.rotation : 0 + rotation: NetworkService.changingPreference ? wifiPreferenceIcon.rotation : 0 RotationAnimation { target: wifiPreferenceIcon property: "rotation" - running: networkTab.changingNetworkPreference + running: NetworkService.changingPreference from: 0 to: 360 duration: 1000 @@ -386,7 +386,7 @@ Item { } Text { - text: NetworkService.changingNetworkPreference ? "Switching..." : "Prefer over Ethernet" + text: NetworkService.changingPreference ? "Switching..." : "Prefer over Ethernet" font.pixelSize: Theme.fontSizeSmall color: NetworkService.networkStatus === "wifi" ? Theme.background : Theme.primary anchors.verticalCenter: parent.verticalCenter @@ -400,7 +400,7 @@ Item { hoverEnabled: true cursorShape: Qt.PointingHandCursor propagateComposedEvents: false - enabled: !networkTab.changingNetworkPreference + enabled: !NetworkService.changingPreference onClicked: { console.log("Force WiFi preference clicked"); if (NetworkService.networkStatus !== "wifi") diff --git a/Modules/NotificationCenter.qml b/Modules/NotificationCenter.qml index 2e86ff50..f592f972 100644 --- a/Modules/NotificationCenter.qml +++ b/Modules/NotificationCenter.qml @@ -373,78 +373,41 @@ PanelWindow { border.width: 1 clip: true + readonly property bool hasNotificationImage: modelData.latestNotification.image && modelData.latestNotification.image !== "" + IconImage { anchors.fill: parent - anchors.margins: 6 + anchors.margins: 2 source: { - // Don't try to load icons for screenshots - let fallback handle them - const isScreenshot = modelData.latestNotification.isScreenshot; - - if (isScreenshot) { - return ""; + // Priority 1: Use notification image if available + if (parent.hasNotificationImage) { + return modelData.latestNotification.cleanImage; + } + + // Priority 2: Use appIcon - handle URLs directly, use iconPath for icon names + if (modelData.latestNotification.appIcon) { + const appIcon = modelData.latestNotification.appIcon; + if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://")) { + return appIcon; + } + return Quickshell.iconPath(appIcon, ""); } - if (modelData.latestNotification.appIcon && modelData.latestNotification.appIcon !== "") - return Quickshell.iconPath(modelData.latestNotification.appIcon, ""); - return ""; } visible: status === Image.Ready - onStatusChanged: { - if (status === Image.Error || status === Image.Null || source === "") - fallbackIcon.visible = true; - else if (status === Image.Ready) - fallbackIcon.visible = false; - } } - Item { - id: fallbackIcon + Text { anchors.centerIn: parent - visible: true - width: parent.width - height: parent.height - - readonly property bool isScreenshot: modelData.latestNotification.isScreenshot - readonly property bool hasNotificationImage: modelData.latestNotification.image && modelData.latestNotification.image !== "" - - // Priority 1: Notification image using Quickshell IconImage - Rectangle { - anchors.fill: parent - anchors.margins: 2 - radius: 20 - clip: true - visible: parent.hasNotificationImage && centerNotificationImage.status === Image.Ready - color: "transparent" - - IconImage { - id: centerNotificationImage - anchors.fill: parent - source: modelData.latestNotification.image || "" - } - } - - // Priority 2: Material Symbols icon for screenshots without notification images - DankIcon { - anchors.centerIn: parent - name: "screenshot_monitor" - size: 20 - color: Theme.primaryText - visible: parent.isScreenshot && !parent.hasNotificationImage - } - - // Priority 3: Fallback to first letter for other notifications - Text { - anchors.centerIn: parent - visible: !parent.hasNotificationImage && !parent.isScreenshot - text: { - const appName = modelData.appName || "?"; - return appName.charAt(0).toUpperCase(); - } - font.pixelSize: 20 - font.weight: Font.Bold - color: Theme.primaryText + visible: !parent.hasNotificationImage && (!modelData.latestNotification.appIcon || modelData.latestNotification.appIcon === "") + text: { + const appName = modelData.appName || "?"; + return appName.charAt(0).toUpperCase(); } + font.pixelSize: 20 + font.weight: Font.Bold + color: Theme.primaryText } } @@ -828,35 +791,32 @@ PanelWindow { readonly property bool hasNotificationImage: modelData.image && modelData.image !== "" - // Priority 1: Notification image using Quickshell IconImage - Rectangle { + IconImage { anchors.fill: parent anchors.margins: 1 - radius: 14 - clip: true - visible: parent.hasNotificationImage && centerIndividualNotificationImage.status === Image.Ready - color: "transparent" - - IconImage { - id: centerIndividualNotificationImage - anchors.fill: parent - source: modelData.image || "" + source: { + // Priority 1: Use notification image if available + if (parent.hasNotificationImage) { + return modelData.cleanImage; + } + + // Priority 2: Use appIcon - handle URLs directly, use iconPath for icon names + if (modelData.appIcon) { + const appIcon = modelData.appIcon; + if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://")) { + return appIcon; + } + return Quickshell.iconPath(appIcon, ""); + } + + return ""; } + visible: status === Image.Ready } - // Priority 2: Material Symbols icon for screenshots without notification images - DankIcon { - anchors.centerIn: parent - name: "screenshot_monitor" - size: 12 - color: Theme.primaryText - visible: modelData.isScreenshot && !parent.hasNotificationImage - } - - // Priority 3: Fallback text Text { anchors.centerIn: parent - visible: !parent.hasNotificationImage && !modelData.isScreenshot + visible: !parent.hasNotificationImage && (!modelData.appIcon || modelData.appIcon === "") text: { const appName = modelData.appName || "?"; return appName.charAt(0).toUpperCase(); diff --git a/Modules/NotificationPopup.qml b/Modules/NotificationPopup.qml index 57d537e3..3ac7d87e 100644 --- a/Modules/NotificationPopup.qml +++ b/Modules/NotificationPopup.qml @@ -1,6 +1,5 @@ import QtQuick import QtQuick.Controls -import QtQuick.Effects import Quickshell import Quickshell.Wayland import Quickshell.Widgets @@ -9,19 +8,8 @@ import qs.Services import qs.Widgets PanelWindow { - id: notificationPopup + id: root - // Expose key child objects for testing - // Expose the currently visible quickReplyField for testing - property TextField quickReplyField: null - // Expose the currently visible iconContainer for testing - property Item iconContainer: null - // Expose the currently visible expandedContent for testing - property Column expandedContent: null - // Expose the currently visible hoverArea for testing - property MouseArea hoverArea: null - - objectName: "notificationPopup" visible: NotificationService.groupedPopups.length > 0 WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.exclusiveZone: -1 @@ -55,43 +43,15 @@ PanelWindow { delegate: Rectangle { required property var modelData - // Context detection for popup - readonly property bool isPopupContext: true - readonly property bool expanded: NotificationService.expandedGroups[modelData.key] || false width: parent.width - height: { - let calculatedHeight; - if (expanded) { - // Calculate expanded height properly: header (48) + spacing + notifications - let headerHeight = 48 + Theme.spacingM; - let maxNotificationsInPopup = Math.min(modelData.notifications.length, 5); - let notificationHeight = maxNotificationsInPopup * (60 + Theme.spacingS); - calculatedHeight = headerHeight + notificationHeight + Theme.spacingL * 2; - } else { - // Collapsed height: header (72) + quick reply if present - calculatedHeight = 72 + Theme.spacingS * 2; - if (modelData.latestNotification.notification.hasInlineReply) - calculatedHeight += 36 + Theme.spacingS; - - calculatedHeight += Theme.spacingL * 2; - } - // Add extra height for single notifications in popup context - if (isPopupContext && modelData.count === 1) - calculatedHeight += 12; - - return calculatedHeight; - } + height: content.height + Theme.spacingL * 2 radius: Theme.cornerRadiusLarge color: Theme.popupBackground() border.color: modelData.latestNotification.urgency === 2 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.width: modelData.latestNotification.urgency === 2 ? 2 : 1 - // Stabilize layout during content changes clip: true - opacity: notificationPopup.visible ? 1 : 0 - scale: notificationPopup.visible ? 1 : 0.98 - // Priority indicator for urgent notifications Rectangle { width: 4 height: parent.height - 16 @@ -103,806 +63,277 @@ PanelWindow { visible: modelData.latestNotification.urgency === 2 } - // Collapsed view - shows app header and latest notification - Column { - id: collapsedContent - + Row { + id: content anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.leftMargin: Theme.spacingL - anchors.rightMargin: Theme.spacingL - anchors.topMargin: 14 // Reduced from Theme.spacingL (16px) by 10% - anchors.bottomMargin: 14 // Reduced from Theme.spacingL (16px) by 10% - spacing: Theme.spacingS - visible: !expanded + anchors.margins: Theme.spacingL + spacing: Theme.spacingM + height: Math.max(48, textContent.height) - // App header with group info - Item { - width: parent.width - height: 72 // Increased height for better text spacing + Rectangle { + width: 48 + height: 48 + radius: 24 + color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) + border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) + border.width: 1 + anchors.verticalCenter: parent.verticalCenter - // Round app icon with proper API usage - Item { - id: iconContainer + readonly property bool hasNotificationImage: modelData.latestNotification.image && modelData.latestNotification.image !== "" + readonly property bool appIconIsImage: modelData.latestNotification.appIcon && + (modelData.latestNotification.appIcon.startsWith("file://") || + modelData.latestNotification.appIcon.startsWith("http://") || + modelData.latestNotification.appIcon.startsWith("https://")) - Component.onCompleted: { - // Expose this iconContainer to the root for testing if visible - notificationPopup.iconContainer = iconContainer; - } - width: 48 - height: 48 - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - - Rectangle { - width: 48 - height: 48 - radius: 24 - color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) - border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) - border.width: 1 - clip: true - - IconImage { - anchors.fill: parent - anchors.margins: 6 - source: { - // Don't try to load icons for screenshots - let fallback handle them - if (modelData.latestNotification.isScreenshot) { - return ""; - } - - if (modelData.latestNotification.appIcon && modelData.latestNotification.appIcon !== "") - return Quickshell.iconPath(modelData.latestNotification.appIcon, ""); - - return ""; - } - visible: status === Image.Ready - onStatusChanged: { - if (status === Image.Error || status === Image.Null || source === "") - fallbackIcon.visible = true; - else if (status === Image.Ready) - fallbackIcon.visible = false; - } + IconImage { + anchors.fill: parent + anchors.margins: 2 + source: { + // Priority 1: Use notification image if available + if (parent.hasNotificationImage) { + return modelData.latestNotification.cleanImage; } - - // Fallback icon - show by default, hide when real icon loads - Item { - id: fallbackIcon - anchors.centerIn: parent - visible: true // Start visible, hide when real icon loads - width: parent.width - height: parent.height - - readonly property bool isScreenshot: modelData.latestNotification.isScreenshot - readonly property bool hasNotificationImage: modelData.latestNotification.image && modelData.latestNotification.image !== "" - - // Priority 1: Notification image using Quickshell IconImage (handles qs://image-X URIs) - Rectangle { - anchors.fill: parent - anchors.margins: 2 - radius: 20 - clip: true - visible: parent.hasNotificationImage && notificationImage.status === Image.Ready - color: "transparent" - - IconImage { - id: notificationImage - anchors.fill: parent - source: modelData.latestNotification.image || "" - } - } - - // Priority 2: Material Symbols icon for screenshots (when no image available) - DankIcon { - anchors.centerIn: parent - name: "screenshot_monitor" - size: 24 - color: Theme.primaryText - visible: parent.isScreenshot && !parent.hasNotificationImage - } - - // Priority 3: Fallback to first letter for other notifications - Text { - anchors.centerIn: parent - visible: !parent.hasNotificationImage && !parent.isScreenshot - text: { - const appName = modelData.appName || "?"; - return appName.charAt(0).toUpperCase(); - } - font.pixelSize: 20 - font.weight: Font.Bold - color: Theme.primaryText + + // Priority 2: Use appIcon - handle URLs directly, use iconPath for icon names + if (modelData.latestNotification.appIcon) { + const appIcon = modelData.latestNotification.appIcon; + if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://")) { + return appIcon; } + return Quickshell.iconPath(appIcon, ""); } - + + return ""; } - - // Count badge for multiple notifications - smaller circle - Rectangle { - width: 18 - height: 18 - radius: 9 - color: Theme.primary - anchors.top: parent.top - anchors.right: parent.right - anchors.topMargin: -2 - anchors.rightMargin: -2 - visible: modelData.count > 1 - - Text { - anchors.centerIn: parent - text: modelData.count > 99 ? "99+" : modelData.count.toString() - color: Theme.primaryText - font.pixelSize: 9 - font.weight: Font.Bold - } - - } - + visible: status === Image.Ready } - // App info and latest notification content - Column { - anchors.left: iconContainer.right - anchors.leftMargin: Theme.spacingM - anchors.right: controlsContainer.left - anchors.rightMargin: 8 // Reduced to align text with close button + Text { + anchors.centerIn: parent + visible: !parent.hasNotificationImage && (!modelData.latestNotification.appIcon || modelData.latestNotification.appIcon === "") + text: { + const appName = modelData.appName || "?"; + return appName.charAt(0).toUpperCase(); + } + font.pixelSize: 20 + font.weight: Font.Bold + color: Theme.primaryText + } + + Rectangle { + width: 18 + height: 18 + radius: 9 + color: Theme.primary anchors.top: parent.top - anchors.topMargin: Theme.spacingS - spacing: 7 // Reduced from Theme.spacingS (8px) by 2px - - // App name and timestamp on same line - Text { - width: parent.width - text: { - if (modelData.latestNotification.timeStr.length > 0) - return modelData.appName + " • " + modelData.latestNotification.timeStr; - else - return modelData.appName; - } - color: Theme.surfaceVariantText - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.Medium - elide: Text.ElideRight - maximumLineCount: 1 - } - - // Latest notification title (emphasized) - Text { - text: modelData.latestNotification.summary - color: Theme.surfaceText - font.pixelSize: Theme.fontSizeMedium + 1 // Slightly larger for emphasis - font.weight: Font.Medium - width: parent.width - elide: Text.ElideRight - maximumLineCount: 1 - visible: text.length > 0 - } - - // Latest notification body (smaller, secondary) - Text { - text: modelData.latestNotification.body - color: Theme.surfaceVariantText - font.pixelSize: Theme.fontSizeSmall - width: parent.width - elide: Text.ElideRight - maximumLineCount: modelData.count > 1 ? 1 : 2 // More space for single notifications - wrapMode: Text.WordWrap - visible: text.length > 0 - } - - } - - // Expand/dismiss controls - aligned with app name and timestamp row - Item { - id: controlsContainer - - width: 72 - height: 32 anchors.right: parent.right - anchors.top: parent.top - anchors.topMargin: 8 - - Rectangle { - width: 32 - height: 32 - radius: 16 - anchors.left: parent.left - color: expandArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent" - visible: modelData.count > 1 - - DankIcon { - anchors.centerIn: parent - name: "expand_more" - size: 18 - color: Theme.surfaceText - rotation: expanded ? 180 : 0 - - Behavior on rotation { - NumberAnimation { - duration: Theme.shortDuration - easing.type: Theme.standardEasing - } - - } - - } - - MouseArea { - // ...existing code... - id: expandArea - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - console.log("Expand clicked - pausing timer"); - dismissTimer.stop(); - NotificationService.toggleGroupExpansion(modelData.key); - } - } + anchors.topMargin: -2 + anchors.rightMargin: -2 + visible: modelData.count > 1 + Text { + anchors.centerIn: parent + text: modelData.count > 99 ? "99+" : modelData.count.toString() + color: Theme.primaryText + font.pixelSize: 9 + font.weight: Font.Bold } - - Rectangle { - width: 32 - height: 32 - radius: 16 - anchors.right: parent.right - color: dismissArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent" - - DankIcon { - anchors.centerIn: parent - name: "close" - size: 16 - color: Theme.surfaceText - } - - MouseArea { - id: dismissArea - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: NotificationService.dismissGroup(modelData.key) - } - - } - } - } - // Quick reply for conversations (only if latest notification supports it) - Row { - width: parent.width - spacing: Theme.spacingS - visible: modelData.latestNotification.notification.hasInlineReply && !expanded + Column { + id: textContent + anchors.verticalCenter: parent.verticalCenter + spacing: 4 + width: parent.width - 48 - Theme.spacingM - controls.width - Theme.spacingS - Rectangle { - width: parent.width - 60 - height: 36 - radius: 18 - color: Theme.surfaceContainer - border.color: quickReplyField.activeFocus ? Theme.primary : Theme.outline - border.width: 1 + Text { + width: parent.width + text: { + if (modelData.latestNotification.timeStr.length > 0) + return modelData.appName + " • " + modelData.latestNotification.timeStr; + else + return modelData.appName; + } + color: Theme.surfaceVariantText + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + elide: Text.ElideRight + maximumLineCount: 1 + } - TextField { - id: quickReplyField + Text { + text: modelData.latestNotification.summary + color: Theme.surfaceText + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + width: parent.width + elide: Text.ElideRight + maximumLineCount: 1 + visible: text.length > 0 + } - anchors.fill: parent - anchors.margins: Theme.spacingS - placeholderText: modelData.latestNotification.notification.inlineReplyPlaceholder || "Quick reply..." - color: Theme.surfaceText - font.pixelSize: Theme.fontSizeSmall - onAccepted: { - if (text.length > 0) { - modelData.latestNotification.notification.sendInlineReply(text); - text = ""; + Text { + text: modelData.latestNotification.body + color: Theme.surfaceVariantText + font.pixelSize: Theme.fontSizeSmall + width: parent.width + elide: Text.ElideRight + maximumLineCount: modelData.count > 1 ? 1 : 2 + wrapMode: Text.WordWrap + visible: text.length > 0 + } + + Row { + width: parent.width + spacing: Theme.spacingS + visible: modelData.latestNotification.notification.hasInlineReply + + Rectangle { + width: parent.width - 60 + height: 36 + radius: 18 + color: Theme.surfaceContainer + border.color: quickReplyField.activeFocus ? Theme.primary : Theme.outline + border.width: 1 + + TextField { + id: quickReplyField + anchors.fill: parent + anchors.margins: Theme.spacingS + placeholderText: modelData.latestNotification.notification.inlineReplyPlaceholder || "Quick reply..." + color: Theme.surfaceText + font.pixelSize: Theme.fontSizeSmall + onAccepted: { + if (text.length > 0) { + modelData.latestNotification.notification.sendInlineReply(text); + text = ""; + } + } + background: Item {} + } + } + + Rectangle { + width: 52 + height: 36 + radius: 18 + color: quickReplyField.text.length > 0 ? Theme.primary : Theme.surfaceContainer + border.color: quickReplyField.text.length > 0 ? "transparent" : Theme.outline + border.width: quickReplyField.text.length > 0 ? 0 : 1 + + DankIcon { + anchors.centerIn: parent + name: "send" + size: 16 + color: quickReplyField.text.length > 0 ? Theme.primaryText : Theme.surfaceVariantText + } + + MouseArea { + anchors.fill: parent + enabled: quickReplyField.text.length > 0 + cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: { + modelData.latestNotification.notification.sendInlineReply(quickReplyField.text); + quickReplyField.text = ""; } } - background: Item { + Behavior on color { + ColorAnimation { + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } } - } - } + } + + Row { + id: controls + anchors.verticalCenter: parent.verticalCenter + spacing: 8 Rectangle { - width: 52 - height: 36 - radius: 18 - color: quickReplyField.text.length > 0 ? Theme.primary : Theme.surfaceContainer - border.color: quickReplyField.text.length > 0 ? "transparent" : Theme.outline - border.width: quickReplyField.text.length > 0 ? 0 : 1 + width: 32 + height: 32 + radius: 16 + color: expandArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent" + visible: modelData.count > 1 DankIcon { anchors.centerIn: parent - name: "send" - size: 16 - color: quickReplyField.text.length > 0 ? Theme.primaryText : Theme.surfaceVariantText + name: "expand_more" + size: 18 + color: Theme.surfaceText } MouseArea { + id: expandArea anchors.fill: parent - enabled: quickReplyField.text.length > 0 - cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor - onClicked: { - modelData.latestNotification.notification.sendInlineReply(quickReplyField.text); - quickReplyField.text = ""; - } - } - - Behavior on color { - ColorAnimation { - duration: Theme.shortDuration - easing.type: Theme.standardEasing - } - - } - - } - - } - - } - - // Expanded view - shows all notifications stacked - Column { - id: expandedContent - - Component.onCompleted: { - // Expose this expandedContent to the root for testing if visible - notificationPopup.expandedContent = expandedContent; - } - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: Theme.spacingL - anchors.rightMargin: Theme.spacingL - anchors.topMargin: 14 // Reduced from Theme.spacingL (16px) by 10% - anchors.bottomMargin: 14 // Reduced from Theme.spacingL (16px) by 10% - spacing: 9 // Reduced from Theme.spacingM (12px) by 1/4 - visible: expanded - - // 1st tier controls with app name - optimized spacing - Item { - width: parent.width - height: 40 - - // App name and count badge - left side - Row { - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingS - - Text { - text: modelData.appName - color: Theme.surfaceText - font.pixelSize: Theme.fontSizeLarge - font.weight: Font.Bold - anchors.verticalCenter: parent.verticalCenter - } - - // Message count badge when expanded - Rectangle { - width: 20 - height: 20 - radius: 10 - color: Theme.primary - visible: modelData.count > 1 - anchors.verticalCenter: parent.verticalCenter - - Text { - anchors.centerIn: parent - text: modelData.count.toString() - color: Theme.primaryText - font.pixelSize: 10 - font.weight: Font.Bold - } + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: NotificationService.toggleGroupExpansion(modelData.key) } } - // Controls container - fixed position on right - Item { - width: 72 + Rectangle { + width: 32 height: 32 - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - Rectangle { - width: 32 - height: 32 - radius: 16 - anchors.left: parent.left - color: collapseAreaTop.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent" - - DankIcon { - anchors.centerIn: parent - name: "expand_less" - size: 18 - color: Theme.surfaceText - } - - MouseArea { - id: collapseAreaTop - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - console.log("Expand clicked - pausing timer"); - dismissTimer.stop(); - NotificationService.toggleGroupExpansion(modelData.key); - } - } + radius: 16 + color: dismissArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent" + DankIcon { + anchors.centerIn: parent + name: "close" + size: 16 + color: Theme.surfaceText } - Rectangle { - width: 32 - height: 32 - radius: 16 - anchors.right: parent.right - color: dismissAllAreaTop.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent" - - DankIcon { - anchors.centerIn: parent - name: "close" - size: 16 - color: Theme.surfaceText - } - - MouseArea { - id: dismissAllAreaTop - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: NotificationService.dismissGroup(modelData.key) - } - + MouseArea { + id: dismissArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: NotificationService.dismissGroup(modelData.key) } - } } - - - // Stacked individual notifications with smooth transitions - Column { - width: parent.width - spacing: 5 // Reduced from Theme.spacingS (8px) by 1/3 - - Repeater { - model: modelData.notifications.slice(0, 5) // Show max 5 in popup - - delegate: Rectangle { - required property var modelData - - width: parent.width - height: notifContent.height + Theme.spacingM * 2 - radius: Theme.cornerRadius - color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5) - border.color: modelData.urgency === 2 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent" - border.width: modelData.urgency === 2 ? 1 : 0 - // Stabilize layout during dismiss operations - clip: true - - Item { - id: notifContent - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: Theme.spacingM - height: Math.max(32, contentColumn.height) - - // Small round notification icon/avatar - fixed position on left - Rectangle { - width: 32 - height: 32 - radius: 16 - anchors.left: parent.left - anchors.top: parent.top - color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) - border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) - border.width: 1 - clip: true - - readonly property bool hasNotificationImage: modelData.image && modelData.image !== "" - - // Priority 1: Notification image using Quickshell IconImage - Rectangle { - anchors.fill: parent - anchors.margins: 1 - radius: 14 - clip: true - visible: parent.hasNotificationImage && individualNotificationImage.status === Image.Ready - color: "transparent" - - IconImage { - id: individualNotificationImage - anchors.fill: parent - source: modelData.image || "" - } - } - - // Priority 2: App icon for non-screenshots without notification images - IconImage { - anchors.fill: parent - anchors.margins: 3 - source: { - if (parent.hasNotificationImage || modelData.isScreenshot) { - return ""; - } - return modelData.appIcon ? Quickshell.iconPath(modelData.appIcon, "") : ""; - } - visible: status === Image.Ready - } - - // Priority 3: Material Symbols icon for screenshots without notification images - DankIcon { - anchors.centerIn: parent - name: "screenshot_monitor" - size: 12 - color: Theme.primaryText - visible: modelData.isScreenshot && !parent.hasNotificationImage - } - - // Priority 4: Fallback text - Text { - anchors.centerIn: parent - visible: !parent.hasNotificationImage && !modelData.isScreenshot && (!modelData.appIcon || modelData.appIcon === "") - text: { - const appName = modelData.appName || "?"; - return appName.charAt(0).toUpperCase(); - } - font.pixelSize: 12 - font.weight: Font.Bold - color: Theme.primaryText - } - - } - - // Individual controls - expand and dismiss buttons - Row { - width: 50 - height: 24 - anchors.right: parent.right - anchors.top: parent.top - anchors.topMargin: -4 // Move up into title area - spacing: 2 - - // Expand/collapse button for 2nd tier - Rectangle { - width: 24 - height: 24 - radius: 12 - color: individualExpandArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent" - visible: modelData.body && modelData.body.length > 50 // Only show if body text is long enough - - property bool isExpanded: NotificationService.expandedMessages[modelData.notification.id] || false - - DankIcon { - anchors.centerIn: parent - name: parent.isExpanded ? "expand_less" : "expand_more" - size: 12 - color: Theme.surfaceVariantText - } - - MouseArea { - id: individualExpandArea - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: NotificationService.toggleMessageExpansion(modelData.notification.id) - } - } - - // Individual dismiss button - Rectangle { - width: 24 - height: 24 - radius: 12 - color: individualDismissArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent" - - DankIcon { - anchors.centerIn: parent - name: "close" - size: 12 - color: Theme.surfaceVariantText - } - - MouseArea { - id: individualDismissArea - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: NotificationService.dismissNotification(modelData) - } - } - } - - // Notification content - fills space between icon and dismiss button - Column { - id: contentColumn - - anchors.left: parent.left - anchors.leftMargin: 44 - anchors.right: parent.right - anchors.rightMargin: 24 // Align text with close button - anchors.top: parent.top - spacing: 2 // Reduced from Theme.spacingXS (4px) by 2px - - property bool isMessageExpanded: NotificationService.expandedMessages[modelData.notification.id] || false - - // Title • timestamp format - Text { - text: { - const summary = modelData.summary || ""; - const timeStr = modelData.timeStr || ""; - if (summary && timeStr) { - return summary + " • " + timeStr; - } else if (summary) { - return summary; - } else { - return "Message • " + timeStr; - } - } - color: Theme.surfaceText - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.Medium - width: parent.width - elide: Text.ElideRight - maximumLineCount: 1 - } - - // Body text with expandable behavior - Text { - text: modelData.body - color: Theme.surfaceVariantText - font.pixelSize: Theme.fontSizeSmall - width: parent.width - wrapMode: Text.WordWrap - maximumLineCount: parent.isMessageExpanded ? -1 : 2 // Unlimited when expanded, 2 when collapsed - elide: parent.isMessageExpanded ? Text.ElideNone : Text.ElideRight - visible: text.length > 0 - } - - // Individual notification inline reply - Row { - width: parent.width - spacing: Theme.spacingS - visible: modelData.notification.hasInlineReply - - Rectangle { - width: parent.width - 50 - height: 28 - radius: 14 - color: Theme.surface - border.color: replyField.activeFocus ? Theme.primary : Theme.outline - border.width: 1 - - TextField { - id: replyField - - anchors.fill: parent - anchors.margins: Theme.spacingXS - placeholderText: modelData.notification.inlineReplyPlaceholder || "Reply..." - color: Theme.surfaceText - font.pixelSize: 11 - onAccepted: { - if (text.length > 0) { - modelData.notification.sendInlineReply(text); - text = ""; - } - } - - background: Item { - } - - } - - } - - Rectangle { - width: 42 - height: 28 - radius: 14 - color: replyField.text.length > 0 ? Theme.primary : Theme.surfaceContainer - - DankIcon { - anchors.centerIn: parent - name: "send" - size: 12 - color: replyField.text.length > 0 ? Theme.primaryText : Theme.surfaceVariantText - } - - MouseArea { - anchors.fill: parent - enabled: replyField.text.length > 0 - cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor - onClicked: { - modelData.notification.sendInlineReply(replyField.text); - replyField.text = ""; - } - } - - } - - } - - } - - } - - } - - } - - } - } - // Hover to pause auto-dismiss - MUST be properly configured MouseArea { - id: hoverArea - - Component.onCompleted: { - // Expose this hoverArea to the root for testing if visible - notificationPopup.hoverArea = hoverArea; - } anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.NoButton - z: 10 // Higher z-order to ensure hover detection propagateComposedEvents: true - onEntered: { - console.log("Notification hover entered - pausing timer"); - dismissTimer.stop(); - } + onEntered: dismissTimer.stop() onExited: { - console.log("Notification hover exited - resuming timer"); - if (modelData.latestNotification.popup && !expanded) + if (modelData.latestNotification.popup) dismissTimer.restart(); - } } - // Auto-dismiss timer - properly pauses on hover Timer { id: dismissTimer - - running: modelData.latestNotification.popup && !expanded + running: modelData.latestNotification.popup interval: modelData.latestNotification.notification.expireTimeout > 0 ? modelData.latestNotification.notification.expireTimeout * 1000 : 5000 onTriggered: { - console.log("Timer triggered - hover state:", hoverArea.containsMouse, "expanded:", expanded); - if (!hoverArea.containsMouse && !expanded) { - console.log("Dismissing notification"); + if (!parent.children[parent.children.length - 2].containsMouse) { modelData.latestNotification.popup = false; - } else { - console.log("Conditions not met - not dismissing"); } } } - // Smooth popup animations transform: Translate { - x: notificationPopup.visible ? 0 : 400 - + x: root.visible ? 0 : 400 Behavior on x { NumberAnimation { duration: 350 easing.type: Easing.OutCubic } - } - } Behavior on opacity { @@ -910,47 +341,15 @@ PanelWindow { duration: 300 easing.type: Easing.OutCubic } - } - - Behavior on scale { - NumberAnimation { - duration: 350 - easing.type: Easing.OutCubic - } - - } - - Behavior on height { - enabled: !isPopupContext // Disable automatic height animation in popup to prevent glitches - - SequentialAnimation { - PauseAnimation { - duration: 25 - } - - NumberAnimation { - duration: Theme.mediumDuration - easing.type: Theme.emphasizedEasing - } - - } - - } - } - } - } - // Smooth height animation Behavior on implicitHeight { NumberAnimation { duration: Theme.mediumDuration easing.type: Theme.emphasizedEasing } - } - -} +} \ No newline at end of file diff --git a/Modules/ProcessListDropdown.qml b/Modules/ProcessListDropdown.qml index 910f96b2..bacb6376 100644 --- a/Modules/ProcessListDropdown.qml +++ b/Modules/ProcessListDropdown.qml @@ -79,7 +79,7 @@ PanelWindow { Scale { id: scaleTransform - origin.x: parent.width * 0.85 // Scale from top-right + origin.x: dropdownContent.width * 0.85 // Scale from top-right origin.y: 0 xScale: processListDropdown.isVisible ? 1 : 0.95 yScale: processListDropdown.isVisible ? 1 : 0.8 diff --git a/Modules/SpotlightLauncher.qml b/Modules/SpotlightLauncher.qml index a5aed4bc..b31dab6e 100644 --- a/Modules/SpotlightLauncher.qml +++ b/Modules/SpotlightLauncher.qml @@ -239,6 +239,7 @@ PanelWindow { } Connections { + target: AppSearchService function onReadyChanged() { if (AppSearchService.ready) { var allCategories = AppSearchService.getAllCategories().filter((cat) => { @@ -254,11 +255,10 @@ PanelWindow { } } - - target: DesktopEntries } Connections { + target: AppSearchService function onApplicationsChanged() { console.log("SpotlightLauncher: DesktopEntries.applicationsChanged signal received"); // Update categories when applications change @@ -278,8 +278,6 @@ PanelWindow { console.log("SpotlightLauncher: AppSearchService not ready, skipping update"); } } - - target: DesktopEntries } // Dimmed overlay background diff --git a/Modules/TopBar/TopBar.qml b/Modules/TopBar/TopBar.qml index 074e8820..98bcc11b 100644 --- a/Modules/TopBar/TopBar.qml +++ b/Modules/TopBar/TopBar.qml @@ -134,7 +134,7 @@ PanelWindow { LauncherButton { anchors.verticalCenter: parent.verticalCenter - isActive: launcher.isVisible + isActive: appLauncher.isVisible onClicked: { appLauncher.toggle(); } diff --git a/Services/NotificationService.qml b/Services/NotificationService.qml index b98ece3b..9d5a2608 100644 --- a/Services/NotificationService.qml +++ b/Services/NotificationService.qml @@ -30,7 +30,14 @@ Singleton { inlineReplySupported: true onNotification: notif => { - console.log("New notification received:", notif.appName, "-", notif.summary); + console.log("=== RAW NOTIFICATION DATA ==="); + console.log("appName:", notif.appName); + console.log("summary:", notif.summary); + console.log("body:", notif.body); + console.log("appIcon:", notif.appIcon); + console.log("image:", notif.image); + console.log("urgency:", notif.urgency); + console.log("============================="); notif.tracked = true; const wrapper = notifComponent.createObject(root, { @@ -70,35 +77,32 @@ Singleton { readonly property string summary: notification.summary readonly property string body: notification.body readonly property string appIcon: notification.appIcon + readonly property string cleanAppIcon: { + if (!appIcon) return ""; + if (appIcon.startsWith("file://")) { + return appIcon.substring(7); + } + return appIcon; + } readonly property string appName: notification.appName readonly property string image: notification.image + readonly property string cleanImage: { + if (!image) return ""; + if (image.startsWith("file://")) { + return image.substring(7); + } + return image; + } readonly property int urgency: notification.urgency readonly property list actions: notification.actions // Enhanced properties for better handling readonly property bool hasImage: image && image.length > 0 readonly property bool hasAppIcon: appIcon && appIcon.length > 0 - readonly property bool isConversation: detectIsConversation() + readonly property bool isConversation: notification.hasInlineReply readonly property bool isMedia: detectIsMedia() readonly property bool isSystem: detectIsSystem() - readonly property bool isScreenshot: detectIsScreenshot() - function detectIsConversation() { - const appNameLower = appName.toLowerCase(); - const summaryLower = summary.toLowerCase(); - const bodyLower = body.toLowerCase(); - - return appNameLower.includes("discord") || - appNameLower.includes("vesktop") || - appNameLower.includes("vencord") || - appNameLower.includes("telegram") || - appNameLower.includes("whatsapp") || - appNameLower.includes("signal") || - appNameLower.includes("slack") || - appNameLower.includes("message") || - summaryLower.includes("message") || - bodyLower.includes("message"); - } function detectIsMedia() { const appNameLower = appName.toLowerCase(); @@ -123,24 +127,6 @@ Singleton { summaryLower.includes("system"); } - function detectIsScreenshot() { - const appNameLower = appName.toLowerCase(); - const summaryLower = summary.toLowerCase(); - const bodyLower = body.toLowerCase(); - const imageLower = image.toLowerCase(); - - // Detect niri screenshot notifications - return appNameLower.includes("niri") && - (summaryLower.includes("screenshot") || - bodyLower.includes("screenshot") || - imageLower.includes("screenshot") || - imageLower.includes("pictures/screenshots")) || - summaryLower.includes("screenshot") || - bodyLower.includes("screenshot taken") || - // Detect screenshot file paths being used as images/icons - imageLower.includes("/screenshots/") || - imageLower.includes("screenshot from"); - } readonly property Timer timer: Timer { running: wrapper.popup @@ -184,114 +170,32 @@ Singleton { wrapper.notification.dismiss(); } - function getNotificationIcon(wrapper) { - // Priority 1: Use notification image if available (Discord avatars, etc.) - // BUT NOT for screenshots - they use file paths which shouldn't be loaded as icons - if (wrapper.hasImage && !wrapper.isScreenshot) { - return wrapper.image; - } - - // Priority 2: Use app icon if available and not a screenshot - if (wrapper.hasAppIcon && !wrapper.isScreenshot) { - return Quickshell.iconPath(wrapper.appIcon, "image-missing"); - } - - // Priority 3: Generate fallback icon based on type - return getFallbackIcon(wrapper); - } - - function getFallbackIcon(wrapper) { - if (wrapper.isScreenshot) { - return Quickshell.iconPath("screenshot_monitor"); - } else if (wrapper.isConversation) { - return Quickshell.iconPath("chat-symbolic"); - } else if (wrapper.isMedia) { - return Quickshell.iconPath("audio-x-generic-symbolic"); - } else if (wrapper.isSystem) { - return Quickshell.iconPath("preferences-system-symbolic"); - } - return Quickshell.iconPath("application-x-executable-symbolic"); - } - - function getAppIconPath(wrapper) { - if (wrapper.hasAppIcon && !wrapper.isScreenshot) { - return Quickshell.iconPath(wrapper.appIcon); - } - return getFallbackIcon(wrapper); - } // Android 16-style notification grouping functions function getGroupKey(wrapper) { const appName = wrapper.appName.toLowerCase(); - // Enhanced grouping for conversation apps + // Conversation apps with inline reply if (wrapper.isConversation) { const summary = wrapper.summary.toLowerCase(); - const body = wrapper.body.toLowerCase(); - // Discord: Group by channel or conversation - if (appName.includes("discord") || appName.includes("vesktop")) { - // Channel notifications: "#general", "#announcements" - if (summary.includes("#")) { - const channelMatch = summary.match(/#[\w-]+/); - if (channelMatch) { - return `${appName}:${channelMatch[0]}`; - } + // Group by conversation/channel name from summary + if (summary.includes("#")) { + const channelMatch = summary.match(/#[\w-]+/); + if (channelMatch) { + return `${appName}:${channelMatch[0]}`; } - // Direct messages: group by sender - if (summary && !summary.includes("new message") && !summary.includes("notification")) { - return `${appName}:dm:${summary}`; - } - // Server messages or general - return `${appName}:messages`; } - // Telegram: Group by chat/channel - if (appName.includes("telegram")) { - if (summary && !summary.includes("new message")) { - return `${appName}:${summary}`; - } - return `${appName}:messages`; - } - - // Signal: Group by conversation - if (appName.includes("signal")) { - if (summary && !summary.includes("new message")) { - return `${appName}:${summary}`; - } - return `${appName}:messages`; - } - - // WhatsApp: Group by contact/group - if (appName.includes("whatsapp")) { - if (summary && !summary.includes("new message")) { - return `${appName}:${summary}`; - } - return `${appName}:messages`; - } - - // Slack: Group by channel/DM - if (appName.includes("slack")) { - if (summary.includes("#")) { - const channelMatch = summary.match(/#[\w-]+/); - if (channelMatch) { - return `${appName}:${channelMatch[0]}`; - } - } - if (summary && !summary.includes("new message")) { - return `${appName}:dm:${summary}`; - } - return `${appName}:messages`; + // Group by sender/conversation name if meaningful + if (summary && !summary.includes("new message") && !summary.includes("notification")) { + return `${appName}:${summary}`; } // Default conversation grouping return `${appName}:conversation`; } - // Screenshots: Group all screenshots together - if (wrapper.isScreenshot) { - return "screenshots"; - } // Media: Replace previous media notification from same app if (wrapper.isMedia) { @@ -332,8 +236,7 @@ Singleton { hasInlineReply: false, isConversation: notif.isConversation, isMedia: notif.isMedia, - isSystem: notif.isSystem, - isScreenshot: notif.isScreenshot + isSystem: notif.isSystem }; } @@ -366,8 +269,7 @@ Singleton { hasInlineReply: false, isConversation: notif.isConversation, isMedia: notif.isMedia, - isSystem: notif.isSystem, - isScreenshot: notif.isScreenshot + isSystem: notif.isSystem }; } @@ -419,17 +321,10 @@ Singleton { } if (group.isConversation) { - // Extract conversation/channel name from group key const keyParts = group.key.split(":"); if (keyParts.length > 1) { const conversationKey = keyParts[keyParts.length - 1]; - if (conversationKey.startsWith("#")) { - return `${conversationKey}: ${group.count} messages`; - } - if (keyParts.includes("dm")) { - return `${conversationKey}: ${group.count} messages`; - } - if (conversationKey !== "messages" && conversationKey !== "conversation") { + if (conversationKey !== "conversation") { return `${conversationKey}: ${group.count} messages`; } } @@ -440,12 +335,6 @@ Singleton { return "Now playing"; } - if (group.isScreenshot) { - if (group.count === 1) { - return "Screenshot saved"; - } - return `${group.count} screenshots saved`; - } if (group.isSystem) { const keyParts = group.key.split(":"); @@ -481,9 +370,6 @@ Singleton { return group.latestNotification.body || "Media playback"; } - if (group.isScreenshot) { - return group.latestNotification.body || "Screenshot available in Pictures/Screenshots"; - } return `Latest: ${group.latestNotification.summary}`; }