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
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
implicitWidth: 400
|
implicitWidth: 400
|
||||||
implicitHeight: 116
|
implicitHeight: 122
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: true
|
top: true
|
||||||
@@ -138,13 +138,12 @@ PanelWindow {
|
|||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 12
|
anchors.topMargin: 12
|
||||||
anchors.leftMargin: 16
|
anchors.leftMargin: 16
|
||||||
anchors.rightMargin: 16
|
anchors.rightMargin: 56
|
||||||
height: 86
|
height: 98
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: iconContainer
|
id: iconContainer
|
||||||
readonly property bool hasNotificationImage: notificationData && notificationData.image && notificationData.image !== ""
|
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
|
property alias iconImage: iconImage
|
||||||
|
|
||||||
width: 55
|
width: 55
|
||||||
@@ -196,89 +195,169 @@ PanelWindow {
|
|||||||
id: textContainer
|
id: textContainer
|
||||||
anchors.left: iconContainer.right
|
anchors.left: iconContainer.right
|
||||||
anchors.leftMargin: 12
|
anchors.leftMargin: 12
|
||||||
anchors.right: closeButton.left
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 8
|
anchors.rightMargin: 0
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.bottomMargin: 8
|
anchors.bottomMargin: 8
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Column {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: 2
|
height: parent.height
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: -4
|
||||||
|
|
||||||
Text {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: {
|
spacing: 2
|
||||||
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 {
|
Text {
|
||||||
text: notificationData ? (notificationData.summary || "") : ""
|
width: parent.width
|
||||||
color: Theme.surfaceText
|
text: {
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
if (!notificationData) return "";
|
||||||
font.weight: Font.Medium
|
const appName = notificationData.appName || "";
|
||||||
width: parent.width
|
const timeStr = notificationData.timeStr || "";
|
||||||
elide: Text.ElideRight
|
if (timeStr.length > 0)
|
||||||
maximumLineCount: 1
|
return appName + " • " + timeStr;
|
||||||
visible: text.length > 0
|
else
|
||||||
}
|
return appName;
|
||||||
|
}
|
||||||
Text {
|
color: Theme.surfaceVariantText
|
||||||
property bool hasUrls: {
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
if (!notificationData || !notificationData.body) return false;
|
font.weight: Font.Medium
|
||||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
elide: Text.ElideRight
|
||||||
return urlRegex.test(notificationData.body);
|
maximumLineCount: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
text: {
|
Text {
|
||||||
if (!notificationData || !notificationData.body) return "";
|
text: notificationData ? (notificationData.summary || "") : ""
|
||||||
let bodyText = notificationData.body;
|
color: Theme.surfaceText
|
||||||
if (bodyText.length > 105)
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
bodyText = bodyText.substring(0, 102) + "...";
|
font.weight: Font.Medium
|
||||||
|
width: parent.width
|
||||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
elide: Text.ElideRight
|
||||||
return bodyText.replace(urlRegex, '<a href="$1" style="color: ' + Theme.primary + '; text-decoration: underline;">$1</a>');
|
maximumLineCount: 1
|
||||||
|
visible: text.length > 0
|
||||||
}
|
}
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
Text {
|
||||||
width: parent.width
|
text: notificationData ? (notificationData.body || "") : ""
|
||||||
elide: Text.ElideRight
|
color: Theme.surfaceVariantText
|
||||||
maximumLineCount: 2
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
wrapMode: Text.WordWrap
|
width: parent.width
|
||||||
visible: text.length > 0
|
elide: Text.ElideRight
|
||||||
textFormat: Text.RichText
|
maximumLineCount: 2
|
||||||
onLinkActivated: function(link) {
|
wrapMode: Text.WordWrap
|
||||||
Qt.openUrlExternally(link);
|
visible: text.length > 0
|
||||||
|
textFormat: Text.PlainText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
id: closeButton
|
}
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
DankActionButton {
|
||||||
iconName: "close"
|
id: closeButton
|
||||||
iconSize: 14
|
anchors.right: parent.right
|
||||||
buttonSize: 20
|
anchors.top: parent.top
|
||||||
z: 15
|
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: {
|
onClicked: {
|
||||||
if (notificationData)
|
if (notificationData) {
|
||||||
notificationData.popup = false;
|
NotificationService.dismissNotification(notificationData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,7 +368,7 @@ PanelWindow {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
propagateComposedEvents: true
|
propagateComposedEvents: true
|
||||||
z: 0
|
z: -1
|
||||||
onEntered: {
|
onEntered: {
|
||||||
if (notificationData && notificationData.timer)
|
if (notificationData && notificationData.timer)
|
||||||
notificationData.timer.stop();
|
notificationData.timer.stop();
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ Singleton {
|
|||||||
property bool addGateBusy: false
|
property bool addGateBusy: false
|
||||||
property int enterAnimMs: 400
|
property int enterAnimMs: 400
|
||||||
property int seqCounter: 0
|
property int seqCounter: 0
|
||||||
|
property bool bulkDismissing: false
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: addGate
|
id: addGate
|
||||||
interval: enterAnimMs + 50
|
interval: enterAnimMs + 50
|
||||||
@@ -34,6 +35,7 @@ Singleton {
|
|||||||
readonly property var groupedNotifications: getGroupedNotifications()
|
readonly property var groupedNotifications: getGroupedNotifications()
|
||||||
readonly property var groupedPopups: getGroupedPopups()
|
readonly property var groupedPopups: getGroupedPopups()
|
||||||
|
|
||||||
|
|
||||||
property var expandedGroups: ({})
|
property var expandedGroups: ({})
|
||||||
property var expandedMessages: ({})
|
property var expandedMessages: ({})
|
||||||
property bool popupsDisabled: false
|
property bool popupsDisabled: false
|
||||||
@@ -149,24 +151,20 @@ Singleton {
|
|||||||
function onDropped(): void {
|
function onDropped(): void {
|
||||||
const notifIndex = root.notifications.indexOf(wrapper);
|
const notifIndex = root.notifications.indexOf(wrapper);
|
||||||
const allIndex = root.allWrappers.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) {
|
// Only collapse the group if there's 1 or fewer notifications left
|
||||||
root.allWrappers.splice(allIndex, 1);
|
if (remainingInGroup.length <= 1) {
|
||||||
|
clearGroupExpansionState(groupKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notifIndex !== -1) {
|
cleanupExpansionStates();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAboutToDestroy(): void {
|
function onAboutToDestroy(): void {
|
||||||
@@ -182,17 +180,34 @@ Singleton {
|
|||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
function clearAllNotifications() {
|
function clearAllNotifications() {
|
||||||
// Actually dismiss all notifications from center
|
bulkDismissing = true;
|
||||||
const notificationsCopy = [...root.notifications];
|
popupsDisabled = true;
|
||||||
notificationsCopy.forEach(notif => {
|
addGate.stop();
|
||||||
notif.notification.dismiss();
|
addGateBusy = false;
|
||||||
});
|
notificationQueue = [];
|
||||||
// Clear all expansion states
|
|
||||||
|
for (const w of visibleNotifications) w.popup = false;
|
||||||
|
visibleNotifications = [];
|
||||||
|
|
||||||
|
const toDismiss = notifications.slice();
|
||||||
|
|
||||||
|
if (notifications.length) notifications.splice(0, notifications.length);
|
||||||
expandedGroups = {};
|
expandedGroups = {};
|
||||||
expandedMessages = {};
|
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) {
|
function dismissNotification(wrapper) {
|
||||||
|
wrapper.popup = false;
|
||||||
wrapper.notification.dismiss();
|
wrapper.notification.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,6 +382,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function clearGroupExpansionState(groupKey) {
|
function clearGroupExpansionState(groupKey) {
|
||||||
let newExpandedGroups = {};
|
let newExpandedGroups = {};
|
||||||
for (const key in expandedGroups) {
|
for (const key in expandedGroups) {
|
||||||
@@ -377,6 +393,7 @@ Singleton {
|
|||||||
expandedGroups = newExpandedGroups;
|
expandedGroups = newExpandedGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function cleanupExpansionStates() {
|
function cleanupExpansionStates() {
|
||||||
const currentGroupKeys = new Set(groupedNotifications.map(g => g.key));
|
const currentGroupKeys = new Set(groupedNotifications.map(g => g.key));
|
||||||
const currentMessageIds = new Set();
|
const currentMessageIds = new Set();
|
||||||
@@ -464,6 +481,7 @@ Singleton {
|
|||||||
notif.body.toLowerCase().includes(searchLower)
|
notif.body.toLowerCase().includes(searchLower)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
cleanupPersistentStorage();
|
cleanupPersistentStorage();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user