mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 21:45:38 -05:00
notification: general improvements to popups
This commit is contained in:
@@ -19,7 +19,7 @@ PanelWindow {
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
implicitWidth: 400
|
||||
implicitHeight: 116
|
||||
implicitHeight: 122
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
@@ -138,13 +138,12 @@ PanelWindow {
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 12
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
height: 86
|
||||
anchors.rightMargin: 56
|
||||
height: 98
|
||||
|
||||
Rectangle {
|
||||
id: iconContainer
|
||||
readonly property bool hasNotificationImage: notificationData && notificationData.image && notificationData.image !== ""
|
||||
readonly property bool appIconIsImage: notificationData && notificationData.appIcon && (notificationData.appIcon.startsWith("file://") || notificationData.appIcon.startsWith("http://") || notificationData.appIcon.startsWith("https://"))
|
||||
property alias iconImage: iconImage
|
||||
|
||||
width: 55
|
||||
@@ -196,89 +195,169 @@ PanelWindow {
|
||||
id: textContainer
|
||||
anchors.left: iconContainer.right
|
||||
anchors.leftMargin: 12
|
||||
anchors.right: closeButton.left
|
||||
anchors.rightMargin: 8
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 8
|
||||
color: "transparent"
|
||||
|
||||
Column {
|
||||
Item {
|
||||
width: parent.width
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: parent.height
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -4
|
||||
|
||||
Text {
|
||||
Column {
|
||||
width: parent.width
|
||||
text: {
|
||||
if (!notificationData) return "";
|
||||
const appName = notificationData.appName || "";
|
||||
const timeStr = notificationData.timeStr || "";
|
||||
if (timeStr.length > 0)
|
||||
return appName + " • " + timeStr;
|
||||
else
|
||||
return appName;
|
||||
}
|
||||
color: Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
text: notificationData ? (notificationData.summary || "") : ""
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
Text {
|
||||
property bool hasUrls: {
|
||||
if (!notificationData || !notificationData.body) return false;
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
return urlRegex.test(notificationData.body);
|
||||
Text {
|
||||
width: parent.width
|
||||
text: {
|
||||
if (!notificationData) return "";
|
||||
const appName = notificationData.appName || "";
|
||||
const timeStr = notificationData.timeStr || "";
|
||||
if (timeStr.length > 0)
|
||||
return appName + " • " + timeStr;
|
||||
else
|
||||
return appName;
|
||||
}
|
||||
color: Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
text: {
|
||||
if (!notificationData || !notificationData.body) return "";
|
||||
let bodyText = notificationData.body;
|
||||
if (bodyText.length > 105)
|
||||
bodyText = bodyText.substring(0, 102) + "...";
|
||||
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
return bodyText.replace(urlRegex, '<a href="$1" style="color: ' + Theme.primary + '; text-decoration: underline;">$1</a>');
|
||||
Text {
|
||||
text: notificationData ? (notificationData.summary || "") : ""
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
visible: text.length > 0
|
||||
}
|
||||
color: Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.WordWrap
|
||||
visible: text.length > 0
|
||||
textFormat: Text.RichText
|
||||
onLinkActivated: function(link) {
|
||||
Qt.openUrlExternally(link);
|
||||
|
||||
Text {
|
||||
text: notificationData ? (notificationData.body || "") : ""
|
||||
color: Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.WordWrap
|
||||
visible: text.length > 0
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: closeButton
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
iconName: "close"
|
||||
iconSize: 14
|
||||
buttonSize: 20
|
||||
z: 15
|
||||
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: closeButton
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 12
|
||||
anchors.rightMargin: 16
|
||||
iconName: "close"
|
||||
iconSize: 18
|
||||
buttonSize: 28
|
||||
z: 15
|
||||
onClicked: {
|
||||
if (notificationData)
|
||||
notificationData.popup = false;
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.right: dismissButton.left
|
||||
anchors.rightMargin: 8
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 8
|
||||
spacing: 8
|
||||
z: 20
|
||||
|
||||
Repeater {
|
||||
model: notificationData ? (notificationData.actions || []) : []
|
||||
|
||||
Rectangle {
|
||||
property bool isHovered: false
|
||||
|
||||
width: Math.max(actionText.implicitWidth + 12, 50)
|
||||
height: 24
|
||||
radius: 4
|
||||
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
|
||||
|
||||
Text {
|
||||
id: actionText
|
||||
text: modelData.text || ""
|
||||
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onEntered: parent.isHovered = true
|
||||
onExited: parent.isHovered = false
|
||||
onClicked: {
|
||||
if (modelData && modelData.invoke) {
|
||||
modelData.invoke();
|
||||
}
|
||||
if (notificationData) {
|
||||
notificationData.popup = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dismissButton
|
||||
property bool isHovered: false
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 8
|
||||
width: Math.max(dismissText.implicitWidth + 12, 50)
|
||||
height: 24
|
||||
radius: 4
|
||||
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
|
||||
z: 20
|
||||
|
||||
Text {
|
||||
id: dismissText
|
||||
text: "Dismiss"
|
||||
color: dismissButton.isHovered ? Theme.primary : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onEntered: dismissButton.isHovered = true
|
||||
onExited: dismissButton.isHovered = false
|
||||
onClicked: {
|
||||
if (notificationData)
|
||||
notificationData.popup = false;
|
||||
if (notificationData) {
|
||||
NotificationService.dismissNotification(notificationData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,7 +368,7 @@ PanelWindow {
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton
|
||||
propagateComposedEvents: true
|
||||
z: 0
|
||||
z: -1
|
||||
onEntered: {
|
||||
if (notificationData && notificationData.timer)
|
||||
notificationData.timer.stop();
|
||||
|
||||
@@ -21,7 +21,8 @@ Singleton {
|
||||
property bool addGateBusy: false
|
||||
property int enterAnimMs: 400
|
||||
property int seqCounter: 0
|
||||
|
||||
property bool bulkDismissing: false
|
||||
|
||||
Timer {
|
||||
id: addGate
|
||||
interval: enterAnimMs + 50
|
||||
@@ -34,6 +35,7 @@ Singleton {
|
||||
readonly property var groupedNotifications: getGroupedNotifications()
|
||||
readonly property var groupedPopups: getGroupedPopups()
|
||||
|
||||
|
||||
property var expandedGroups: ({})
|
||||
property var expandedMessages: ({})
|
||||
property bool popupsDisabled: false
|
||||
@@ -149,24 +151,20 @@ Singleton {
|
||||
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 (allIndex !== -1) {
|
||||
root.allWrappers.splice(allIndex, 1);
|
||||
// Only collapse the group if there's 1 or fewer notifications left
|
||||
if (remainingInGroup.length <= 1) {
|
||||
clearGroupExpansionState(groupKey);
|
||||
}
|
||||
|
||||
if (notifIndex !== -1) {
|
||||
const groupKey = getGroupKey(wrapper);
|
||||
root.notifications.splice(notifIndex, 1);
|
||||
|
||||
const remainingInGroup = root.notifications.filter(n => getGroupKey(n) === groupKey);
|
||||
if (remainingInGroup.length === 0) {
|
||||
clearGroupExpansionState(groupKey);
|
||||
} else if (remainingInGroup.length === 1) {
|
||||
clearGroupExpansionState(groupKey);
|
||||
}
|
||||
|
||||
cleanupExpansionStates();
|
||||
}
|
||||
cleanupExpansionStates();
|
||||
}
|
||||
|
||||
function onAboutToDestroy(): void {
|
||||
@@ -182,17 +180,34 @@ Singleton {
|
||||
|
||||
// Helper functions
|
||||
function clearAllNotifications() {
|
||||
// Actually dismiss all notifications from center
|
||||
const notificationsCopy = [...root.notifications];
|
||||
notificationsCopy.forEach(notif => {
|
||||
notif.notification.dismiss();
|
||||
});
|
||||
// Clear all expansion states
|
||||
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 (let i = 0; i < toDismiss.length; ++i) {
|
||||
const w = toDismiss[i];
|
||||
if (w && w.notification) {
|
||||
try { w.notification.dismiss(); } catch (e) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
bulkDismissing = false;
|
||||
popupsDisabled = false;
|
||||
}
|
||||
|
||||
function dismissNotification(wrapper) {
|
||||
wrapper.popup = false;
|
||||
wrapper.notification.dismiss();
|
||||
}
|
||||
|
||||
@@ -367,6 +382,7 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function clearGroupExpansionState(groupKey) {
|
||||
let newExpandedGroups = {};
|
||||
for (const key in expandedGroups) {
|
||||
@@ -377,6 +393,7 @@ Singleton {
|
||||
expandedGroups = newExpandedGroups;
|
||||
}
|
||||
|
||||
|
||||
function cleanupExpansionStates() {
|
||||
const currentGroupKeys = new Set(groupedNotifications.map(g => g.key));
|
||||
const currentMessageIds = new Set();
|
||||
@@ -464,6 +481,7 @@ Singleton {
|
||||
notif.body.toLowerCase().includes(searchLower)
|
||||
);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
cleanupPersistentStorage();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user