mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-13 00:42:49 -05:00
fix notification dismiss performance
This commit is contained in:
@@ -95,6 +95,8 @@ Singleton {
|
||||
property color error: currentThemeData.error || "#F2B8B5"
|
||||
property color warning: currentThemeData.warning || "#FF9800"
|
||||
property color info: currentThemeData.info || "#2196F3"
|
||||
property color tempWarning: "#ff9933"
|
||||
property color tempDanger: "#ff5555"
|
||||
|
||||
property color primaryHover: Qt.rgba(primary.r, primary.g, primary.b, 0.12)
|
||||
property color primaryHoverLight: Qt.rgba(primary.r, primary.g, primary.b, 0.08)
|
||||
|
||||
@@ -308,110 +308,26 @@ QtObject {
|
||||
if (!group)
|
||||
return
|
||||
|
||||
// Save current state for smart navigation
|
||||
const currentGroupKey = group.key
|
||||
const isNotification = currentItem.type === "notification"
|
||||
const notificationIndex = currentItem.notificationIndex
|
||||
const totalNotificationsInGroup = group.notifications ? group.notifications.length : 0
|
||||
const isLastNotificationInGroup = isNotification
|
||||
&& totalNotificationsInGroup === 1
|
||||
const isLastNotificationInList = isNotification
|
||||
&& notificationIndex === totalNotificationsInGroup - 1
|
||||
|
||||
// Store what to select next BEFORE clearing
|
||||
let nextTargetType = ""
|
||||
let nextTargetGroupKey = ""
|
||||
let nextTargetNotificationIndex = -1
|
||||
|
||||
if (currentItem.type === "group") {
|
||||
NotificationService.dismissGroup(group.key)
|
||||
|
||||
// Look for next group
|
||||
for (var i = currentItem.groupIndex + 1; i < groups.length; i++) {
|
||||
nextTargetType = "group"
|
||||
nextTargetGroupKey = groups[i].key
|
||||
break
|
||||
}
|
||||
|
||||
if (!nextTargetGroupKey && currentItem.groupIndex > 0) {
|
||||
nextTargetType = "group"
|
||||
nextTargetGroupKey = groups[currentItem.groupIndex - 1].key
|
||||
}
|
||||
} else if (isNotification) {
|
||||
const notification = group.notifications[notificationIndex]
|
||||
} else if (currentItem.type === "notification") {
|
||||
const notification = group.notifications[currentItem.notificationIndex]
|
||||
NotificationService.dismissNotification(notification)
|
||||
|
||||
if (isLastNotificationInGroup) {
|
||||
for (var i = currentItem.groupIndex + 1; i < groups.length; i++) {
|
||||
nextTargetType = "group"
|
||||
nextTargetGroupKey = groups[i].key
|
||||
break
|
||||
}
|
||||
|
||||
if (!nextTargetGroupKey && currentItem.groupIndex > 0) {
|
||||
nextTargetType = "group"
|
||||
nextTargetGroupKey = groups[currentItem.groupIndex - 1].key
|
||||
}
|
||||
} else if (isLastNotificationInList) {
|
||||
// If group still has notifications after this one is removed, select the new last one
|
||||
if (group.count > 1) {
|
||||
nextTargetType = "notification"
|
||||
nextTargetGroupKey = currentGroupKey
|
||||
// After removing current notification, the new last index will be count-2
|
||||
nextTargetNotificationIndex = group.count - 2
|
||||
} else {
|
||||
// Group will be empty or collapsed, select the group header
|
||||
nextTargetType = "group"
|
||||
nextTargetGroupKey = currentGroupKey
|
||||
nextTargetNotificationIndex = -1
|
||||
}
|
||||
} else {
|
||||
nextTargetType = "notification"
|
||||
nextTargetGroupKey = currentGroupKey
|
||||
nextTargetNotificationIndex = notificationIndex
|
||||
}
|
||||
}
|
||||
|
||||
rebuildFlatNavigation()
|
||||
|
||||
// Find and select the target we identified
|
||||
if (flatNavigation.length === 0) {
|
||||
selectedFlatIndex = 0
|
||||
updateSelectedIdFromIndex()
|
||||
} else if (nextTargetGroupKey) {
|
||||
let found = false
|
||||
for (var i = 0; i < flatNavigation.length; i++) {
|
||||
const item = flatNavigation[i]
|
||||
|
||||
if (nextTargetType === "group" && item.type === "group"
|
||||
&& item.groupKey === nextTargetGroupKey) {
|
||||
selectedFlatIndex = i
|
||||
found = true
|
||||
break
|
||||
} else if (nextTargetType === "notification"
|
||||
&& item.type === "notification"
|
||||
&& item.groupKey === nextTargetGroupKey
|
||||
&& item.notificationIndex === nextTargetNotificationIndex) {
|
||||
selectedFlatIndex = i
|
||||
found = true
|
||||
break
|
||||
keyboardNavigationActive = false
|
||||
if (listView) {
|
||||
listView.keyboardActive = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex,
|
||||
flatNavigation.length - 1)
|
||||
}
|
||||
|
||||
updateSelectedIdFromIndex()
|
||||
} else {
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex,
|
||||
flatNavigation.length - 1)
|
||||
selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1)
|
||||
updateSelectedIdFromIndex()
|
||||
}
|
||||
|
||||
ensureVisible()
|
||||
}
|
||||
}
|
||||
|
||||
function ensureVisible() {
|
||||
if (flatNavigation.length === 0
|
||||
|
||||
@@ -25,6 +25,17 @@ Singleton {
|
||||
property int seqCounter: 0
|
||||
property bool bulkDismissing: false
|
||||
|
||||
property var _dismissQueue: []
|
||||
property int _dismissBatchSize: 8
|
||||
property int _dismissTickMs: 8
|
||||
property bool _suspendGrouping: false
|
||||
property var _groupCache: ({"notifications": [], "popups": []})
|
||||
property bool _groupsDirty: false
|
||||
|
||||
Component.onCompleted: {
|
||||
_recomputeGroups()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: addGate
|
||||
interval: enterAnimMs + 50
|
||||
@@ -47,11 +58,41 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: dismissPump
|
||||
interval: _dismissTickMs
|
||||
repeat: true
|
||||
running: false
|
||||
onTriggered: {
|
||||
let n = Math.min(_dismissBatchSize, _dismissQueue.length)
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const w = _dismissQueue.pop()
|
||||
try {
|
||||
if (w && w.notification) w.notification.dismiss()
|
||||
} catch (e) {}
|
||||
}
|
||||
if (_dismissQueue.length === 0) {
|
||||
dismissPump.stop()
|
||||
_suspendGrouping = false
|
||||
bulkDismissing = false
|
||||
popupsDisabled = false
|
||||
_recomputeGroupsLater()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: groupsDebounce
|
||||
interval: 16
|
||||
repeat: false
|
||||
onTriggered: _recomputeGroups()
|
||||
}
|
||||
|
||||
property bool timeUpdateTick: false
|
||||
property bool clockFormatChanged: false
|
||||
|
||||
readonly property var groupedNotifications: getGroupedNotifications()
|
||||
readonly property var groupedPopups: getGroupedPopups()
|
||||
readonly property var groupedNotifications: _groupCache.notifications
|
||||
readonly property var groupedPopups: _groupCache.popups
|
||||
|
||||
property var expandedGroups: ({})
|
||||
property var expandedMessages: ({})
|
||||
@@ -89,6 +130,8 @@ Singleton {
|
||||
processQueue()
|
||||
}
|
||||
}
|
||||
|
||||
_recomputeGroupsLater()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,12 +260,8 @@ Singleton {
|
||||
target: wrapper.notification.Retainable
|
||||
|
||||
function onDropped(): void {
|
||||
const notifIndex = root.notifications.indexOf(wrapper)
|
||||
const allIndex = root.allWrappers.indexOf(wrapper)
|
||||
if (allIndex !== -1)
|
||||
root.allWrappers.splice(allIndex, 1)
|
||||
if (notifIndex !== -1)
|
||||
root.notifications.splice(notifIndex, 1)
|
||||
root.allWrappers = root.allWrappers.filter(w => w !== wrapper)
|
||||
root.notifications = root.notifications.filter(w => w !== wrapper)
|
||||
|
||||
if (root.bulkDismissing)
|
||||
return
|
||||
@@ -236,6 +275,7 @@ Singleton {
|
||||
}
|
||||
|
||||
cleanupExpansionStates()
|
||||
root._recomputeGroupsLater()
|
||||
}
|
||||
|
||||
function onAboutToDestroy(): void {
|
||||
@@ -256,30 +296,20 @@ Singleton {
|
||||
addGateBusy = false
|
||||
notificationQueue = []
|
||||
|
||||
for (const w of visibleNotifications)
|
||||
for (const w of allWrappers)
|
||||
w.popup = false
|
||||
visibleNotifications = []
|
||||
|
||||
const toDismiss = notifications.slice()
|
||||
|
||||
_dismissQueue = notifications.slice()
|
||||
if (notifications.length)
|
||||
notifications.splice(0, notifications.length)
|
||||
notifications = []
|
||||
expandedGroups = {}
|
||||
expandedMessages = {}
|
||||
|
||||
for (var i = 0; i < toDismiss.length; ++i) {
|
||||
const w = toDismiss[i]
|
||||
if (w && w.notification) {
|
||||
try {
|
||||
w.notification.dismiss()
|
||||
} catch (e) {
|
||||
_suspendGrouping = true
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bulkDismissing = false
|
||||
popupsDisabled = false
|
||||
if (!dismissPump.running && _dismissQueue.length)
|
||||
dismissPump.start()
|
||||
}
|
||||
|
||||
function dismissNotification(wrapper) {
|
||||
@@ -293,10 +323,10 @@ Singleton {
|
||||
popupsDisabled = disable
|
||||
if (disable) {
|
||||
notificationQueue = []
|
||||
visibleNotifications = []
|
||||
for (const notif of root.allWrappers) {
|
||||
for (const notif of visibleNotifications) {
|
||||
notif.popup = false
|
||||
}
|
||||
visibleNotifications = []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,32 +355,20 @@ Singleton {
|
||||
}
|
||||
|
||||
function removeFromVisibleNotifications(wrapper) {
|
||||
const i = visibleNotifications.findIndex(n => n === wrapper)
|
||||
if (i !== -1) {
|
||||
const v = [...visibleNotifications]
|
||||
v.splice(i, 1)
|
||||
visibleNotifications = v
|
||||
visibleNotifications = visibleNotifications.filter(n => n !== wrapper)
|
||||
processQueue()
|
||||
}
|
||||
}
|
||||
|
||||
function releaseWrapper(w) {
|
||||
let v = visibleNotifications.slice()
|
||||
const vi = v.indexOf(w)
|
||||
if (vi !== -1) {
|
||||
v.splice(vi, 1)
|
||||
visibleNotifications = v
|
||||
}
|
||||
|
||||
let q = notificationQueue.slice()
|
||||
const qi = q.indexOf(w)
|
||||
if (qi !== -1) {
|
||||
q.splice(qi, 1)
|
||||
notificationQueue = q
|
||||
}
|
||||
visibleNotifications = visibleNotifications.filter(n => n !== w)
|
||||
notificationQueue = notificationQueue.filter(n => n !== w)
|
||||
|
||||
if (w && w.destroy && !w.isPersistent) {
|
||||
Qt.callLater(() => {
|
||||
try {
|
||||
w.destroy()
|
||||
} catch (e) {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,7 +380,25 @@ Singleton {
|
||||
return wrapper.appName.toLowerCase()
|
||||
}
|
||||
|
||||
function getGroupedNotifications() {
|
||||
function _recomputeGroups() {
|
||||
if (_suspendGrouping) {
|
||||
_groupsDirty = true
|
||||
return
|
||||
}
|
||||
_groupCache = {
|
||||
"notifications": _calcGroupedNotifications(),
|
||||
"popups": _calcGroupedPopups()
|
||||
}
|
||||
_groupsDirty = false
|
||||
}
|
||||
|
||||
function _recomputeGroupsLater() {
|
||||
_groupsDirty = true
|
||||
if (!groupsDebounce.running)
|
||||
groupsDebounce.start()
|
||||
}
|
||||
|
||||
function _calcGroupedNotifications() {
|
||||
const groups = {}
|
||||
|
||||
for (const notif of notifications) {
|
||||
@@ -400,7 +436,7 @@ Singleton {
|
||||
})
|
||||
}
|
||||
|
||||
function getGroupedPopups() {
|
||||
function _calcGroupedPopups() {
|
||||
const groups = {}
|
||||
|
||||
for (const notif of popups) {
|
||||
|
||||
207
spam-notifications.sh
Executable file
207
spam-notifications.sh
Executable file
@@ -0,0 +1,207 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Notification Spam Test Script - Sends 100 rapid notifications from fake apps
|
||||
|
||||
echo "NOTIFICATION SPAM TEST - 100 RAPID NOTIFICATIONS"
|
||||
echo "============================================================="
|
||||
echo "WARNING: This will send 100 notifications very quickly!"
|
||||
echo "Press Ctrl+C to cancel, or wait 3 seconds to continue..."
|
||||
sleep 3
|
||||
|
||||
# Arrays of fake app names and icons
|
||||
APPS=(
|
||||
"slack:mail-message-new"
|
||||
"discord:internet-chat"
|
||||
"teams:call-start"
|
||||
"zoom:camera-video"
|
||||
"spotify:audio-x-generic"
|
||||
"chrome:web-browser"
|
||||
"firefox:web-browser"
|
||||
"vscode:text-editor"
|
||||
"terminal:utilities-terminal"
|
||||
"steam:applications-games"
|
||||
"telegram:internet-chat"
|
||||
"whatsapp:phone"
|
||||
"signal:security-high"
|
||||
"thunderbird:mail-client"
|
||||
"calendar:office-calendar"
|
||||
"notes:text-editor"
|
||||
"todo:emblem-default"
|
||||
"weather:weather-few-clouds"
|
||||
"news:rss"
|
||||
"reddit:web-browser"
|
||||
"twitter:internet-web-browser"
|
||||
"instagram:camera-photo"
|
||||
"youtube:video-x-generic"
|
||||
"netflix:media-playback-start"
|
||||
"github:folder-development"
|
||||
"gitlab:folder-development"
|
||||
"jira:applications-office"
|
||||
"notion:text-editor"
|
||||
"obsidian:accessories-text-editor"
|
||||
"dropbox:folder-remote"
|
||||
"gdrive:folder-google-drive"
|
||||
"onedrive:folder-cloud"
|
||||
"backup:drive-harddisk"
|
||||
"antivirus:security-high"
|
||||
"vpn:network-vpn"
|
||||
"torrent:network-server"
|
||||
"docker:application-x-executable"
|
||||
"kubernetes:applications-system"
|
||||
"postgres:database"
|
||||
"mongodb:database"
|
||||
"redis:database"
|
||||
"nginx:network-server"
|
||||
"apache:network-server"
|
||||
"jenkins:applications-development"
|
||||
"gradle:applications-development"
|
||||
"maven:applications-development"
|
||||
"npm:package-x-generic"
|
||||
"yarn:package-x-generic"
|
||||
"pip:package-x-generic"
|
||||
"apt:system-software-install"
|
||||
)
|
||||
|
||||
# Arrays of message types
|
||||
TITLES=(
|
||||
"New message"
|
||||
"Update available"
|
||||
"Download complete"
|
||||
"Task finished"
|
||||
"Build successful"
|
||||
"Deployment complete"
|
||||
"Sync complete"
|
||||
"Backup finished"
|
||||
"Security alert"
|
||||
"New notification"
|
||||
"Process complete"
|
||||
"Upload finished"
|
||||
"Connection established"
|
||||
"Meeting starting"
|
||||
"Reminder"
|
||||
"Warning"
|
||||
"Error occurred"
|
||||
"Success"
|
||||
"Failed"
|
||||
"Pending"
|
||||
"In progress"
|
||||
"Scheduled"
|
||||
"New activity"
|
||||
"Status update"
|
||||
"Alert"
|
||||
"Information"
|
||||
"Breaking news"
|
||||
"Hot update"
|
||||
"Trending"
|
||||
"New release"
|
||||
)
|
||||
|
||||
MESSAGES=(
|
||||
"Your request has been processed successfully"
|
||||
"New content is available for download"
|
||||
"Operation completed without errors"
|
||||
"Check your inbox for updates"
|
||||
"3 new items require your attention"
|
||||
"Background task finished executing"
|
||||
"All systems operational"
|
||||
"Performance metrics updated"
|
||||
"Configuration saved successfully"
|
||||
"Database connection established"
|
||||
"Cache cleared and rebuilt"
|
||||
"Service restarted automatically"
|
||||
"Logs have been rotated"
|
||||
"Memory usage optimized"
|
||||
"Network latency improved"
|
||||
"Security scan completed - no threats"
|
||||
"Automatic backup created"
|
||||
"Files synchronized across devices"
|
||||
"Updates installed successfully"
|
||||
"New features are now available"
|
||||
"Your subscription has been renewed"
|
||||
"Report generated and ready"
|
||||
"Analysis complete - view results"
|
||||
"Queue processed: 42 items"
|
||||
"Rate limit will reset in 5 minutes"
|
||||
"API call successful (200 OK)"
|
||||
"Webhook delivered successfully"
|
||||
"Container started on port 8080"
|
||||
"Build artifact uploaded"
|
||||
"Test suite passed: 100/100"
|
||||
"Coverage report: 95%"
|
||||
"Dependencies updated to latest"
|
||||
"Migration completed successfully"
|
||||
"Index rebuilt for faster queries"
|
||||
"SSL certificate renewed"
|
||||
"Firewall rules updated"
|
||||
"DNS propagation complete"
|
||||
"CDN cache purged globally"
|
||||
"Load balancer health check: OK"
|
||||
"Cluster scaled to 5 nodes"
|
||||
)
|
||||
|
||||
# Urgency levels
|
||||
URGENCY=("low" "normal")
|
||||
|
||||
# Counter
|
||||
COUNT=0
|
||||
TOTAL=100
|
||||
|
||||
echo ""
|
||||
echo "Starting notification spam..."
|
||||
echo "------------------------------"
|
||||
|
||||
# Send notifications rapidly
|
||||
for i in $(seq 1 $TOTAL); do
|
||||
# Pick random app, title, message, and urgency
|
||||
APP=${APPS[$RANDOM % ${#APPS[@]}]}
|
||||
APP_NAME=${APP%%:*}
|
||||
APP_ICON=${APP#*:}
|
||||
TITLE=${TITLES[$RANDOM % ${#TITLES[@]}]}
|
||||
MESSAGE=${MESSAGES[$RANDOM % ${#MESSAGES[@]}]}
|
||||
URG=${URGENCY[$RANDOM % ${#URGENCY[@]}]}
|
||||
|
||||
# Add some variety with random numbers and timestamps
|
||||
RAND_NUM=$((RANDOM % 1000))
|
||||
TIMESTAMP=$(date +"%H:%M:%S")
|
||||
|
||||
# Randomly add extra details to some messages
|
||||
if [ $((RANDOM % 3)) -eq 0 ]; then
|
||||
MESSAGE="[$TIMESTAMP] $MESSAGE (#$RAND_NUM)"
|
||||
fi
|
||||
|
||||
# Send notification with very short delay
|
||||
notify-send \
|
||||
-h string:desktop-entry:$APP_NAME \
|
||||
-i $APP_ICON \
|
||||
-u $URG \
|
||||
"$APP_NAME: $TITLE" \
|
||||
"$MESSAGE" &
|
||||
|
||||
# Increment counter
|
||||
COUNT=$((COUNT + 1))
|
||||
|
||||
# Show progress every 10 notifications
|
||||
if [ $((COUNT % 10)) -eq 0 ]; then
|
||||
echo " Sent $COUNT/$TOTAL notifications..."
|
||||
fi
|
||||
|
||||
# Tiny delay to prevent complete system freeze
|
||||
# Adjust this value: smaller = faster spam, larger = slower spam
|
||||
sleep 0.01
|
||||
done
|
||||
|
||||
# Wait for all background notifications to complete
|
||||
wait
|
||||
|
||||
echo ""
|
||||
echo "Spam test complete!"
|
||||
echo "============================================================="
|
||||
echo "Statistics:"
|
||||
echo " Total notifications sent: $TOTAL"
|
||||
echo " Apps simulated: ${#APPS[@]}"
|
||||
echo " Message variations: ${#MESSAGES[@]}"
|
||||
echo " Time taken: ~$(($TOTAL / 100)) seconds"
|
||||
echo ""
|
||||
echo "Check your notification center - it should be FULL!"
|
||||
echo "Tip: You may want to clear all notifications after this test"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user