mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-09 15:05:39 -05:00
Migrate notification system to native Quickshell NotificationServer API
- Replace custom NotificationGroupingService with native NotificationService - Implement proper image/icon priority system (notification image → app icon → fallback) - Add NotificationItem with image layering and elegant emoji fallbacks - Create native popup and history components with smooth animations - Fix Discord/Vesktop avatar display issues - Clean up legacy notification components and demos - Improve Material Design 3 theming consistency
This commit is contained in:
@@ -1,530 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Grouped notifications model - initialize as ListModel directly
|
||||
property ListModel groupedNotifications: ListModel {}
|
||||
|
||||
// Total count of all notifications across all groups
|
||||
property int totalCount: 0
|
||||
|
||||
// Map to track group indices by app name for efficient lookups
|
||||
property var appGroupMap: ({})
|
||||
|
||||
// Debounce timer for sorting
|
||||
property bool _sortDirty: false
|
||||
Timer {
|
||||
id: sortTimer
|
||||
interval: 50 // 50ms debounce interval
|
||||
onTriggered: {
|
||||
if (_sortDirty) {
|
||||
sortGroupsByPriority()
|
||||
_sortDirty = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configuration
|
||||
property int maxNotificationsPerGroup: 10
|
||||
property int maxGroups: 20
|
||||
|
||||
// Priority constants for Android 16-style stacking
|
||||
readonly property int priorityHigh: 2 // Conversations, calls, media
|
||||
readonly property int priorityNormal: 1 // Regular notifications
|
||||
readonly property int priorityLow: 0 // System, background updates
|
||||
|
||||
// Notification type constants
|
||||
readonly property int typeConversation: 1
|
||||
readonly property int typeMedia: 2
|
||||
readonly property int typeSystem: 3
|
||||
readonly property int typeNormal: 4
|
||||
|
||||
|
||||
|
||||
// Format timestamp for display
|
||||
function formatTimestamp(timestamp) {
|
||||
if (!timestamp) return ""
|
||||
|
||||
const now = new Date()
|
||||
const notifTime = new Date(timestamp)
|
||||
const diffMs = now.getTime() - notifTime.getTime()
|
||||
const diffMinutes = Math.floor(diffMs / 60000)
|
||||
const diffHours = Math.floor(diffMs / 3600000)
|
||||
const diffDays = Math.floor(diffMs / 86400000)
|
||||
|
||||
if (diffMinutes < 1) {
|
||||
return "now"
|
||||
} else if (diffMinutes < 60) {
|
||||
return `${diffMinutes}m ago`
|
||||
} else if (diffHours < 24) {
|
||||
return `${diffHours}h ago`
|
||||
} else if (diffDays < 7) {
|
||||
return `${diffDays}d ago`
|
||||
} else {
|
||||
return notifTime.toLocaleDateString()
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new notification to the appropriate group
|
||||
function addNotification(notificationObj) {
|
||||
if (!notificationObj || !notificationObj.appName) {
|
||||
console.warn("Invalid notification object:", notificationObj)
|
||||
return
|
||||
}
|
||||
|
||||
// Enhance notification with priority and type detection
|
||||
notificationObj = enhanceNotification(notificationObj)
|
||||
|
||||
const appName = notificationObj.appName
|
||||
let groupIndex = appGroupMap[appName]
|
||||
|
||||
if (groupIndex === undefined) {
|
||||
// Create new group
|
||||
groupIndex = createNewGroup(appName, notificationObj)
|
||||
} else {
|
||||
// Add to existing group
|
||||
addToExistingGroup(groupIndex, notificationObj)
|
||||
}
|
||||
|
||||
updateTotalCount()
|
||||
}
|
||||
|
||||
// Create a new notification group
|
||||
function createNewGroup(appName, notificationObj) {
|
||||
// Check if we need to remove oldest group
|
||||
if (groupedNotifications.count >= maxGroups) {
|
||||
removeOldestGroup()
|
||||
}
|
||||
|
||||
const groupIndex = groupedNotifications.count
|
||||
const notificationsList = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
ListModel {}
|
||||
`, root)
|
||||
|
||||
notificationsList.append(notificationObj)
|
||||
|
||||
// Create properly structured latestNotification object
|
||||
const latestNotificationData = {
|
||||
"id": notificationObj.id || "",
|
||||
"appName": notificationObj.appName || "",
|
||||
"appIcon": notificationObj.appIcon || "",
|
||||
"summary": notificationObj.summary || "",
|
||||
"body": notificationObj.body || "",
|
||||
"timestamp": notificationObj.timestamp || new Date(),
|
||||
"priority": notificationObj.priority || priorityNormal,
|
||||
"notificationType": notificationObj.notificationType || typeNormal,
|
||||
"urgency": notificationObj.urgency || 1,
|
||||
"image": notificationObj.image || ""
|
||||
}
|
||||
|
||||
const groupData = {
|
||||
"appName": appName,
|
||||
"appIcon": notificationObj.appIcon || "",
|
||||
"notifications": notificationsList,
|
||||
"totalCount": 1,
|
||||
"latestNotification": latestNotificationData,
|
||||
"expanded": false,
|
||||
"timestamp": notificationObj.timestamp || new Date(),
|
||||
"priority": notificationObj.priority || priorityNormal,
|
||||
"notificationType": notificationObj.notificationType || typeNormal
|
||||
}
|
||||
|
||||
groupedNotifications.append(groupData)
|
||||
|
||||
// Sort groups by priority after adding
|
||||
requestSort()
|
||||
|
||||
appGroupMap[appName] = groupIndex
|
||||
updateGroupMap()
|
||||
|
||||
return groupIndex
|
||||
}
|
||||
|
||||
// Add notification to existing group
|
||||
function addToExistingGroup(groupIndex, notificationObj) {
|
||||
if (groupIndex >= groupedNotifications.count) {
|
||||
console.warn("Invalid group index:", groupIndex)
|
||||
return
|
||||
}
|
||||
|
||||
const group = groupedNotifications.get(groupIndex)
|
||||
if (!group) return
|
||||
|
||||
// Add to front of group (newest first)
|
||||
group.notifications.insert(0, notificationObj)
|
||||
|
||||
// Create a new object with proper property structure for latestNotification
|
||||
const latestNotificationData = {
|
||||
"id": notificationObj.id || "",
|
||||
"appName": notificationObj.appName || "",
|
||||
"appIcon": notificationObj.appIcon || "",
|
||||
"summary": notificationObj.summary || "",
|
||||
"body": notificationObj.body || "",
|
||||
"timestamp": notificationObj.timestamp || new Date(),
|
||||
"priority": notificationObj.priority || priorityNormal,
|
||||
"notificationType": notificationObj.notificationType || typeNormal,
|
||||
"urgency": notificationObj.urgency || 1,
|
||||
"image": notificationObj.image || ""
|
||||
}
|
||||
|
||||
// Update group metadata
|
||||
groupedNotifications.setProperty(groupIndex, "totalCount", group.totalCount + 1)
|
||||
groupedNotifications.setProperty(groupIndex, "latestNotification", latestNotificationData)
|
||||
groupedNotifications.setProperty(groupIndex, "timestamp", notificationObj.timestamp || new Date())
|
||||
|
||||
// Update group priority if this notification has higher priority
|
||||
const currentPriority = group.priority || priorityNormal
|
||||
const newPriority = Math.max(currentPriority, notificationObj.priority || priorityNormal)
|
||||
groupedNotifications.setProperty(groupIndex, "priority", newPriority)
|
||||
|
||||
// Update notification type if needed
|
||||
if (notificationObj.notificationType === typeConversation ||
|
||||
notificationObj.notificationType === typeMedia) {
|
||||
groupedNotifications.setProperty(groupIndex, "notificationType", notificationObj.notificationType)
|
||||
}
|
||||
|
||||
// Keep only max notifications per group
|
||||
while (group.notifications.count > maxNotificationsPerGroup) {
|
||||
group.notifications.remove(group.notifications.count - 1)
|
||||
}
|
||||
|
||||
// Re-sort groups by priority after updating
|
||||
requestSort()
|
||||
}
|
||||
|
||||
// Request a debounced sort
|
||||
function requestSort() {
|
||||
_sortDirty = true
|
||||
sortTimer.restart()
|
||||
}
|
||||
|
||||
// Sort groups by priority and recency
|
||||
function sortGroupsByPriority() {
|
||||
if (groupedNotifications.count <= 1) return
|
||||
|
||||
for (let i = 0; i < groupedNotifications.count - 1; i++) {
|
||||
for (let j = 0; j < groupedNotifications.count - i - 1; j++) {
|
||||
const groupA = groupedNotifications.get(j)
|
||||
const groupB = groupedNotifications.get(j + 1)
|
||||
|
||||
const priorityA = groupA.priority || priorityNormal
|
||||
const priorityB = groupB.priority || priorityNormal
|
||||
|
||||
let shouldSwap = false
|
||||
if (priorityA !== priorityB) {
|
||||
if (priorityB > priorityA) {
|
||||
shouldSwap = true
|
||||
}
|
||||
} else {
|
||||
const timeA = new Date(groupA.timestamp || 0).getTime()
|
||||
const timeB = new Date(groupB.timestamp || 0).getTime()
|
||||
if (timeB > timeA) {
|
||||
shouldSwap = true
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSwap) {
|
||||
// Swap the elements at j and j + 1
|
||||
groupedNotifications.move(j, j + 1, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateGroupMap()
|
||||
}
|
||||
|
||||
// Remove the oldest group (least recent activity)
|
||||
function removeOldestGroup() {
|
||||
if (groupedNotifications.count === 0) return
|
||||
|
||||
const lastIndex = groupedNotifications.count - 1
|
||||
const group = groupedNotifications.get(lastIndex)
|
||||
if (group) {
|
||||
delete appGroupMap[group.appName]
|
||||
groupedNotifications.remove(lastIndex)
|
||||
updateGroupMap()
|
||||
}
|
||||
}
|
||||
|
||||
// Update the app group map after structural changes
|
||||
function updateGroupMap() {
|
||||
appGroupMap = {}
|
||||
for (let i = 0; i < groupedNotifications.count; i++) {
|
||||
const group = groupedNotifications.get(i)
|
||||
if (group) {
|
||||
appGroupMap[group.appName] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle group expansion state
|
||||
function toggleGroupExpansion(groupIndex) {
|
||||
if (groupIndex >= groupedNotifications.count) return
|
||||
|
||||
const group = groupedNotifications.get(groupIndex)
|
||||
if (group) {
|
||||
groupedNotifications.setProperty(groupIndex, "expanded", !group.expanded)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a specific notification from a group
|
||||
function removeNotification(groupIndex, notificationIndex) {
|
||||
if (groupIndex >= groupedNotifications.count) return
|
||||
|
||||
const group = groupedNotifications.get(groupIndex)
|
||||
if (!group || notificationIndex >= group.notifications.count) return
|
||||
|
||||
group.notifications.remove(notificationIndex)
|
||||
|
||||
// Update group count
|
||||
const newCount = group.totalCount - 1
|
||||
groupedNotifications.setProperty(groupIndex, "totalCount", newCount)
|
||||
|
||||
// If group is empty, remove it
|
||||
if (newCount === 0) {
|
||||
removeGroup(groupIndex)
|
||||
} else {
|
||||
// Update latest notification if we removed the latest one
|
||||
if (notificationIndex === 0 && group.notifications.count > 0) {
|
||||
const newLatest = group.notifications.get(0)
|
||||
|
||||
// Create a new object with the correct structure
|
||||
const latestNotificationData = {
|
||||
"id": newLatest.id || "",
|
||||
"appName": newLatest.appName || "",
|
||||
"appIcon": newLatest.appIcon || "",
|
||||
"summary": newLatest.summary || "",
|
||||
"body": newLatest.body || "",
|
||||
"timestamp": newLatest.timestamp || new Date(),
|
||||
"priority": newLatest.priority || priorityNormal,
|
||||
"notificationType": newLatest.notificationType || typeNormal,
|
||||
"urgency": newLatest.urgency || 1,
|
||||
"image": newLatest.image || ""
|
||||
}
|
||||
|
||||
groupedNotifications.setProperty(groupIndex, "latestNotification", latestNotificationData)
|
||||
|
||||
// Update group priority after removal
|
||||
const newPriority = getGroupPriority(groupIndex)
|
||||
groupedNotifications.setProperty(groupIndex, "priority", newPriority)
|
||||
}
|
||||
}
|
||||
|
||||
updateTotalCount()
|
||||
}
|
||||
|
||||
// Remove an entire group
|
||||
function removeGroup(groupIndex) {
|
||||
if (groupIndex >= groupedNotifications.count) return
|
||||
|
||||
const group = groupedNotifications.get(groupIndex)
|
||||
if (group) {
|
||||
delete appGroupMap[group.appName]
|
||||
groupedNotifications.remove(groupIndex)
|
||||
updateGroupMap() // Re-map all group indices
|
||||
updateTotalCount()
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all notifications
|
||||
function clearAllNotifications() {
|
||||
groupedNotifications.clear()
|
||||
appGroupMap = {}
|
||||
totalCount = 0
|
||||
}
|
||||
|
||||
// Update total count across all groups
|
||||
function updateTotalCount() {
|
||||
let count = 0
|
||||
for (let i = 0; i < groupedNotifications.count; i++) {
|
||||
const group = groupedNotifications.get(i)
|
||||
if (group) {
|
||||
count += group.totalCount
|
||||
}
|
||||
}
|
||||
totalCount = count
|
||||
}
|
||||
|
||||
// Enhance notification with priority and type detection
|
||||
function enhanceNotification(notificationObj) {
|
||||
const enhanced = Object.assign({}, notificationObj)
|
||||
|
||||
// Detect notification type and priority
|
||||
enhanced.notificationType = detectNotificationType(enhanced)
|
||||
enhanced.priority = detectPriority(enhanced)
|
||||
|
||||
return enhanced
|
||||
}
|
||||
|
||||
// Detect notification type based on content and app
|
||||
function detectNotificationType(notification) {
|
||||
const appName = notification.appName?.toLowerCase() || ""
|
||||
const summary = notification.summary?.toLowerCase() || ""
|
||||
const body = notification.body?.toLowerCase() || ""
|
||||
|
||||
// Media notifications
|
||||
if (appName.includes("music") || appName.includes("player") ||
|
||||
appName.includes("spotify") || appName.includes("youtube") ||
|
||||
summary.includes("now playing") || summary.includes("playing")) {
|
||||
return typeMedia
|
||||
}
|
||||
|
||||
// Conversation notifications
|
||||
if (appName.includes("message") || appName.includes("chat") ||
|
||||
appName.includes("telegram") || appName.includes("whatsapp") ||
|
||||
appName.includes("discord") || appName.includes("slack") ||
|
||||
summary.includes("message") || body.includes("message")) {
|
||||
return typeConversation
|
||||
}
|
||||
|
||||
// System notifications
|
||||
if (appName.includes("system") || appName.includes("update") ||
|
||||
summary.includes("update") || summary.includes("system")) {
|
||||
return typeSystem
|
||||
}
|
||||
|
||||
return typeNormal
|
||||
}
|
||||
|
||||
// Detect priority based on type and urgency
|
||||
function detectPriority(notification) {
|
||||
const notificationType = notification.notificationType
|
||||
const urgency = notification.urgency || 1 // Default to normal
|
||||
|
||||
// High priority for conversations and media
|
||||
if (notificationType === typeConversation || notificationType === typeMedia) {
|
||||
return priorityHigh
|
||||
}
|
||||
|
||||
// Low priority for system notifications
|
||||
if (notificationType === typeSystem) {
|
||||
return priorityLow
|
||||
}
|
||||
|
||||
// Use urgency for regular notifications
|
||||
if (urgency >= 2) {
|
||||
return priorityHigh
|
||||
} else if (urgency >= 1) {
|
||||
return priorityNormal
|
||||
}
|
||||
|
||||
return priorityLow
|
||||
}
|
||||
|
||||
// Get group priority (highest priority notification in group)
|
||||
function getGroupPriority(groupIndex) {
|
||||
if (groupIndex >= groupedNotifications.count) return priorityLow
|
||||
|
||||
const group = groupedNotifications.get(groupIndex)
|
||||
if (!group) return priorityLow
|
||||
|
||||
let maxPriority = priorityLow
|
||||
for (let i = 0; i < group.notifications.count; i++) {
|
||||
const notification = group.notifications.get(i)
|
||||
if (notification && notification.priority > maxPriority) {
|
||||
maxPriority = notification.priority
|
||||
}
|
||||
}
|
||||
|
||||
return maxPriority
|
||||
}
|
||||
|
||||
// Generate smart group summary for collapsed state
|
||||
function generateGroupSummary(group) {
|
||||
if (!group || !group.notifications || group.notifications.count === 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const notificationCount = group.notifications.count
|
||||
const latestNotification = group.notifications.get(0)
|
||||
|
||||
if (notificationCount === 1) {
|
||||
return latestNotification.summary || latestNotification.body || ""
|
||||
}
|
||||
|
||||
// For conversations, show sender names
|
||||
if (latestNotification.notificationType === typeConversation) {
|
||||
const senders = []
|
||||
for (let i = 0; i < Math.min(3, notificationCount); i++) {
|
||||
const notif = group.notifications.get(i)
|
||||
if (notif && notif.summary && !senders.includes(notif.summary)) {
|
||||
senders.push(notif.summary)
|
||||
}
|
||||
}
|
||||
|
||||
if (senders.length > 0) {
|
||||
const remaining = notificationCount - senders.length
|
||||
if (remaining > 0) {
|
||||
return `${senders.join(", ")} and ${remaining} other${remaining > 1 ? "s" : ""}`
|
||||
}
|
||||
return senders.join(", ")
|
||||
}
|
||||
}
|
||||
|
||||
// For media, show current track info
|
||||
if (latestNotification.notificationType === typeMedia) {
|
||||
return latestNotification.summary || "Media playing"
|
||||
}
|
||||
|
||||
// Generic summary for other types
|
||||
return `${notificationCount} notification${notificationCount > 1 ? "s" : ""}`
|
||||
}
|
||||
|
||||
// Get notification by ID across all groups
|
||||
function getNotificationById(notificationId) {
|
||||
for (let i = 0; i < groupedNotifications.count; i++) {
|
||||
const group = groupedNotifications.get(i)
|
||||
if (!group) continue
|
||||
|
||||
for (let j = 0; j < group.notifications.count; j++) {
|
||||
const notification = group.notifications.get(j)
|
||||
if (notification && notification.id === notificationId) {
|
||||
return {
|
||||
groupIndex: i,
|
||||
notificationIndex: j,
|
||||
notification: notification
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Get group by app name
|
||||
function getGroupByAppName(appName) {
|
||||
const groupIndex = appGroupMap[appName]
|
||||
if (groupIndex !== undefined) {
|
||||
return {
|
||||
groupIndex: groupIndex,
|
||||
group: groupedNotifications.get(groupIndex)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Get visible notifications for a group (considering expansion state)
|
||||
function getVisibleNotifications(groupIndex, maxVisible = 3) {
|
||||
if (groupIndex >= groupedNotifications.count) return []
|
||||
|
||||
const group = groupedNotifications.get(groupIndex)
|
||||
if (!group) return []
|
||||
|
||||
if (group.expanded) {
|
||||
// Show all notifications when expanded
|
||||
return group.notifications
|
||||
} else {
|
||||
// Show only the latest notification(s) when collapsed
|
||||
const visibleCount = Math.min(maxVisible, group.notifications.count)
|
||||
const visible = []
|
||||
for (let i = 0; i < visibleCount; i++) {
|
||||
visible.push(group.notifications.get(i))
|
||||
}
|
||||
return visible
|
||||
}
|
||||
}
|
||||
}
|
||||
184
Services/NotificationService.qml
Normal file
184
Services/NotificationService.qml
Normal file
@@ -0,0 +1,184 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property list<NotifWrapper> notifications: []
|
||||
readonly property list<NotifWrapper> popups: notifications.filter(n => n.popup)
|
||||
|
||||
NotificationServer {
|
||||
id: server
|
||||
|
||||
keepOnReload: false
|
||||
actionsSupported: true
|
||||
bodyHyperlinksSupported: true
|
||||
bodyImagesSupported: true
|
||||
bodyMarkupSupported: true
|
||||
imageSupported: true
|
||||
|
||||
onNotification: notif => {
|
||||
notif.tracked = true;
|
||||
|
||||
const wrapper = notifComponent.createObject(root, {
|
||||
popup: true,
|
||||
notification: notif
|
||||
});
|
||||
|
||||
root.notifications.push(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
component NotifWrapper: QtObject {
|
||||
id: wrapper
|
||||
|
||||
property bool popup: true
|
||||
readonly property date time: new Date()
|
||||
readonly property string timeStr: {
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - time.getTime();
|
||||
const m = Math.floor(diff / 60000);
|
||||
const h = Math.floor(m / 60);
|
||||
|
||||
if (h < 1 && m < 1)
|
||||
return "now";
|
||||
if (h < 1)
|
||||
return `${m}m`;
|
||||
return `${h}h`;
|
||||
}
|
||||
|
||||
required property Notification notification
|
||||
readonly property string summary: notification.summary
|
||||
readonly property string body: notification.body
|
||||
readonly property string appIcon: notification.appIcon
|
||||
readonly property string appName: notification.appName
|
||||
readonly property string image: notification.image
|
||||
readonly property int urgency: notification.urgency
|
||||
readonly property list<NotificationAction> 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 isMedia: detectIsMedia()
|
||||
readonly property bool isSystem: detectIsSystem()
|
||||
|
||||
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();
|
||||
const summaryLower = summary.toLowerCase();
|
||||
|
||||
return appNameLower.includes("spotify") ||
|
||||
appNameLower.includes("vlc") ||
|
||||
appNameLower.includes("mpv") ||
|
||||
appNameLower.includes("music") ||
|
||||
appNameLower.includes("player") ||
|
||||
summaryLower.includes("now playing") ||
|
||||
summaryLower.includes("playing");
|
||||
}
|
||||
|
||||
function detectIsSystem() {
|
||||
const appNameLower = appName.toLowerCase();
|
||||
const summaryLower = summary.toLowerCase();
|
||||
|
||||
return appNameLower.includes("system") ||
|
||||
appNameLower.includes("update") ||
|
||||
summaryLower.includes("update") ||
|
||||
summaryLower.includes("system");
|
||||
}
|
||||
|
||||
readonly property Timer timer: Timer {
|
||||
running: wrapper.popup
|
||||
interval: wrapper.notification.expireTimeout > 0 ? wrapper.notification.expireTimeout : 5000 // 5 second default
|
||||
onTriggered: {
|
||||
wrapper.popup = false;
|
||||
}
|
||||
}
|
||||
|
||||
readonly property Connections conn: Connections {
|
||||
target: wrapper.notification.Retainable
|
||||
|
||||
function onDropped(): void {
|
||||
const index = root.notifications.indexOf(wrapper);
|
||||
if (index !== -1) {
|
||||
root.notifications.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function onAboutToDestroy(): void {
|
||||
wrapper.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: notifComponent
|
||||
NotifWrapper {}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
function clearAllNotifications() {
|
||||
// Create a copy of the array to avoid modification during iteration
|
||||
const notificationsCopy = [...root.notifications];
|
||||
for (const notif of notificationsCopy) {
|
||||
notif.notification.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
function dismissNotification(wrapper) {
|
||||
wrapper.notification.dismiss();
|
||||
}
|
||||
|
||||
function getNotificationIcon(wrapper) {
|
||||
// Priority 1: Use notification image if available (Discord avatars, etc.)
|
||||
if (wrapper.hasImage) {
|
||||
return wrapper.image;
|
||||
}
|
||||
|
||||
// Priority 2: Use app icon if available
|
||||
if (wrapper.hasAppIcon) {
|
||||
return Quickshell.iconPath(wrapper.appIcon, "image-missing");
|
||||
}
|
||||
|
||||
// Priority 3: Generate fallback icon based on type
|
||||
return getFallbackIcon(wrapper);
|
||||
}
|
||||
|
||||
function getFallbackIcon(wrapper) {
|
||||
if (wrapper.isConversation) {
|
||||
return Quickshell.iconPath("chat", "image-missing");
|
||||
} else if (wrapper.isMedia) {
|
||||
return Quickshell.iconPath("music_note", "image-missing");
|
||||
} else if (wrapper.isSystem) {
|
||||
return Quickshell.iconPath("settings", "image-missing");
|
||||
}
|
||||
return Quickshell.iconPath("apps", "image-missing");
|
||||
}
|
||||
|
||||
function getAppIconPath(wrapper) {
|
||||
if (wrapper.hasAppIcon) {
|
||||
return Quickshell.iconPath(wrapper.appIcon, "image-missing");
|
||||
}
|
||||
return getFallbackIcon(wrapper);
|
||||
}
|
||||
}
|
||||
@@ -15,4 +15,4 @@ singleton NiriWorkspaceService 1.0 NiriWorkspaceService.qml
|
||||
singleton CalendarService 1.0 CalendarService.qml
|
||||
singleton UserInfoService 1.0 UserInfoService.qml
|
||||
singleton FocusedWindowService 1.0 FocusedWindowService.qml
|
||||
singleton NotificationGroupingService 1.0 NotificationGroupingService.qml
|
||||
singleton NotificationService 1.0 NotificationService.qml
|
||||
|
||||
Reference in New Issue
Block a user