mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
notifications: initial refactory of popups
This commit is contained in:
File diff suppressed because it is too large
Load Diff
115
Modules/Notifications/NotificationPopupManager.qml
Normal file
115
Modules/Notifications/NotificationPopupManager.qml
Normal file
@@ -0,0 +1,115 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property list<var> popupInstances: []
|
||||
property int baseNotificationHeight: 130 // Height of a single notification + margins
|
||||
property bool dismissalInProgress: false
|
||||
property int maxTargetNotifications: 3 // Target number of notifications to maintain
|
||||
|
||||
Timer {
|
||||
id: dismissalTimer
|
||||
interval: 500 // Half second delay between dismissals
|
||||
onTriggered: dismissNextOldest()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: dismissalDelayTimer
|
||||
interval: 1 // Start immediately
|
||||
onTriggered: startSequentialDismissal()
|
||||
}
|
||||
|
||||
Component {
|
||||
id: popupComponent
|
||||
NotificationPopup {}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: NotificationService
|
||||
function onNotificationQueueChanged() {
|
||||
syncPopupsWithQueue();
|
||||
repositionAll();
|
||||
}
|
||||
function onVisibleNotificationsChanged() {
|
||||
repositionAll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function syncPopupsWithQueue() {
|
||||
const queue = NotificationService.notificationQueue.filter(n => n && n.popup);
|
||||
|
||||
// Clean up destroyed popups first
|
||||
popupInstances = popupInstances.filter(p => p && p.notificationId);
|
||||
|
||||
// DON'T aggressively destroy popups - let them handle their own lifecycle
|
||||
// Only remove popups that are actually destroyed/invalid
|
||||
// The popup will destroy itself when notificationData.popup becomes false
|
||||
|
||||
// Only create NEW notifications, don't touch existing ones AT ALL
|
||||
for (const notif of queue) {
|
||||
const existingPopup = popupInstances.find(p => p.notificationId === notif.notification.id);
|
||||
if (existingPopup) {
|
||||
// CRITICAL: Do absolutely NOTHING to existing popups
|
||||
// Don't change their verticalOffset, don't touch any properties
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate position for NEW notification only - at the bottom of ACTIVE stack
|
||||
const currentActive = popupInstances.filter(p => p && p.notificationData && p.notificationData.popup).length;
|
||||
const popup = popupComponent.createObject(root, {
|
||||
notificationData: notif,
|
||||
notificationId: notif.notification.id,
|
||||
verticalOffset: currentActive * baseNotificationHeight // ✅ bottom of active stack
|
||||
});
|
||||
|
||||
if (popup) {
|
||||
popupInstances.push(popup);
|
||||
|
||||
// Pin it until entrance finishes, then maybe start overflow
|
||||
popup.entered.connect(function() {
|
||||
repositionAll(); // it's now "stable"; allow vertical compaction
|
||||
maybeStartOverflow(); // defer overflow until after slot N is fully occupied
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Overflow dismissal handled in Connections now
|
||||
}
|
||||
|
||||
function repositionAll() {
|
||||
// Only compact stable (non-entering) active popups
|
||||
const stable = popupInstances.filter(p => p && p.notificationData && p.notificationData.popup && !p.entering);
|
||||
for (let i = 0; i < stable.length; ++i)
|
||||
stable[i].verticalOffset = i * baseNotificationHeight;
|
||||
|
||||
// Newcomers keep their creation-time offset (slot N) until `entered()`
|
||||
}
|
||||
|
||||
function maybeStartOverflow() {
|
||||
const active = popupInstances.filter(p => p && p.notificationData && p.notificationData.popup);
|
||||
if (active.length > maxTargetNotifications && !dismissalInProgress)
|
||||
startSequentialDismissal();
|
||||
}
|
||||
|
||||
function startSequentialDismissal() {
|
||||
if (dismissalInProgress) return; // Don't start multiple dismissals
|
||||
|
||||
dismissalInProgress = true;
|
||||
dismissNextOldest();
|
||||
}
|
||||
|
||||
function dismissNextOldest() {
|
||||
const active = popupInstances.filter(p => p && p.notificationData.popup);
|
||||
if (active.length <= maxTargetNotifications) { dismissalInProgress = false; return; }
|
||||
const oldest = active[0];
|
||||
if (oldest) {
|
||||
oldest.notificationData.removedByLimit = true;
|
||||
oldest.notificationData.popup = false; // triggers slide-out in popup
|
||||
dismissalTimer.restart(); // your existing 500ms is fine
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,20 @@ Singleton {
|
||||
readonly property list<NotifWrapper> allWrappers: []
|
||||
readonly property list<NotifWrapper> popups: allWrappers.filter(n => n.popup)
|
||||
|
||||
property list<NotifWrapper> notificationQueue: []
|
||||
property list<NotifWrapper> visibleNotifications: []
|
||||
property int maxVisibleNotifications: 3
|
||||
property bool addGateBusy: false
|
||||
property int enterAnimMs: 400
|
||||
|
||||
Timer {
|
||||
id: addGate
|
||||
interval: enterAnimMs + 50
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: { addGateBusy = false; processQueue(); }
|
||||
}
|
||||
|
||||
// Android 16-style grouped notifications
|
||||
readonly property var groupedNotifications: getGroupedNotifications()
|
||||
readonly property var groupedPopups: getGroupedPopups()
|
||||
@@ -39,29 +53,22 @@ Singleton {
|
||||
inlineReplySupported: true
|
||||
|
||||
onNotification: notif => {
|
||||
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("hasInlineReply:", notif.hasInlineReply);
|
||||
console.log("=============================");
|
||||
|
||||
notif.tracked = true;
|
||||
|
||||
const wrapper = notifComponent.createObject(root, {
|
||||
popup: true, // Always show as popup initially
|
||||
popup: !root.popupsDisabled,
|
||||
notification: notif
|
||||
});
|
||||
|
||||
if (wrapper) {
|
||||
const groupKey = getGroupKey(wrapper);
|
||||
|
||||
root.allWrappers.push(wrapper);
|
||||
root.notifications.push(wrapper);
|
||||
addToPersistentStorage(wrapper);
|
||||
|
||||
if (!root.popupsDisabled) {
|
||||
notificationQueue = [...notificationQueue, wrapper];
|
||||
processQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,15 +77,20 @@ Singleton {
|
||||
id: wrapper
|
||||
|
||||
property bool popup: false
|
||||
property bool removedByLimit: false
|
||||
|
||||
Component.onCompleted: {
|
||||
popup = !root.popupsDisabled;
|
||||
onPopupChanged: {
|
||||
if (!popup) {
|
||||
removeFromVisibleNotifications(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't override popup in onCompleted - it's set correctly during creation
|
||||
|
||||
readonly property Timer timer: Timer {
|
||||
interval: 5000
|
||||
repeat: false
|
||||
running: wrapper.popup
|
||||
running: false
|
||||
onTriggered: {
|
||||
wrapper.popup = false;
|
||||
}
|
||||
@@ -185,11 +197,37 @@ Singleton {
|
||||
function disablePopups(disable) {
|
||||
popupsDisabled = disable;
|
||||
if (disable) {
|
||||
notificationQueue = [];
|
||||
visibleNotifications = [];
|
||||
for (const notif of root.allWrappers) {
|
||||
notif.popup = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processQueue() {
|
||||
if (addGateBusy) return;
|
||||
if (popupsDisabled) return;
|
||||
if (notificationQueue.length === 0) return;
|
||||
|
||||
const [next, ...rest] = notificationQueue;
|
||||
notificationQueue = rest;
|
||||
|
||||
visibleNotifications = [...visibleNotifications, next];
|
||||
next.popup = true;
|
||||
|
||||
addGateBusy = true;
|
||||
addGate.restart();
|
||||
}
|
||||
|
||||
function removeFromVisibleNotifications(wrapper) {
|
||||
const i = visibleNotifications.findIndex(n => n === wrapper);
|
||||
if (i !== -1) {
|
||||
const v = [...visibleNotifications]; v.splice(i, 1);
|
||||
visibleNotifications = v;
|
||||
processQueue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Android 16-style notification grouping functions
|
||||
@@ -264,11 +302,6 @@ Singleton {
|
||||
}
|
||||
|
||||
return Object.values(groups).sort((a, b) => {
|
||||
const aUrgency = a.latestNotification.urgency || 0;
|
||||
const bUrgency = b.latestNotification.urgency || 0;
|
||||
if (aUrgency !== bUrgency) {
|
||||
return bUrgency - aUrgency;
|
||||
}
|
||||
return b.latestNotification.time.getTime() - a.latestNotification.time.getTime();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user