1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-28 07:22:50 -05:00

replace qmlformat with a better tool

still not perfect, but well - what can ya do
This commit is contained in:
bbedward
2025-08-08 15:55:37 -04:00
parent 8dc6e2805d
commit 4d408c65f2
137 changed files with 30315 additions and 29625 deletions

View File

@@ -1,5 +1,6 @@
pragma Singleton
pragma ComponentBehavior: Bound
pragma ComponentBehavior
import QtQuick
import Quickshell
@@ -10,430 +11,449 @@ import qs.Common
import "../Common/markdown2html.js" as Markdown2Html
Singleton {
id: root
id: root
readonly property list<NotifWrapper> notifications: []
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
property int seqCounter: 0
property bool bulkDismissing: false
readonly property list<NotifWrapper> notifications: []
readonly property list<NotifWrapper> allWrappers: []
readonly property list<NotifWrapper> popups: allWrappers.filter(n => n.popup)
Timer {
id: addGate
interval: enterAnimMs + 50
running: false
repeat: false
onTriggered: { addGateBusy = false; processQueue(); }
property list<NotifWrapper> notificationQueue: []
property list<NotifWrapper> visibleNotifications: []
property int maxVisibleNotifications: 3
property bool addGateBusy: false
property int enterAnimMs: 400
property int seqCounter: 0
property bool bulkDismissing: false
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()
property var expandedGroups: ({})
property var expandedMessages: ({})
property bool popupsDisabled: false
}
NotificationServer {
id: server
// Android 16-style grouped notifications
readonly property var groupedNotifications: getGroupedNotifications()
readonly property var groupedPopups: getGroupedPopups()
keepOnReload: false
actionsSupported: true
actionIconsSupported: true
bodyHyperlinksSupported: true
bodyImagesSupported: true
bodyMarkupSupported: true
imageSupported: true
inlineReplySupported: true
property var expandedGroups: ({})
property var expandedMessages: ({})
property bool popupsDisabled: false
onNotification: notif => {
notif.tracked = true;
NotificationServer {
id: server
const shouldShowPopup = !root.popupsDisabled && !SessionData.doNotDisturb;
const wrapper = notifComponent.createObject(root, {
popup: shouldShowPopup,
notification: notif
});
keepOnReload: false
actionsSupported: true
actionIconsSupported: true
bodyHyperlinksSupported: true
bodyImagesSupported: true
bodyMarkupSupported: true
imageSupported: true
inlineReplySupported: true
if (wrapper) {
root.allWrappers.push(wrapper);
root.notifications.push(wrapper);
if (shouldShowPopup) {
notificationQueue = [...notificationQueue, wrapper];
processQueue();
}
}
onNotification: notif => {
notif.tracked = true
const shouldShowPopup = !root.popupsDisabled && !SessionData.doNotDisturb
const wrapper = notifComponent.createObject(root, {
"popup": shouldShowPopup,
"notification": notif
})
if (wrapper) {
root.allWrappers.push(wrapper)
root.notifications.push(wrapper)
if (shouldShowPopup) {
notificationQueue = [...notificationQueue, wrapper]
processQueue()
}
}
}
}
component NotifWrapper: QtObject {
id: wrapper
property bool popup: false
property bool removedByLimit: false
property bool isPersistent: true
property int seq: 0
onPopupChanged: {
if (!popup) {
removeFromVisibleNotifications(wrapper)
}
}
component NotifWrapper: QtObject {
id: wrapper
readonly property Timer timer: Timer {
interval: 5000
repeat: false
running: false
onTriggered: {
wrapper.popup = false
}
}
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)
property bool popup: false
property bool removedByLimit: false
property bool isPersistent: true
property int seq: 0
onPopupChanged: {
if (!popup) {
removeFromVisibleNotifications(wrapper);
}
}
readonly property Timer timer: Timer {
interval: 5000
repeat: false
running: false
onTriggered: {
wrapper.popup = false;
}
}
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 htmlBody: {
if (body && (body.includes('<') && body.includes('>'))) {
return body;
}
return Markdown2Html.markdownToHtml(body);
}
readonly property string appIcon: notification.appIcon
readonly property string appName: {
if (notification.appName == "") {
// try to get the app name from the desktop entry
const entry = DesktopEntries.byId(notification.desktopEntry);
if (entry && entry.name) {
return entry.name.toLowerCase();
}
}
return notification.appName || "app";
}
readonly property string desktopEntry: notification.desktopEntry
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 list<NotificationAction> actions: notification.actions
readonly property bool hasImage: image && image.length > 0
readonly property bool hasAppIcon: appIcon && appIcon.length > 0
readonly property Connections conn: Connections {
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);
if (root.bulkDismissing) return;
const groupKey = getGroupKey(wrapper);
const remainingInGroup = root.notifications.filter(n => getGroupKey(n) === groupKey);
if (remainingInGroup.length <= 1) {
clearGroupExpansionState(groupKey);
}
cleanupExpansionStates();
}
function onAboutToDestroy(): void {
wrapper.destroy();
}
}
if (h < 1 && m < 1)
return "now"
if (h < 1)
return `${m}m`
return `${h}h`
}
Component {
id: notifComponent
NotifWrapper {}
required property Notification notification
readonly property string summary: notification.summary
readonly property string body: notification.body
readonly property string htmlBody: {
if (body && (body.includes('<') && body.includes('>'))) {
return body
}
return Markdown2Html.markdownToHtml(body)
}
readonly property string appIcon: notification.appIcon
readonly property string appName: {
if (notification.appName == "") {
// try to get the app name from the desktop entry
const entry = DesktopEntries.byId(notification.desktopEntry)
if (entry && entry.name) {
return entry.name.toLowerCase()
}
}
return notification.appName || "app"
}
readonly property string desktopEntry: notification.desktopEntry
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 list<NotificationAction> actions: notification.actions
readonly property bool hasImage: image && image.length > 0
readonly property bool hasAppIcon: appIcon && appIcon.length > 0
readonly property Connections conn: Connections {
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)
if (root.bulkDismissing)
return
const groupKey = getGroupKey(wrapper)
const remainingInGroup = root.notifications.filter(n => getGroupKey(
n) === groupKey)
if (remainingInGroup.length <= 1) {
clearGroupExpansionState(groupKey)
}
cleanupExpansionStates()
}
function onAboutToDestroy(): void {
wrapper.destroy()
}
}
}
Component {
id: notifComponent
NotifWrapper {}
}
function clearAllNotifications() {
bulkDismissing = true
popupsDisabled = true
addGate.stop()
addGateBusy = false
notificationQueue = []
for (const w of visibleNotifications)
w.popup = false
visibleNotifications = []
const toDismiss = notifications.slice()
if (notifications.length)
notifications.splice(0, notifications.length)
expandedGroups = {}
expandedMessages = {}
for (var i = 0; i < toDismiss.length; ++i) {
const w = toDismiss[i]
if (w && w.notification) {
try {
w.notification.dismiss()
} catch (e) {
}
}
}
function clearAllNotifications() {
bulkDismissing = true;
popupsDisabled = true;
addGate.stop();
addGateBusy = false;
notificationQueue = [];
bulkDismissing = false
popupsDisabled = false
}
for (const w of visibleNotifications) w.popup = false;
visibleNotifications = [];
function dismissNotification(wrapper) {
if (!wrapper || !wrapper.notification)
return
wrapper.popup = false
wrapper.notification.dismiss()
}
const toDismiss = notifications.slice();
function disablePopups(disable) {
popupsDisabled = disable
if (disable) {
notificationQueue = []
visibleNotifications = []
for (const notif of root.allWrappers) {
notif.popup = false
}
}
}
if (notifications.length) notifications.splice(0, notifications.length);
expandedGroups = {};
expandedMessages = {};
function processQueue() {
if (addGateBusy)
return
if (popupsDisabled)
return
if (SessionData.doNotDisturb)
return
if (notificationQueue.length === 0)
return
for (let i = 0; i < toDismiss.length; ++i) {
const w = toDismiss[i];
if (w && w.notification) {
try { w.notification.dismiss(); } catch (e) {}
}
}
const next = notificationQueue.shift()
bulkDismissing = false;
popupsDisabled = false;
next.seq = ++seqCounter
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()
}
}
function releaseWrapper(w) {
// Remove from visible
let v = visibleNotifications.slice()
const vi = v.indexOf(w)
if (vi !== -1) {
v.splice(vi, 1)
visibleNotifications = v
}
function dismissNotification(wrapper) {
if (!wrapper || !wrapper.notification) return;
wrapper.popup = false;
wrapper.notification.dismiss();
}
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 (SessionData.doNotDisturb) return;
if (notificationQueue.length === 0) return;
const [next, ...rest] = notificationQueue;
notificationQueue = rest;
next.seq = ++seqCounter;
visibleNotifications = [...visibleNotifications, next];
next.popup = true;
addGateBusy = true;
addGate.restart();
// Remove from queue
let q = notificationQueue.slice()
const qi = q.indexOf(w)
if (qi !== -1) {
q.splice(qi, 1)
notificationQueue = q
}
function removeFromVisibleNotifications(wrapper) {
const i = visibleNotifications.findIndex(n => n === wrapper);
if (i !== -1) {
const v = [...visibleNotifications]; v.splice(i, 1);
visibleNotifications = v;
processQueue();
}
// Destroy wrapper if non-persistent
if (w && w.destroy && !w.isPersistent) {
w.destroy()
}
}
// Android 16-style notification grouping functions
function getGroupKey(wrapper) {
// Priority 1: Use desktopEntry if available
if (wrapper.desktopEntry && wrapper.desktopEntry !== "") {
return wrapper.desktopEntry.toLowerCase()
}
function releaseWrapper(w) {
// Remove from visible
let v = visibleNotifications.slice();
const vi = v.indexOf(w);
if (vi !== -1) {
v.splice(vi, 1);
visibleNotifications = v;
}
// Priority 2: Use appName as fallback
return wrapper.appName.toLowerCase()
}
// Remove from queue
let q = notificationQueue.slice();
const qi = q.indexOf(w);
if (qi !== -1) {
q.splice(qi, 1);
notificationQueue = q;
}
function getGroupedNotifications() {
const groups = {}
// Destroy wrapper if non-persistent
if (w && w.destroy && !w.isPersistent) {
w.destroy();
for (const notif of notifications) {
const groupKey = getGroupKey(notif)
if (!groups[groupKey]) {
groups[groupKey] = {
"key": groupKey,
"appName": notif.appName,
"notifications": [],
"latestNotification": null,
"count": 0,
"hasInlineReply": false
}
}
groups[groupKey].notifications.unshift(notif)
groups[groupKey].latestNotification = groups[groupKey].notifications[0]
groups[groupKey].count = groups[groupKey].notifications.length
if (notif.notification.hasInlineReply) {
groups[groupKey].hasInlineReply = true
}
}
return Object.values(groups).sort((a, b) => {
const aUrgency = a.latestNotification.urgency
|| NotificationUrgency.Low
const bUrgency = b.latestNotification.urgency
|| NotificationUrgency.Low
if (aUrgency !== bUrgency) {
return bUrgency - aUrgency
}
return b.latestNotification.time.getTime(
) - a.latestNotification.time.getTime(
)
})
}
// Android 16-style notification grouping functions
function getGroupKey(wrapper) {
// Priority 1: Use desktopEntry if available
if (wrapper.desktopEntry && wrapper.desktopEntry !== "") {
return wrapper.desktopEntry.toLowerCase();
function getGroupedPopups() {
const groups = {}
for (const notif of popups) {
const groupKey = getGroupKey(notif)
if (!groups[groupKey]) {
groups[groupKey] = {
"key": groupKey,
"appName": notif.appName,
"notifications": [],
"latestNotification": null,
"count": 0,
"hasInlineReply": false
}
// Priority 2: Use appName as fallback
return wrapper.appName.toLowerCase();
}
groups[groupKey].notifications.unshift(notif)
groups[groupKey].latestNotification = groups[groupKey].notifications[0]
groups[groupKey].count = groups[groupKey].notifications.length
if (notif.notification.hasInlineReply) {
groups[groupKey].hasInlineReply = true
}
}
function getGroupedNotifications() {
const groups = {};
for (const notif of notifications) {
const groupKey = getGroupKey(notif);
if (!groups[groupKey]) {
groups[groupKey] = {
key: groupKey,
appName: notif.appName,
notifications: [],
latestNotification: null,
count: 0,
hasInlineReply: false,
};
}
groups[groupKey].notifications.unshift(notif);
groups[groupKey].latestNotification = groups[groupKey].notifications[0];
groups[groupKey].count = groups[groupKey].notifications.length;
if (notif.notification.hasInlineReply) {
groups[groupKey].hasInlineReply = true;
}
}
return Object.values(groups).sort((a, b) => {
const aUrgency = a.latestNotification.urgency || NotificationUrgency.Low;
const bUrgency = b.latestNotification.urgency || NotificationUrgency.Low;
if (aUrgency !== bUrgency) {
return bUrgency - aUrgency;
}
return b.latestNotification.time.getTime() - a.latestNotification.time.getTime();
});
}
return Object.values(groups).sort((a, b) => {
return b.latestNotification.time.getTime(
) - a.latestNotification.time.getTime(
)
})
}
function getGroupedPopups() {
const groups = {};
for (const notif of popups) {
const groupKey = getGroupKey(notif);
if (!groups[groupKey]) {
groups[groupKey] = {
key: groupKey,
appName: notif.appName,
notifications: [],
latestNotification: null,
count: 0,
hasInlineReply: false,
};
}
groups[groupKey].notifications.unshift(notif);
groups[groupKey].latestNotification = groups[groupKey].notifications[0];
groups[groupKey].count = groups[groupKey].notifications.length;
if (notif.notification.hasInlineReply) {
groups[groupKey].hasInlineReply = true;
}
}
return Object.values(groups).sort((a, b) => {
return b.latestNotification.time.getTime() - a.latestNotification.time.getTime();
});
function toggleGroupExpansion(groupKey) {
let newExpandedGroups = {}
for (const key in expandedGroups) {
newExpandedGroups[key] = expandedGroups[key]
}
newExpandedGroups[groupKey] = !newExpandedGroups[groupKey]
expandedGroups = newExpandedGroups
}
function toggleGroupExpansion(groupKey) {
let newExpandedGroups = {};
for (const key in expandedGroups) {
newExpandedGroups[key] = expandedGroups[key];
function dismissGroup(groupKey) {
const group = groupedNotifications.find(g => g.key === groupKey)
if (group) {
for (const notif of group.notifications) {
if (notif && notif.notification) {
notif.notification.dismiss()
}
newExpandedGroups[groupKey] = !newExpandedGroups[groupKey];
expandedGroups = newExpandedGroups;
}
} else {
for (const notif of allWrappers) {
if (notif && notif.notification && getGroupKey(notif) === groupKey) {
notif.notification.dismiss()
}
}
}
}
function dismissGroup(groupKey) {
const group = groupedNotifications.find(g => g.key === groupKey);
if (group) {
for (const notif of group.notifications) {
if (notif && notif.notification) {
notif.notification.dismiss();
}
}
} else {
for (const notif of allWrappers) {
if (notif && notif.notification && getGroupKey(notif) === groupKey) {
notif.notification.dismiss();
}
}
}
function clearGroupExpansionState(groupKey) {
let newExpandedGroups = {}
for (const key in expandedGroups) {
if (key !== groupKey && expandedGroups[key]) {
newExpandedGroups[key] = true
}
}
function clearGroupExpansionState(groupKey) {
let newExpandedGroups = {};
for (const key in expandedGroups) {
if (key !== groupKey && expandedGroups[key]) {
newExpandedGroups[key] = true;
}
}
expandedGroups = newExpandedGroups;
expandedGroups = newExpandedGroups
}
function cleanupExpansionStates() {
const currentGroupKeys = new Set(groupedNotifications.map(g => g.key))
const currentMessageIds = new Set()
for (const group of groupedNotifications) {
for (const notif of group.notifications) {
currentMessageIds.add(notif.notification.id)
}
}
function cleanupExpansionStates() {
const currentGroupKeys = new Set(groupedNotifications.map(g => g.key));
const currentMessageIds = new Set();
for (const group of groupedNotifications) {
for (const notif of group.notifications) {
currentMessageIds.add(notif.notification.id);
}
}
let newExpandedGroups = {};
for (const key in expandedGroups) {
if (currentGroupKeys.has(key) && expandedGroups[key]) {
newExpandedGroups[key] = true;
}
}
expandedGroups = newExpandedGroups;
let newExpandedMessages = {};
for (const messageId in expandedMessages) {
if (currentMessageIds.has(messageId) && expandedMessages[messageId]) {
newExpandedMessages[messageId] = true;
}
}
expandedMessages = newExpandedMessages;
let newExpandedGroups = {}
for (const key in expandedGroups) {
if (currentGroupKeys.has(key) && expandedGroups[key]) {
newExpandedGroups[key] = true
}
}
function toggleMessageExpansion(messageId) {
let newExpandedMessages = {};
for (const key in expandedMessages) {
newExpandedMessages[key] = expandedMessages[key];
}
newExpandedMessages[messageId] = !newExpandedMessages[messageId];
expandedMessages = newExpandedMessages;
expandedGroups = newExpandedGroups
let newExpandedMessages = {}
for (const messageId in expandedMessages) {
if (currentMessageIds.has(messageId) && expandedMessages[messageId]) {
newExpandedMessages[messageId] = true
}
}
expandedMessages = newExpandedMessages
}
Connections {
target: SessionData
function onDoNotDisturbChanged() {
if (SessionData.doNotDisturb) {
// Hide all current popups when DND is enabled
for (const notif of visibleNotifications) {
notif.popup = false;
}
visibleNotifications = [];
notificationQueue = [];
} else {
// Re-enable popup processing when DND is disabled
processQueue();
}
}
function toggleMessageExpansion(messageId) {
let newExpandedMessages = {}
for (const key in expandedMessages) {
newExpandedMessages[key] = expandedMessages[key]
}
newExpandedMessages[messageId] = !newExpandedMessages[messageId]
expandedMessages = newExpandedMessages
}
}
Connections {
target: SessionData
function onDoNotDisturbChanged() {
if (SessionData.doNotDisturb) {
// Hide all current popups when DND is enabled
for (const notif of visibleNotifications) {
notif.popup = false
}
visibleNotifications = []
notificationQueue = []
} else {
// Re-enable popup processing when DND is disabled
processQueue()
}
}
}
}