mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-07 14:05:38 -05:00
bunch of cleanups
This commit is contained in:
@@ -221,6 +221,7 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
target: AppSearchService
|
||||||
function onApplicationsChanged() {
|
function onApplicationsChanged() {
|
||||||
console.log("AppLauncher: DesktopEntries.applicationsChanged signal received");
|
console.log("AppLauncher: DesktopEntries.applicationsChanged signal received");
|
||||||
// Update categories when applications change
|
// Update categories when applications change
|
||||||
@@ -231,8 +232,6 @@ PanelWindow {
|
|||||||
}));
|
}));
|
||||||
updateFilteredModel();
|
updateFilteredModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
target: DesktopEntries
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
|||||||
@@ -868,15 +868,6 @@ PanelWindow {
|
|||||||
else
|
else
|
||||||
console.warn("ClipboardHistory: Failed to load clipboard history");
|
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 {
|
stdout: SplitParser {
|
||||||
splitMarker: "\n"
|
splitMarker: "\n"
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ Item {
|
|||||||
radius: 6
|
radius: 6
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
z: 10
|
z: 10
|
||||||
opacity: networkTab.changingNetworkPreference ? 0.6 : 1
|
opacity: NetworkService.changingPreference ? 0.6 : 1
|
||||||
visible: NetworkService.networkStatus !== "ethernet" && NetworkService.wifiAvailable && NetworkService.wifiEnabled
|
visible: NetworkService.networkStatus !== "ethernet" && NetworkService.wifiAvailable && NetworkService.wifiEnabled
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -133,17 +133,17 @@ Item {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: ethernetPreferenceIcon
|
id: ethernetPreferenceIcon
|
||||||
|
|
||||||
name: networkTab.changingNetworkPreference ? "sync" : ""
|
name: NetworkService.changingPreference ? "sync" : ""
|
||||||
size: Theme.fontSizeSmall
|
size: Theme.fontSizeSmall
|
||||||
color: networkTab.networkStatus === "ethernet" ? Theme.background : Theme.primary
|
color: networkTab.networkStatus === "ethernet" ? Theme.background : Theme.primary
|
||||||
visible: networkTab.changingNetworkPreference
|
visible: NetworkService.changingPreference
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
rotation: networkTab.changingNetworkPreference ? ethernetPreferenceIcon.rotation : 0
|
rotation: NetworkService.changingPreference ? ethernetPreferenceIcon.rotation : 0
|
||||||
|
|
||||||
RotationAnimation {
|
RotationAnimation {
|
||||||
target: ethernetPreferenceIcon
|
target: ethernetPreferenceIcon
|
||||||
property: "rotation"
|
property: "rotation"
|
||||||
running: networkTab.changingNetworkPreference
|
running: NetworkService.changingPreference
|
||||||
from: 0
|
from: 0
|
||||||
to: 360
|
to: 360
|
||||||
duration: 1000
|
duration: 1000
|
||||||
@@ -153,7 +153,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
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
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: networkTab.networkStatus === "ethernet" ? Theme.background : Theme.primary
|
color: networkTab.networkStatus === "ethernet" ? Theme.background : Theme.primary
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -167,7 +167,7 @@ Item {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
propagateComposedEvents: false
|
propagateComposedEvents: false
|
||||||
enabled: !networkTab.changingNetworkPreference
|
enabled: !NetworkService.changingPreference
|
||||||
onClicked: {
|
onClicked: {
|
||||||
console.log("*** ETHERNET PREFERENCE BUTTON CLICKED ***");
|
console.log("*** ETHERNET PREFERENCE BUTTON CLICKED ***");
|
||||||
if (networkTab.networkStatus !== "ethernet") {
|
if (networkTab.networkStatus !== "ethernet") {
|
||||||
@@ -356,7 +356,7 @@ Item {
|
|||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: Theme.spacingL + 48 + Theme.spacingM
|
anchors.rightMargin: Theme.spacingL + 48 + Theme.spacingM
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
opacity: networkTab.changingNetworkPreference ? 0.6 : 1
|
opacity: NetworkService.changingPreference ? 0.6 : 1
|
||||||
visible: NetworkService.networkStatus !== "wifi" && NetworkService.ethernetConnected && NetworkService.wifiEnabled
|
visible: NetworkService.networkStatus !== "wifi" && NetworkService.ethernetConnected && NetworkService.wifiEnabled
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -366,17 +366,17 @@ Item {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: wifiPreferenceIcon
|
id: wifiPreferenceIcon
|
||||||
|
|
||||||
name: networkTab.changingNetworkPreference ? "sync" : ""
|
name: NetworkService.changingPreference ? "sync" : ""
|
||||||
size: Theme.fontSizeSmall
|
size: Theme.fontSizeSmall
|
||||||
color: networkTab.networkStatus === "wifi" ? Theme.background : Theme.primary
|
color: networkTab.networkStatus === "wifi" ? Theme.background : Theme.primary
|
||||||
visible: networkTab.changingNetworkPreference
|
visible: NetworkService.changingPreference
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
rotation: networkTab.changingNetworkPreference ? wifiPreferenceIcon.rotation : 0
|
rotation: NetworkService.changingPreference ? wifiPreferenceIcon.rotation : 0
|
||||||
|
|
||||||
RotationAnimation {
|
RotationAnimation {
|
||||||
target: wifiPreferenceIcon
|
target: wifiPreferenceIcon
|
||||||
property: "rotation"
|
property: "rotation"
|
||||||
running: networkTab.changingNetworkPreference
|
running: NetworkService.changingPreference
|
||||||
from: 0
|
from: 0
|
||||||
to: 360
|
to: 360
|
||||||
duration: 1000
|
duration: 1000
|
||||||
@@ -386,7 +386,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: NetworkService.changingNetworkPreference ? "Switching..." : "Prefer over Ethernet"
|
text: NetworkService.changingPreference ? "Switching..." : "Prefer over Ethernet"
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: NetworkService.networkStatus === "wifi" ? Theme.background : Theme.primary
|
color: NetworkService.networkStatus === "wifi" ? Theme.background : Theme.primary
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -400,7 +400,7 @@ Item {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
propagateComposedEvents: false
|
propagateComposedEvents: false
|
||||||
enabled: !networkTab.changingNetworkPreference
|
enabled: !NetworkService.changingPreference
|
||||||
onClicked: {
|
onClicked: {
|
||||||
console.log("Force WiFi preference clicked");
|
console.log("Force WiFi preference clicked");
|
||||||
if (NetworkService.networkStatus !== "wifi")
|
if (NetworkService.networkStatus !== "wifi")
|
||||||
|
|||||||
@@ -373,70 +373,34 @@ PanelWindow {
|
|||||||
border.width: 1
|
border.width: 1
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
|
readonly property bool hasNotificationImage: modelData.latestNotification.image && modelData.latestNotification.image !== ""
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 6
|
anchors.margins: 2
|
||||||
source: {
|
source: {
|
||||||
// Don't try to load icons for screenshots - let fallback handle them
|
// Priority 1: Use notification image if available
|
||||||
const isScreenshot = modelData.latestNotification.isScreenshot;
|
if (parent.hasNotificationImage) {
|
||||||
|
return modelData.latestNotification.cleanImage;
|
||||||
if (isScreenshot) {
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelData.latestNotification.appIcon && modelData.latestNotification.appIcon !== "")
|
// Priority 2: Use appIcon - handle URLs directly, use iconPath for icon names
|
||||||
return Quickshell.iconPath(modelData.latestNotification.appIcon, "");
|
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 "";
|
return "";
|
||||||
}
|
}
|
||||||
visible: status === Image.Ready
|
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
|
|
||||||
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 {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: !parent.hasNotificationImage && !parent.isScreenshot
|
visible: !parent.hasNotificationImage && (!modelData.latestNotification.appIcon || modelData.latestNotification.appIcon === "")
|
||||||
text: {
|
text: {
|
||||||
const appName = modelData.appName || "?";
|
const appName = modelData.appName || "?";
|
||||||
return appName.charAt(0).toUpperCase();
|
return appName.charAt(0).toUpperCase();
|
||||||
@@ -445,7 +409,6 @@ PanelWindow {
|
|||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
color: Theme.primaryText
|
color: Theme.primaryText
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -828,35 +791,32 @@ PanelWindow {
|
|||||||
|
|
||||||
readonly property bool hasNotificationImage: modelData.image && modelData.image !== ""
|
readonly property bool hasNotificationImage: modelData.image && modelData.image !== ""
|
||||||
|
|
||||||
// Priority 1: Notification image using Quickshell IconImage
|
IconImage {
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 1
|
anchors.margins: 1
|
||||||
radius: 14
|
source: {
|
||||||
clip: true
|
// Priority 1: Use notification image if available
|
||||||
visible: parent.hasNotificationImage && centerIndividualNotificationImage.status === Image.Ready
|
if (parent.hasNotificationImage) {
|
||||||
color: "transparent"
|
return modelData.cleanImage;
|
||||||
|
|
||||||
IconImage {
|
|
||||||
id: centerIndividualNotificationImage
|
|
||||||
anchors.fill: parent
|
|
||||||
source: modelData.image || ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priority 2: Material Symbols icon for screenshots without notification images
|
// Priority 2: Use appIcon - handle URLs directly, use iconPath for icon names
|
||||||
DankIcon {
|
if (modelData.appIcon) {
|
||||||
anchors.centerIn: parent
|
const appIcon = modelData.appIcon;
|
||||||
name: "screenshot_monitor"
|
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://")) {
|
||||||
size: 12
|
return appIcon;
|
||||||
color: Theme.primaryText
|
}
|
||||||
visible: modelData.isScreenshot && !parent.hasNotificationImage
|
return Quickshell.iconPath(appIcon, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
visible: status === Image.Ready
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priority 3: Fallback text
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: !parent.hasNotificationImage && !modelData.isScreenshot
|
visible: !parent.hasNotificationImage && (!modelData.appIcon || modelData.appIcon === "")
|
||||||
text: {
|
text: {
|
||||||
const appName = modelData.appName || "?";
|
const appName = modelData.appName || "?";
|
||||||
return appName.charAt(0).toUpperCase();
|
return appName.charAt(0).toUpperCase();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Effects
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
@@ -9,19 +8,8 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
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
|
visible: NotificationService.groupedPopups.length > 0
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
@@ -55,43 +43,15 @@ PanelWindow {
|
|||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
required property var modelData
|
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
|
width: parent.width
|
||||||
height: {
|
height: content.height + Theme.spacingL * 2
|
||||||
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;
|
|
||||||
}
|
|
||||||
radius: Theme.cornerRadiusLarge
|
radius: Theme.cornerRadiusLarge
|
||||||
color: Theme.popupBackground()
|
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.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
|
border.width: modelData.latestNotification.urgency === 2 ? 2 : 1
|
||||||
// Stabilize layout during content changes
|
|
||||||
clip: true
|
clip: true
|
||||||
opacity: notificationPopup.visible ? 1 : 0
|
|
||||||
scale: notificationPopup.visible ? 1 : 0.98
|
|
||||||
|
|
||||||
// Priority indicator for urgent notifications
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 4
|
width: 4
|
||||||
height: parent.height - 16
|
height: parent.height - 16
|
||||||
@@ -103,37 +63,14 @@ PanelWindow {
|
|||||||
visible: modelData.latestNotification.urgency === 2
|
visible: modelData.latestNotification.urgency === 2
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collapsed view - shows app header and latest notification
|
Row {
|
||||||
Column {
|
id: content
|
||||||
id: collapsedContent
|
|
||||||
|
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.leftMargin: Theme.spacingL
|
anchors.margins: Theme.spacingL
|
||||||
anchors.rightMargin: Theme.spacingL
|
spacing: Theme.spacingM
|
||||||
anchors.topMargin: 14 // Reduced from Theme.spacingL (16px) by 10%
|
height: Math.max(48, textContent.height)
|
||||||
anchors.bottomMargin: 14 // Reduced from Theme.spacingL (16px) by 10%
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
visible: !expanded
|
|
||||||
|
|
||||||
// App header with group info
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: 72 // Increased height for better text spacing
|
|
||||||
|
|
||||||
// Round app icon with proper API usage
|
|
||||||
Item {
|
|
||||||
id: iconContainer
|
|
||||||
|
|
||||||
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 {
|
Rectangle {
|
||||||
width: 48
|
width: 48
|
||||||
@@ -142,71 +79,40 @@ PanelWindow {
|
|||||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
|
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.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
clip: true
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
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://"))
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 6
|
anchors.margins: 2
|
||||||
source: {
|
source: {
|
||||||
// Don't try to load icons for screenshots - let fallback handle them
|
// Priority 1: Use notification image if available
|
||||||
if (modelData.latestNotification.isScreenshot) {
|
if (parent.hasNotificationImage) {
|
||||||
return "";
|
return modelData.latestNotification.cleanImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelData.latestNotification.appIcon && modelData.latestNotification.appIcon !== "")
|
// Priority 2: Use appIcon - handle URLs directly, use iconPath for icon names
|
||||||
return Quickshell.iconPath(modelData.latestNotification.appIcon, "");
|
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 "";
|
return "";
|
||||||
}
|
}
|
||||||
visible: status === Image.Ready
|
visible: status === Image.Ready
|
||||||
onStatusChanged: {
|
|
||||||
if (status === Image.Error || status === Image.Null || source === "")
|
|
||||||
fallbackIcon.visible = true;
|
|
||||||
else if (status === Image.Ready)
|
|
||||||
fallbackIcon.visible = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: !parent.hasNotificationImage && !parent.isScreenshot
|
visible: !parent.hasNotificationImage && (!modelData.latestNotification.appIcon || modelData.latestNotification.appIcon === "")
|
||||||
text: {
|
text: {
|
||||||
const appName = modelData.appName || "?";
|
const appName = modelData.appName || "?";
|
||||||
return appName.charAt(0).toUpperCase();
|
return appName.charAt(0).toUpperCase();
|
||||||
@@ -215,11 +121,7 @@ PanelWindow {
|
|||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
color: Theme.primaryText
|
color: Theme.primaryText
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count badge for multiple notifications - smaller circle
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 18
|
width: 18
|
||||||
height: 18
|
height: 18
|
||||||
@@ -238,22 +140,15 @@ PanelWindow {
|
|||||||
font.pixelSize: 9
|
font.pixelSize: 9
|
||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// App info and latest notification content
|
|
||||||
Column {
|
Column {
|
||||||
anchors.left: iconContainer.right
|
id: textContent
|
||||||
anchors.leftMargin: Theme.spacingM
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.right: controlsContainer.left
|
spacing: 4
|
||||||
anchors.rightMargin: 8 // Reduced to align text with close button
|
width: parent.width - 48 - Theme.spacingM - controls.width - Theme.spacingS
|
||||||
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 {
|
Text {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: {
|
text: {
|
||||||
@@ -269,11 +164,10 @@ PanelWindow {
|
|||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Latest notification title (emphasized)
|
|
||||||
Text {
|
Text {
|
||||||
text: modelData.latestNotification.summary
|
text: modelData.latestNotification.summary
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
font.pixelSize: Theme.fontSizeMedium + 1 // Slightly larger for emphasis
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
width: parent.width
|
width: parent.width
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
@@ -281,105 +175,21 @@ PanelWindow {
|
|||||||
visible: text.length > 0
|
visible: text.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Latest notification body (smaller, secondary)
|
|
||||||
Text {
|
Text {
|
||||||
text: modelData.latestNotification.body
|
text: modelData.latestNotification.body
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
width: parent.width
|
width: parent.width
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
maximumLineCount: modelData.count > 1 ? 1 : 2 // More space for single notifications
|
maximumLineCount: modelData.count > 1 ? 1 : 2
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
visible: text.length > 0
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
visible: modelData.latestNotification.notification.hasInlineReply && !expanded
|
visible: modelData.latestNotification.notification.hasInlineReply
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width - 60
|
width: parent.width - 60
|
||||||
@@ -391,7 +201,6 @@ PanelWindow {
|
|||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
id: quickReplyField
|
id: quickReplyField
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingS
|
anchors.margins: Theme.spacingS
|
||||||
placeholderText: modelData.latestNotification.notification.inlineReplyPlaceholder || "Quick reply..."
|
placeholderText: modelData.latestNotification.notification.inlineReplyPlaceholder || "Quick reply..."
|
||||||
@@ -403,12 +212,8 @@ PanelWindow {
|
|||||||
text = "";
|
text = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
background: Item {}
|
||||||
background: Item {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -441,113 +246,44 @@ PanelWindow {
|
|||||||
duration: Theme.shortDuration
|
duration: Theme.shortDuration
|
||||||
easing.type: Theme.standardEasing
|
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 {
|
Row {
|
||||||
anchors.left: parent.left
|
id: controls
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Controls container - fixed position on right
|
|
||||||
Item {
|
|
||||||
width: 72
|
|
||||||
height: 32
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 32
|
width: 32
|
||||||
height: 32
|
height: 32
|
||||||
radius: 16
|
radius: 16
|
||||||
anchors.left: parent.left
|
color: expandArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||||
color: collapseAreaTop.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
visible: modelData.count > 1
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "expand_less"
|
name: "expand_more"
|
||||||
size: 18
|
size: 18
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: collapseAreaTop
|
id: expandArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: NotificationService.toggleGroupExpansion(modelData.key)
|
||||||
console.log("Expand clicked - pausing timer");
|
|
||||||
dismissTimer.stop();
|
|
||||||
NotificationService.toggleGroupExpansion(modelData.key);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 32
|
width: 32
|
||||||
height: 32
|
height: 32
|
||||||
radius: 16
|
radius: 16
|
||||||
anchors.right: parent.right
|
color: dismissArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
||||||
color: dismissAllAreaTop.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
@@ -557,352 +293,47 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: dismissAllAreaTop
|
id: dismissArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: NotificationService.dismissGroup(modelData.key)
|
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 {
|
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
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
z: 10 // Higher z-order to ensure hover detection
|
|
||||||
propagateComposedEvents: true
|
propagateComposedEvents: true
|
||||||
onEntered: {
|
onEntered: dismissTimer.stop()
|
||||||
console.log("Notification hover entered - pausing timer");
|
|
||||||
dismissTimer.stop();
|
|
||||||
}
|
|
||||||
onExited: {
|
onExited: {
|
||||||
console.log("Notification hover exited - resuming timer");
|
if (modelData.latestNotification.popup)
|
||||||
if (modelData.latestNotification.popup && !expanded)
|
|
||||||
dismissTimer.restart();
|
dismissTimer.restart();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-dismiss timer - properly pauses on hover
|
|
||||||
Timer {
|
Timer {
|
||||||
id: dismissTimer
|
id: dismissTimer
|
||||||
|
running: modelData.latestNotification.popup
|
||||||
running: modelData.latestNotification.popup && !expanded
|
|
||||||
interval: modelData.latestNotification.notification.expireTimeout > 0 ? modelData.latestNotification.notification.expireTimeout * 1000 : 5000
|
interval: modelData.latestNotification.notification.expireTimeout > 0 ? modelData.latestNotification.notification.expireTimeout * 1000 : 5000
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
console.log("Timer triggered - hover state:", hoverArea.containsMouse, "expanded:", expanded);
|
if (!parent.children[parent.children.length - 2].containsMouse) {
|
||||||
if (!hoverArea.containsMouse && !expanded) {
|
|
||||||
console.log("Dismissing notification");
|
|
||||||
modelData.latestNotification.popup = false;
|
modelData.latestNotification.popup = false;
|
||||||
} else {
|
|
||||||
console.log("Conditions not met - not dismissing");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Smooth popup animations
|
|
||||||
transform: Translate {
|
transform: Translate {
|
||||||
x: notificationPopup.visible ? 0 : 400
|
x: root.visible ? 0 : 400
|
||||||
|
|
||||||
Behavior on x {
|
Behavior on x {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: 350
|
duration: 350
|
||||||
easing.type: Easing.OutCubic
|
easing.type: Easing.OutCubic
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
@@ -910,47 +341,15 @@ PanelWindow {
|
|||||||
duration: 300
|
duration: 300
|
||||||
easing.type: Easing.OutCubic
|
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 {
|
Behavior on implicitHeight {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.mediumDuration
|
duration: Theme.mediumDuration
|
||||||
easing.type: Theme.emphasizedEasing
|
easing.type: Theme.emphasizedEasing
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ PanelWindow {
|
|||||||
Scale {
|
Scale {
|
||||||
id: scaleTransform
|
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
|
origin.y: 0
|
||||||
xScale: processListDropdown.isVisible ? 1 : 0.95
|
xScale: processListDropdown.isVisible ? 1 : 0.95
|
||||||
yScale: processListDropdown.isVisible ? 1 : 0.8
|
yScale: processListDropdown.isVisible ? 1 : 0.8
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
target: AppSearchService
|
||||||
function onReadyChanged() {
|
function onReadyChanged() {
|
||||||
if (AppSearchService.ready) {
|
if (AppSearchService.ready) {
|
||||||
var allCategories = AppSearchService.getAllCategories().filter((cat) => {
|
var allCategories = AppSearchService.getAllCategories().filter((cat) => {
|
||||||
@@ -254,11 +255,10 @@ PanelWindow {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target: DesktopEntries
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
target: AppSearchService
|
||||||
function onApplicationsChanged() {
|
function onApplicationsChanged() {
|
||||||
console.log("SpotlightLauncher: DesktopEntries.applicationsChanged signal received");
|
console.log("SpotlightLauncher: DesktopEntries.applicationsChanged signal received");
|
||||||
// Update categories when applications change
|
// Update categories when applications change
|
||||||
@@ -278,8 +278,6 @@ PanelWindow {
|
|||||||
console.log("SpotlightLauncher: AppSearchService not ready, skipping update");
|
console.log("SpotlightLauncher: AppSearchService not ready, skipping update");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target: DesktopEntries
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dimmed overlay background
|
// Dimmed overlay background
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ PanelWindow {
|
|||||||
|
|
||||||
LauncherButton {
|
LauncherButton {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
isActive: launcher.isVisible
|
isActive: appLauncher.isVisible
|
||||||
onClicked: {
|
onClicked: {
|
||||||
appLauncher.toggle();
|
appLauncher.toggle();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,14 @@ Singleton {
|
|||||||
inlineReplySupported: true
|
inlineReplySupported: true
|
||||||
|
|
||||||
onNotification: notif => {
|
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;
|
notif.tracked = true;
|
||||||
|
|
||||||
const wrapper = notifComponent.createObject(root, {
|
const wrapper = notifComponent.createObject(root, {
|
||||||
@@ -70,35 +77,32 @@ Singleton {
|
|||||||
readonly property string summary: notification.summary
|
readonly property string summary: notification.summary
|
||||||
readonly property string body: notification.body
|
readonly property string body: notification.body
|
||||||
readonly property string appIcon: notification.appIcon
|
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 appName: notification.appName
|
||||||
readonly property string image: notification.image
|
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 int urgency: notification.urgency
|
||||||
readonly property list<NotificationAction> actions: notification.actions
|
readonly property list<NotificationAction> actions: notification.actions
|
||||||
|
|
||||||
// Enhanced properties for better handling
|
// Enhanced properties for better handling
|
||||||
readonly property bool hasImage: image && image.length > 0
|
readonly property bool hasImage: image && image.length > 0
|
||||||
readonly property bool hasAppIcon: appIcon && appIcon.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 isMedia: detectIsMedia()
|
||||||
readonly property bool isSystem: detectIsSystem()
|
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() {
|
function detectIsMedia() {
|
||||||
const appNameLower = appName.toLowerCase();
|
const appNameLower = appName.toLowerCase();
|
||||||
@@ -123,24 +127,6 @@ Singleton {
|
|||||||
summaryLower.includes("system");
|
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 {
|
readonly property Timer timer: Timer {
|
||||||
running: wrapper.popup
|
running: wrapper.popup
|
||||||
@@ -184,114 +170,32 @@ Singleton {
|
|||||||
wrapper.notification.dismiss();
|
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
|
// Android 16-style notification grouping functions
|
||||||
function getGroupKey(wrapper) {
|
function getGroupKey(wrapper) {
|
||||||
const appName = wrapper.appName.toLowerCase();
|
const appName = wrapper.appName.toLowerCase();
|
||||||
|
|
||||||
// Enhanced grouping for conversation apps
|
// Conversation apps with inline reply
|
||||||
if (wrapper.isConversation) {
|
if (wrapper.isConversation) {
|
||||||
const summary = wrapper.summary.toLowerCase();
|
const summary = wrapper.summary.toLowerCase();
|
||||||
const body = wrapper.body.toLowerCase();
|
|
||||||
|
|
||||||
// Discord: Group by channel or conversation
|
// Group by conversation/channel name from summary
|
||||||
if (appName.includes("discord") || appName.includes("vesktop")) {
|
|
||||||
// Channel notifications: "#general", "#announcements"
|
|
||||||
if (summary.includes("#")) {
|
if (summary.includes("#")) {
|
||||||
const channelMatch = summary.match(/#[\w-]+/);
|
const channelMatch = summary.match(/#[\w-]+/);
|
||||||
if (channelMatch) {
|
if (channelMatch) {
|
||||||
return `${appName}:${channelMatch[0]}`;
|
return `${appName}:${channelMatch[0]}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Direct messages: group by sender
|
|
||||||
|
// Group by sender/conversation name if meaningful
|
||||||
if (summary && !summary.includes("new message") && !summary.includes("notification")) {
|
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}:${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`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default conversation grouping
|
// Default conversation grouping
|
||||||
return `${appName}:conversation`;
|
return `${appName}:conversation`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Screenshots: Group all screenshots together
|
|
||||||
if (wrapper.isScreenshot) {
|
|
||||||
return "screenshots";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Media: Replace previous media notification from same app
|
// Media: Replace previous media notification from same app
|
||||||
if (wrapper.isMedia) {
|
if (wrapper.isMedia) {
|
||||||
@@ -332,8 +236,7 @@ Singleton {
|
|||||||
hasInlineReply: false,
|
hasInlineReply: false,
|
||||||
isConversation: notif.isConversation,
|
isConversation: notif.isConversation,
|
||||||
isMedia: notif.isMedia,
|
isMedia: notif.isMedia,
|
||||||
isSystem: notif.isSystem,
|
isSystem: notif.isSystem
|
||||||
isScreenshot: notif.isScreenshot
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,8 +269,7 @@ Singleton {
|
|||||||
hasInlineReply: false,
|
hasInlineReply: false,
|
||||||
isConversation: notif.isConversation,
|
isConversation: notif.isConversation,
|
||||||
isMedia: notif.isMedia,
|
isMedia: notif.isMedia,
|
||||||
isSystem: notif.isSystem,
|
isSystem: notif.isSystem
|
||||||
isScreenshot: notif.isScreenshot
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,17 +321,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (group.isConversation) {
|
if (group.isConversation) {
|
||||||
// Extract conversation/channel name from group key
|
|
||||||
const keyParts = group.key.split(":");
|
const keyParts = group.key.split(":");
|
||||||
if (keyParts.length > 1) {
|
if (keyParts.length > 1) {
|
||||||
const conversationKey = keyParts[keyParts.length - 1];
|
const conversationKey = keyParts[keyParts.length - 1];
|
||||||
if (conversationKey.startsWith("#")) {
|
if (conversationKey !== "conversation") {
|
||||||
return `${conversationKey}: ${group.count} messages`;
|
|
||||||
}
|
|
||||||
if (keyParts.includes("dm")) {
|
|
||||||
return `${conversationKey}: ${group.count} messages`;
|
|
||||||
}
|
|
||||||
if (conversationKey !== "messages" && conversationKey !== "conversation") {
|
|
||||||
return `${conversationKey}: ${group.count} messages`;
|
return `${conversationKey}: ${group.count} messages`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -440,12 +335,6 @@ Singleton {
|
|||||||
return "Now playing";
|
return "Now playing";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (group.isScreenshot) {
|
|
||||||
if (group.count === 1) {
|
|
||||||
return "Screenshot saved";
|
|
||||||
}
|
|
||||||
return `${group.count} screenshots saved`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (group.isSystem) {
|
if (group.isSystem) {
|
||||||
const keyParts = group.key.split(":");
|
const keyParts = group.key.split(":");
|
||||||
@@ -481,9 +370,6 @@ Singleton {
|
|||||||
return group.latestNotification.body || "Media playback";
|
return group.latestNotification.body || "Media playback";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (group.isScreenshot) {
|
|
||||||
return group.latestNotification.body || "Screenshot available in Pictures/Screenshots";
|
|
||||||
}
|
|
||||||
|
|
||||||
return `Latest: ${group.latestNotification.summary}`;
|
return `Latest: ${group.latestNotification.summary}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user