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: {
let toolH = toolButtonsItem ? toolButtonsItem.height : 0;
let h = Math.min(actionDrawer.height - toolH, notificationWidget.listView.contentHeight + Kirigami.Units.largeSpacing + topMargin);
return maximumHeight > 0 ? Math.min(h, maximumHeight) : h;
let avail = actionDrawer.height - toolH;
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
@ -63,10 +70,11 @@ Item {
// margin adjusted to fit and position into the action drawer
MobileShell.NotificationsWidget {
id: notificationWidget
readonly property bool isConvergence: ShellSettings.Settings.convergenceModeEnabled
anchors.fill: parent
anchors.topMargin: root.topMargin
anchors.rightMargin: actionDrawer.mode == MobileShell.ActionDrawer.Portrait ? 0 : Math.max(root.width - Kirigami.Units.gridUnit * 25, 0)
anchors.leftMargin: actionDrawer.mode == MobileShell.ActionDrawer.Portrait ? 0 : -Kirigami.Units.gridUnit
anchors.topMargin: root.topMargin + (isConvergence ? Kirigami.Units.gridUnit : 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 : (isConvergence ? Kirigami.Units.gridUnit : -Kirigami.Units.gridUnit)
historyModel: actionDrawer.notificationModel
historyModelType: actionDrawer.notificationModelType
@ -75,6 +83,7 @@ Item {
onUnlockRequested: actionDrawer.permissionsRequested()
topPadding: root.topPadding
showHeader: actionDrawer.mode != MobileShell.ActionDrawer.Portrait
emptyText: ShellSettings.Settings.convergenceModeEnabled ? i18n("No notifications") : ""
listView.interactive: !actionDrawer.dragging && root.listOverflowing
cardColorScheme: Kirigami.Theme.View

View file

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

View file

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

View file

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

View file

@ -6,6 +6,7 @@
*/
import QtQuick 2.15
import QtQuick.Controls as Controls
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
@ -58,7 +59,6 @@ BaseNotificationItem {
onDismissRequested: {
model.resident = false;
notificationItem.dismissRequested();
notificationItem.close();
}
onDragStart: notificationItem.dragStart()
@ -116,6 +116,17 @@ BaseNotificationItem {
time: notificationItem.time
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

View file

@ -86,6 +86,8 @@ Item {
*/
property bool showHeader: false
property string emptyText: ""
/**
* Gives access to the notification list view outside of the notification widget.
*/
@ -179,6 +181,32 @@ Item {
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 {
id: list
model: historyModel
@ -250,24 +278,6 @@ Item {
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.
add: Transition {
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.notificationSettings: NotificationManager.Settings {}
actionDrawer.notificationSettings: NotificationManager.Settings {
id: notificationSettings
}
actionDrawer.notificationModel: NotificationManager.Notifications {
showExpired: true
showDismissed: true
showJobs: drawer.actionDrawer.notificationSettings.jobsInNotifications
showJobs: notificationSettings.jobsInNotifications
sortMode: NotificationManager.Notifications.SortByTypeAndUrgency
groupMode: NotificationManager.Notifications.GroupApplicationsFlat
groupLimit: 2
expandUnread: true
blacklistedDesktopEntries: drawer.actionDrawer.notificationSettings.historyBlacklistedApplications
blacklistedNotifyRcNames: drawer.actionDrawer.notificationSettings.historyBlacklistedServices
urgencies: {
var urgencies = NotificationManager.Notifications.CriticalUrgency
| NotificationManager.Notifications.NormalUrgency;
if (drawer.actionDrawer.notificationSettings.lowPriorityHistory) {
urgencies |= NotificationManager.Notifications.LowUrgency;
}
return urgencies;
}
// Strip "@other" from the blacklist: Plasma's default config blocks
// notifications from unregistered/non-configurable sources, which on
// a mobile/convergence shell silently hides anything not shipping a
// .desktop entry (e.g. third-party DBus senders). Shift surfaces all
// notifications and lets the user blacklist individual apps later.
blacklistedDesktopEntries: notificationSettings.historyBlacklistedApplications
.filter(function(e) { return e !== "@other"; })
blacklistedNotifyRcNames: notificationSettings.historyBlacklistedServices
urgencies: NotificationManager.Notifications.CriticalUrgency
| NotificationManager.Notifications.NormalUrgency
| NotificationManager.Notifications.LowUrgency
}
Connections {