Fix convergence notifications

Filter @other out of the history blacklist so senders without a\ndesktop entry still land in the drawer. Keep the convergence empty\nstate, dismiss path, and popup geometry aligned with the current\nframe and dock layout.
This commit is contained in:
Marco Allegretti 2026-05-24 15:48:49 +02:00
parent 25d11acacd
commit 2e8ab6a741
8 changed files with 84 additions and 48 deletions

View file

@ -46,8 +46,15 @@ Item {
height: { height: {
let toolH = toolButtonsItem ? toolButtonsItem.height : 0; let toolH = toolButtonsItem ? toolButtonsItem.height : 0;
let h = Math.min(actionDrawer.height - toolH, notificationWidget.listView.contentHeight + Kirigami.Units.largeSpacing + topMargin); let avail = actionDrawer.height - toolH;
return maximumHeight > 0 ? Math.min(h, maximumHeight) : h; let content = notificationWidget.listView.contentHeight + Kirigami.Units.largeSpacing + topMargin;
// When empty, reserve the external cap so the placeholder has room;
// when populated, hug content so the clear-all toolbar sits below the list.
if (maximumHeight > 0 && !hasNotifications) {
return Math.min(avail, maximumHeight);
}
let cap = maximumHeight > 0 ? Math.min(avail, maximumHeight) : avail;
return Math.min(cap, content);
} }
// time source for the time and date whenin landscape mode // time source for the time and date whenin landscape mode
@ -63,10 +70,11 @@ Item {
// margin adjusted to fit and position into the action drawer // margin adjusted to fit and position into the action drawer
MobileShell.NotificationsWidget { MobileShell.NotificationsWidget {
id: notificationWidget id: notificationWidget
readonly property bool isConvergence: ShellSettings.Settings.convergenceModeEnabled
anchors.fill: parent anchors.fill: parent
anchors.topMargin: root.topMargin anchors.topMargin: root.topMargin + (isConvergence ? Kirigami.Units.gridUnit : 0)
anchors.rightMargin: actionDrawer.mode == MobileShell.ActionDrawer.Portrait ? 0 : Math.max(root.width - Kirigami.Units.gridUnit * 25, 0) anchors.rightMargin: actionDrawer.mode == MobileShell.ActionDrawer.Portrait ? 0 : (isConvergence ? Kirigami.Units.gridUnit : Math.max(root.width - Kirigami.Units.gridUnit * 25, 0))
anchors.leftMargin: actionDrawer.mode == MobileShell.ActionDrawer.Portrait ? 0 : -Kirigami.Units.gridUnit anchors.leftMargin: actionDrawer.mode == MobileShell.ActionDrawer.Portrait ? 0 : (isConvergence ? Kirigami.Units.gridUnit : -Kirigami.Units.gridUnit)
historyModel: actionDrawer.notificationModel historyModel: actionDrawer.notificationModel
historyModelType: actionDrawer.notificationModelType historyModelType: actionDrawer.notificationModelType
@ -75,6 +83,7 @@ Item {
onUnlockRequested: actionDrawer.permissionsRequested() onUnlockRequested: actionDrawer.permissionsRequested()
topPadding: root.topPadding topPadding: root.topPadding
showHeader: actionDrawer.mode != MobileShell.ActionDrawer.Portrait showHeader: actionDrawer.mode != MobileShell.ActionDrawer.Portrait
emptyText: ShellSettings.Settings.convergenceModeEnabled ? i18n("No notifications") : ""
listView.interactive: !actionDrawer.dragging && root.listOverflowing listView.interactive: !actionDrawer.dragging && root.listOverflowing
cardColorScheme: Kirigami.Theme.View cardColorScheme: Kirigami.Theme.View

View file

@ -27,8 +27,8 @@ Item {
property Item contentItem: Item {} property Item contentItem: Item {}
property Item background: Item {} property Item background: Item {}
implicitHeight: topPadding + bottomPadding + contentItem.implicitHeight implicitHeight: topPadding + bottomPadding + (contentItem ? contentItem.implicitHeight : 0)
implicitWidth: leftPadding + rightPadding + contentItem.implicitWidth implicitWidth: leftPadding + rightPadding + (contentItem ? contentItem.implicitWidth : 0)
onContentItemChanged: { onContentItemChanged: {
if (contentItem !== null && contentItem !== undefined) { if (contentItem !== null && contentItem !== undefined) {

View file

@ -27,6 +27,7 @@ Item {
// 'popupWidth' and 'openOffset' is set by the 'notificationPopupManager' // 'popupWidth' and 'openOffset' is set by the 'notificationPopupManager'
property int popupWidth property int popupWidth
property real openOffset property real openOffset
property real convergenceBottomInset: openOffset
property bool isConvergence: false property bool isConvergence: false
readonly property int primaryAnimationDuration: Math.round(MobileShell.Motion.duration(MobileShell.Motion.SpatialSlow) * 1.5) readonly property int primaryAnimationDuration: Math.round(MobileShell.Motion.duration(MobileShell.Motion.SpatialSlow) * 1.5)
readonly property int secondaryAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.SpatialSlow) readonly property int secondaryAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.SpatialSlow)
@ -35,7 +36,7 @@ Item {
// In convergence the popup enters from the bottom-right corner // In convergence the popup enters from the bottom-right corner
readonly property real effectiveOpenOffset: isConvergence readonly property real effectiveOpenOffset: isConvergence
? (Screen.height - openOffset - popupHeight) ? (Screen.height - convergenceBottomInset - popupHeight)
: openOffset : openOffset
readonly property real effectiveClosedOffset: isConvergence readonly property real effectiveClosedOffset: isConvergence
? (Screen.height + Kirigami.Units.smallSpacing) ? (Screen.height + Kirigami.Units.smallSpacing)

View file

@ -33,9 +33,8 @@ Window {
readonly property real openOffset: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3 readonly property real openOffset: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
readonly property int longestLength: Math.max(Screen.width, Screen.height) readonly property int longestLength: Math.max(Screen.width, Screen.height)
readonly property bool isConvergence: ShellSettings.Settings.convergenceModeEnabled readonly property bool isConvergence: ShellSettings.Settings.convergenceModeEnabled
// Margin between popup and screen edge in convergence mode; used in both readonly property real convergencePopupMargin: MobileShell.Constants.convergenceWorkspaceFrameThickness + Kirigami.Units.largeSpacing
// the delegate x position and the input-region calculation so they stay in sync. readonly property real convergencePopupBottomInset: MobileShell.Constants.convergenceDockHeight + MobileShell.Constants.convergenceWorkspaceFrameThickness + Kirigami.Units.largeSpacing
readonly property real convergencePopupMargin: Kirigami.Units.gridUnit * 2
readonly property int popupAnimationDuration: Math.round(MobileShell.Motion.duration(MobileShell.Motion.SpatialSlow) * 1.5) readonly property int popupAnimationDuration: Math.round(MobileShell.Motion.duration(MobileShell.Motion.SpatialSlow) * 1.5)
property var keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone property var keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone
@ -104,9 +103,9 @@ Window {
} }
if (isConvergence) { if (isConvergence) {
let regionX = notificationPopupManager.width - notificationPopupManager.popupWidth - notificationPopupManager.convergencePopupMargin; let regionX = notificationPopupManager.width - notificationPopupManager.popupWidth - notificationPopupManager.convergencePopupMargin - Kirigami.Units.gridUnit / 2;
let regionY = openOffset; let regionY = (currentPopup ? currentPopup.effectiveOpenOffset : Screen.height - notificationPopupManager.convergencePopupBottomInset - popupHeight) - Kirigami.Units.gridUnit / 2;
ShellUtil.setInputRegion(notificationPopupManager, Qt.rect(regionX, regionY, notificationPopupManager.popupWidth + Kirigami.Units.gridUnit * 2, popupHeight + Kirigami.Units.gridUnit * 2)); ShellUtil.setInputRegion(notificationPopupManager, Qt.rect(regionX, regionY, notificationPopupManager.popupWidth + Kirigami.Units.gridUnit, popupHeight + Kirigami.Units.gridUnit));
} else { } else {
ShellUtil.setInputRegion(notificationPopupManager, Qt.rect((notificationPopupManager.width - notificationPopupManager.popupWidth - Kirigami.Units.gridUnit) / 2, openOffset - Kirigami.Units.gridUnit / 2, notificationPopupManager.popupWidth + Kirigami.Units.gridUnit, popupHeight + Kirigami.Units.gridUnit * ((notifications.count - notifications.currentPopupIndex > 1) ? 4 : 1))); ShellUtil.setInputRegion(notificationPopupManager, Qt.rect((notificationPopupManager.width - notificationPopupManager.popupWidth - Kirigami.Units.gridUnit) / 2, openOffset - Kirigami.Units.gridUnit / 2, notificationPopupManager.popupWidth + Kirigami.Units.gridUnit, popupHeight + Kirigami.Units.gridUnit * ((notifications.count - notifications.currentPopupIndex > 1) ? 4 : 1)));
} }
@ -207,13 +206,14 @@ Window {
id: popup id: popup
x: notificationPopupManager.isConvergence x: notificationPopupManager.isConvergence
? (parent.width - width - notificationPopupManager.convergencePopupMargin) ? (notificationPopupManager.width - width - notificationPopupManager.convergencePopupMargin)
: (parent.width - width) / 2 : (notificationPopupManager.width - width) / 2
z: notifications.count - index z: notifications.count - index
isConvergence: notificationPopupManager.isConvergence isConvergence: notificationPopupManager.isConvergence
popupWidth: notificationPopupManager.popupWidth popupWidth: notificationPopupManager.popupWidth
openOffset: notificationPopupManager.openOffset openOffset: notificationPopupManager.openOffset
convergenceBottomInset: notificationPopupManager.convergencePopupBottomInset
keyboardInteractivity: notificationPopupManager.keyboardInteractivity keyboardInteractivity: notificationPopupManager.keyboardInteractivity
popupNotifications: notifications popupNotifications: notifications

View file

@ -78,10 +78,12 @@ Item {
signal dragEnd() signal dragEnd()
onContentItemChanged: { onContentItemChanged: {
if (!contentItem) {
return;
}
contentItem.parent = contentParent; contentItem.parent = contentParent;
contentItem.anchors.fill = contentParent; contentItem.anchors.fill = contentParent;
contentItem.anchors.margins = Kirigami.Units.largeSpacing; contentItem.anchors.margins = Kirigami.Units.largeSpacing;
contentParent.children.push(contentItem);
} }
implicitHeight: contentParent.implicitHeight implicitHeight: contentParent.implicitHeight

View file

@ -6,6 +6,7 @@
*/ */
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls as Controls
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import QtQuick.Window 2.2 import QtQuick.Window 2.2
@ -58,7 +59,6 @@ BaseNotificationItem {
onDismissRequested: { onDismissRequested: {
model.resident = false; model.resident = false;
notificationItem.dismissRequested(); notificationItem.dismissRequested();
notificationItem.close();
} }
onDragStart: notificationItem.dragStart() onDragStart: notificationItem.dragStart()
@ -116,6 +116,17 @@ BaseNotificationItem {
time: notificationItem.time time: notificationItem.time
clockSource: notificationItem.clockSource clockSource: notificationItem.clockSource
} }
PlasmaComponents.ToolButton {
visible: notificationItem.closable
icon.name: "window-close"
text: i18n("Dismiss")
display: Controls.AbstractButton.IconOnly
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
onClicked: mainCard.dismissRequested()
}
} }
// notification contents // notification contents

View file

@ -86,6 +86,8 @@ Item {
*/ */
property bool showHeader: false property bool showHeader: false
property string emptyText: ""
/** /**
* Gives access to the notification list view outside of the notification widget. * Gives access to the notification list view outside of the notification widget.
*/ */
@ -179,6 +181,32 @@ Item {
id: clock id: clock
} }
// Empty-state placeholders centred in the full widget rather than the
// (possibly tiny) ListView so they don't clip out of view.
PlasmaExtras.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - (Kirigami.Units.gridUnit * 4)
text: i18n("Notification service not available")
visible: list.count === 0 && !NotificationManager.Server.valid && historyModelType === NotificationsModelType.NotificationsModel
PlasmaComponents3.Label {
readonly property NotificationManager.ServerInfo currentOwner: !NotificationManager.Server.valid ? NotificationManager.Server.currentOwner : null
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: currentOwner ? i18nc("Vendor and product name", "Notifications are currently provided by '%1 %2'", currentOwner.vendor, currentOwner.name) : ""
visible: currentOwner && currentOwner.vendor && currentOwner.name
}
}
PlasmaExtras.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - (Kirigami.Units.gridUnit * 4)
text: root.emptyText
visible: list.count === 0 && root.emptyText.length > 0 && NotificationManager.Server.valid
}
ListView { ListView {
id: list id: list
model: historyModel model: historyModel
@ -250,24 +278,6 @@ Item {
criteria: ViewSection.FullString criteria: ViewSection.FullString
} }
PlasmaExtras.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - (Kirigami.Units.gridUnit * 4)
text: i18n("Notification service not available")
visible: list.count === 0 && !NotificationManager.Server.valid && historyModelType === NotificationsModelType.NotificationsModel
PlasmaComponents3.Label {
// Checking valid to avoid creating ServerInfo object if everything is alright
readonly property NotificationManager.ServerInfo currentOwner: !NotificationManager.Server.valid ? NotificationManager.Server.currentOwner : null
// PlasmaExtras.PlaceholderMessage is internally a ColumnLayout, so we can use Layout.whatever properties here
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: currentOwner ? i18nc("Vendor and product name", "Notifications are currently provided by '%1 %2'", currentOwner.vendor, currentOwner.name) : ""
visible: currentOwner && currentOwner.vendor && currentOwner.name
}
}
// Run every time an item is visually added to the list, thus when `Show n more` button is clicked as well. // Run every time an item is visually added to the list, thus when `Show n more` button is clicked as well.
add: Transition { add: Transition {
MobileShell.MotionNumberAnimation { property: "opacity"; from: 0; to: 1; duration: list.animationDuration; type: MobileShell.Motion.Standard } MobileShell.MotionNumberAnimation { property: "opacity"; from: 0; to: 1; duration: list.animationDuration; type: MobileShell.Motion.Standard }

View file

@ -183,25 +183,28 @@ Item {
actionDrawer.restrictedPermissions: MobileShellState.LockscreenDBusClient.lockscreenActive actionDrawer.restrictedPermissions: MobileShellState.LockscreenDBusClient.lockscreenActive
actionDrawer.notificationSettings: NotificationManager.Settings {} actionDrawer.notificationSettings: NotificationManager.Settings {
id: notificationSettings
}
actionDrawer.notificationModel: NotificationManager.Notifications { actionDrawer.notificationModel: NotificationManager.Notifications {
showExpired: true showExpired: true
showDismissed: true showDismissed: true
showJobs: drawer.actionDrawer.notificationSettings.jobsInNotifications showJobs: notificationSettings.jobsInNotifications
sortMode: NotificationManager.Notifications.SortByTypeAndUrgency sortMode: NotificationManager.Notifications.SortByTypeAndUrgency
groupMode: NotificationManager.Notifications.GroupApplicationsFlat groupMode: NotificationManager.Notifications.GroupApplicationsFlat
groupLimit: 2 groupLimit: 2
expandUnread: true expandUnread: true
blacklistedDesktopEntries: drawer.actionDrawer.notificationSettings.historyBlacklistedApplications // Strip "@other" from the blacklist: Plasma's default config blocks
blacklistedNotifyRcNames: drawer.actionDrawer.notificationSettings.historyBlacklistedServices // notifications from unregistered/non-configurable sources, which on
urgencies: { // a mobile/convergence shell silently hides anything not shipping a
var urgencies = NotificationManager.Notifications.CriticalUrgency // .desktop entry (e.g. third-party DBus senders). Shift surfaces all
| NotificationManager.Notifications.NormalUrgency; // notifications and lets the user blacklist individual apps later.
if (drawer.actionDrawer.notificationSettings.lowPriorityHistory) { blacklistedDesktopEntries: notificationSettings.historyBlacklistedApplications
urgencies |= NotificationManager.Notifications.LowUrgency; .filter(function(e) { return e !== "@other"; })
} blacklistedNotifyRcNames: notificationSettings.historyBlacklistedServices
return urgencies; urgencies: NotificationManager.Notifications.CriticalUrgency
} | NotificationManager.Notifications.NormalUrgency
| NotificationManager.Notifications.LowUrgency
} }
Connections { Connections {