1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-27 15:02:50 -05:00

Enhanced Notification System & Settings

This commit is contained in:
purian23
2025-07-20 19:07:48 -04:00
parent 30983b50d5
commit 79a600cf0d
5 changed files with 678 additions and 40 deletions

View File

@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Services.Notifications
import qs.Services
Singleton {
id: root
@@ -17,6 +18,11 @@ Singleton {
property var expandedGroups: ({}) // Track which groups are expanded
property var expandedMessages: ({}) // Track which individual messages are expanded
// Notification persistence settings
property int maxStoredNotifications: 100
property int maxNotificationAge: 7 * 24 * 60 * 60 * 1000 // 7 days in milliseconds
property var persistedNotifications: ([]) // Stored notification history
NotificationServer {
id: server
@@ -39,6 +45,13 @@ Singleton {
console.log("urgency:", notif.urgency);
console.log("hasInlineReply:", notif.hasInlineReply);
console.log("=============================");
// Check if notification should be shown based on settings
if (!NotificationSettings.shouldShowNotification(notif)) {
console.log("Notification blocked by settings for app:", notif.appName);
return;
}
notif.tracked = true;
const wrapper = notifComponent.createObject(root, {
@@ -47,7 +60,32 @@ Singleton {
});
if (wrapper) {
root.notifications.push(wrapper);
// Handle media notification replacement
if (wrapper.isMedia) {
handleMediaNotification(wrapper);
} else {
root.notifications.push(wrapper);
}
// Auto-expand conversation groups with new messages
if (wrapper.isConversation && notifications.length > 1) {
const groupKey = getGroupKey(wrapper);
const existingGroup = groupedNotifications.find(group => group.key === groupKey);
if (existingGroup && existingGroup.count > 1) {
let newExpandedGroups = {};
for (const key in expandedGroups) {
newExpandedGroups[key] = expandedGroups[key];
}
newExpandedGroups[groupKey] = true;
expandedGroups = newExpandedGroups;
}
}
// Add to persistent storage (only for non-transient notifications)
if (!notif.transient) {
addToPersistentStorage(wrapper);
}
console.log("Notification added. Total notifications:", root.notifications.length);
console.log("Grouped notifications:", root.groupedNotifications.length);
} else {
@@ -101,13 +139,91 @@ Singleton {
readonly property bool hasImage: image && image.length > 0
readonly property bool hasAppIcon: appIcon && appIcon.length > 0
readonly property bool isConversation: notification.hasInlineReply
readonly property bool isMedia: isMediaNotification()
readonly property bool isSystem: isSystemNotification()
function isMediaNotification() {
const appNameLower = appName.toLowerCase();
const summaryLower = summary.toLowerCase();
// Check for media apps
if (appNameLower.includes("spotify") ||
appNameLower.includes("vlc") ||
appNameLower.includes("mpv") ||
appNameLower.includes("music") ||
appNameLower.includes("player") ||
appNameLower.includes("youtube") ||
appNameLower.includes("media")) {
return true;
}
// Check for media-related summary text
if (summaryLower.includes("now playing") ||
summaryLower.includes("playing") ||
summaryLower.includes("paused") ||
summaryLower.includes("track")) {
return true;
}
// Check for media actions
for (const action of actions) {
const actionId = action.identifier.toLowerCase();
if (actionId.includes("play") ||
actionId.includes("pause") ||
actionId.includes("next") ||
actionId.includes("previous") ||
actionId.includes("media")) {
return true;
}
}
return false;
}
function isSystemNotification() {
const appNameLower = appName.toLowerCase();
const summaryLower = summary.toLowerCase();
// Check for system apps
if (appNameLower.includes("system") ||
appNameLower.includes("networkmanager") ||
appNameLower.includes("upower") ||
appNameLower.includes("notification-daemon") ||
appNameLower.includes("systemd") ||
appNameLower.includes("update") ||
appNameLower.includes("battery") ||
appNameLower.includes("network") ||
appNameLower.includes("wifi") ||
appNameLower.includes("bluetooth")) {
return true;
}
// Check for system-related summary text
if (summaryLower.includes("battery") ||
summaryLower.includes("power") ||
summaryLower.includes("update") ||
summaryLower.includes("connected") ||
summaryLower.includes("disconnected") ||
summaryLower.includes("network") ||
summaryLower.includes("wifi") ||
summaryLower.includes("bluetooth")) {
return true;
}
return false;
}
readonly property Timer timer: Timer {
running: wrapper.popup
interval: wrapper.notification.expireTimeout > 0 ? wrapper.notification.expireTimeout : 5000 // 5 second default
interval: {
if (wrapper.notification.expireTimeout > 0) {
return wrapper.notification.expireTimeout * 1000;
}
return NotificationSettings.getAppTimeout(wrapper.appName);
}
onTriggered: {
wrapper.popup = false;
}
@@ -147,11 +263,53 @@ Singleton {
wrapper.notification.dismiss();
}
function handleMediaNotification(newMediaWrapper) {
const groupKey = getGroupKey(newMediaWrapper);
// Find and replace any existing media notification from the same app
for (let i = notifications.length - 1; i >= 0; i--) {
const existing = notifications[i];
if (existing.isMedia && getGroupKey(existing) === groupKey) {
// Replace the existing media notification
existing.notification.dismiss();
break;
}
}
// Add the new media notification
root.notifications.push(newMediaWrapper);
}
// Android 16-style notification grouping functions
function getGroupKey(wrapper) {
const appName = wrapper.appName.toLowerCase();
// Media notifications: replace previous media notification from same app
if (wrapper.isMedia) {
return `${appName}:media`;
}
// System notifications: group by category
if (wrapper.isSystem) {
const summary = wrapper.summary.toLowerCase();
if (summary.includes("battery") || summary.includes("power")) {
return "system:battery";
}
if (summary.includes("network") || summary.includes("wifi") || summary.includes("connected") || summary.includes("disconnected")) {
return "system:network";
}
if (summary.includes("update") || summary.includes("upgrade")) {
return "system:updates";
}
if (summary.includes("bluetooth")) {
return "system:bluetooth";
}
// Default system grouping
return "system:general";
}
// Conversation apps with inline reply
if (wrapper.isConversation) {
const summary = wrapper.summary.toLowerCase();
@@ -173,8 +331,6 @@ Singleton {
return `${appName}:conversation`;
}
// Default: Group by app
return appName;
}
@@ -192,7 +348,9 @@ Singleton {
latestNotification: null,
count: 0,
hasInlineReply: false,
isConversation: notif.isConversation
isConversation: notif.isConversation,
isMedia: notif.isMedia,
isSystem: notif.isSystem
};
}
@@ -223,7 +381,9 @@ Singleton {
latestNotification: null,
count: 0,
hasInlineReply: false,
isConversation: notif.isConversation
isConversation: notif.isConversation,
isMedia: notif.isMedia,
isSystem: notif.isSystem
};
}
@@ -274,6 +434,25 @@ Singleton {
return group.latestNotification.summary;
}
if (group.isMedia) {
return "Now Playing";
}
if (group.isSystem) {
const keyParts = group.key.split(":");
if (keyParts.length > 1) {
const systemCategory = keyParts[1];
switch (systemCategory) {
case "battery": return `${group.count} Battery alerts`;
case "network": return `${group.count} Network updates`;
case "updates": return `${group.count} System updates`;
case "bluetooth": return `${group.count} Bluetooth updates`;
default: return `${group.count} System notifications`;
}
}
return `${group.count} System notifications`;
}
if (group.isConversation) {
const keyParts = group.key.split(":");
if (keyParts.length > 1) {
@@ -285,7 +464,6 @@ Singleton {
return `${group.count} new messages`;
}
return `${group.count} notifications`;
}
@@ -294,6 +472,18 @@ Singleton {
return group.latestNotification.body;
}
if (group.isMedia) {
const latest = group.latestNotification;
if (latest.body && latest.body.length > 0) {
return latest.body;
}
return latest.summary;
}
if (group.isSystem) {
return `Latest: ${group.latestNotification.summary}`;
}
if (group.isConversation) {
const latest = group.latestNotification;
if (latest.body && latest.body.length > 0) {
@@ -302,8 +492,70 @@ Singleton {
return "Tap to view conversation";
}
return `Latest: ${group.latestNotification.summary}`;
}
// Notification persistence functions
function addToPersistentStorage(wrapper) {
const persistedNotif = {
id: wrapper.notification.id,
appName: wrapper.appName,
summary: wrapper.summary,
body: wrapper.body,
appIcon: wrapper.appIcon,
image: wrapper.image,
urgency: wrapper.urgency,
timestamp: wrapper.time.getTime(),
isConversation: wrapper.isConversation,
isMedia: wrapper.isMedia,
isSystem: wrapper.isSystem
};
// Add to beginning of array
persistedNotifications.unshift(persistedNotif);
// Clean up old notifications
cleanupPersistentStorage();
}
function cleanupPersistentStorage() {
const now = new Date().getTime();
let newPersisted = [];
for (let i = 0; i < persistedNotifications.length && i < maxStoredNotifications; i++) {
const notif = persistedNotifications[i];
if (now - notif.timestamp < maxNotificationAge) {
newPersisted.push(notif);
}
}
persistedNotifications = newPersisted;
}
function getPersistentNotificationsByApp(appName) {
return persistedNotifications.filter(notif => notif.appName.toLowerCase() === appName.toLowerCase());
}
function getPersistentNotificationsByType(type) {
switch (type) {
case "conversation": return persistedNotifications.filter(notif => notif.isConversation);
case "media": return persistedNotifications.filter(notif => notif.isMedia);
case "system": return persistedNotifications.filter(notif => notif.isSystem);
default: return persistedNotifications;
}
}
function searchPersistentNotifications(query) {
const searchLower = query.toLowerCase();
return persistedNotifications.filter(notif =>
notif.appName.toLowerCase().includes(searchLower) ||
notif.summary.toLowerCase().includes(searchLower) ||
notif.body.toLowerCase().includes(searchLower)
);
}
// Initialize persistence on component creation
Component.onCompleted: {
cleanupPersistentStorage();
}
}

View File

@@ -0,0 +1,190 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
// General notification settings
property bool notificationsEnabled: true
property bool soundEnabled: true
property bool persistNotifications: true
property int defaultTimeout: 5000 // milliseconds
// Grouping settings
property bool enableSmartGrouping: true
property bool autoExpandConversations: true
property bool replaceMediaNotifications: true
// Persistence settings
property int maxStoredNotifications: 100
property int notificationRetentionDays: 7
// Display settings
property bool showNotificationPopups: true
property bool showAppIcons: true
property bool showTimestamps: true
property bool enableInlineReply: true
property bool showActionButtons: true
// Priority settings
property bool allowCriticalNotifications: true
property bool respectDoNotDisturb: true
// App-specific settings
property var appSettings: ({})
// Do Not Disturb settings
property bool doNotDisturbMode: false
property string doNotDisturbStart: "22:00"
property string doNotDisturbEnd: "08:00"
property bool allowCriticalInDND: true
// Sound settings
property string notificationSound: "default"
property real soundVolume: 0.7
property bool vibrationEnabled: false
function getAppSetting(appName, setting, defaultValue) {
const app = appSettings[appName.toLowerCase()];
if (app && app.hasOwnProperty(setting)) {
return app[setting];
}
return defaultValue;
}
function setAppSetting(appName, setting, value) {
let newAppSettings = {};
for (const app in appSettings) {
newAppSettings[app] = appSettings[app];
}
const appKey = appName.toLowerCase();
if (!newAppSettings[appKey]) {
newAppSettings[appKey] = {};
}
newAppSettings[appKey][setting] = value;
appSettings = newAppSettings;
// Save to persistent storage
saveSettings();
}
function isAppBlocked(appName) {
return getAppSetting(appName, "blocked", false);
}
function isAppMuted(appName) {
return getAppSetting(appName, "muted", false);
}
function getAppTimeout(appName) {
return getAppSetting(appName, "timeout", defaultTimeout);
}
function isInDoNotDisturbMode() {
if (!doNotDisturbMode && !respectDoNotDisturb) {
return false;
}
const now = new Date();
const currentTime = now.getHours() * 60 + now.getMinutes();
const startParts = doNotDisturbStart.split(":");
const endParts = doNotDisturbEnd.split(":");
const startTime = parseInt(startParts[0]) * 60 + parseInt(startParts[1]);
const endTime = parseInt(endParts[0]) * 60 + parseInt(endParts[1]);
if (startTime <= endTime) {
// Same day range (e.g., 9:00 - 17:00)
return currentTime >= startTime && currentTime <= endTime;
} else {
// Overnight range (e.g., 22:00 - 08:00)
return currentTime >= startTime || currentTime <= endTime;
}
}
function shouldShowNotification(notification) {
// Check if notifications are globally disabled
if (!notificationsEnabled) {
return false;
}
// Check if app is blocked
if (isAppBlocked(notification.appName)) {
return false;
}
// Check Do Not Disturb mode
if (isInDoNotDisturbMode()) {
// Allow critical notifications if configured
if (allowCriticalInDND && notification.urgency === 2) {
return true;
}
return false;
}
return true;
}
function shouldPlaySound(notification) {
if (!soundEnabled) {
return false;
}
if (isAppMuted(notification.appName)) {
return false;
}
if (isInDoNotDisturbMode() && !allowCriticalInDND) {
return false;
}
return true;
}
function saveSettings() {
// In a real implementation, this would save to a config file
console.log("NotificationSettings: Settings saved");
}
function loadSettings() {
// In a real implementation, this would load from a config file
console.log("NotificationSettings: Settings loaded");
}
function resetToDefaults() {
notificationsEnabled = true;
soundEnabled = true;
persistNotifications = true;
defaultTimeout = 5000;
enableSmartGrouping = true;
autoExpandConversations = true;
replaceMediaNotifications = true;
maxStoredNotifications = 100;
notificationRetentionDays = 7;
showNotificationPopups = true;
showAppIcons = true;
showTimestamps = true;
enableInlineReply = true;
showActionButtons = true;
allowCriticalNotifications = true;
respectDoNotDisturb = true;
doNotDisturbMode = false;
doNotDisturbStart = "22:00";
doNotDisturbEnd = "08:00";
allowCriticalInDND = true;
notificationSound = "default";
soundVolume = 0.7;
vibrationEnabled = false;
appSettings = {};
saveSettings();
}
Component.onCompleted: {
loadSettings();
}
}