toppanel/notifications: add clean all and do not disturb

Hi, this MR adds "clear all" and "do not disturb" buttons to `NotificationsWidget`.

Few other corrections have been made:
 - Transitions should now work correctly, plus they can be disabled via `MobileShell.MobileShellSettings.animationsEnabled`;
 - Loaders are now asynchronous;
 - List items have now their own margins, in order to not clip their shadows.\

It only remains to implement a context menu to set do not disturb mode for a given period of time. I think it's a secondary feature on which we can work later, but in case tell me what to do.

Unfortunately I can't test the lockscreen on a phone at the moment, and I cannot take a performance test as well. It would be great if someone tested all these things, otherwise I'll provide as soon as possible.

Closes: https://invent.kde.org/plasma/plasma-mobile/-/issues/134
This commit is contained in:
Yari Polla 2022-06-24 17:44:30 +02:00 committed by Devin Lin
parent db86c14293
commit d8399e88be
4 changed files with 194 additions and 45 deletions

View file

@ -71,6 +71,7 @@ PlasmaCore.ColorScope {
top: mediaWidget.bottom top: mediaWidget.bottom
topMargin: 0 topMargin: 0
bottom: parent.bottom bottom: parent.bottom
bottomMargin: 0
right: quickSettings.left right: quickSettings.left
left: parent.left left: parent.left
} }

View file

@ -43,7 +43,7 @@ PlasmaCore.ColorScope {
color: Qt.rgba(PlasmaCore.Theme.backgroundColor.r, color: Qt.rgba(PlasmaCore.Theme.backgroundColor.r,
PlasmaCore.Theme.backgroundColor.g, PlasmaCore.Theme.backgroundColor.g,
PlasmaCore.Theme.backgroundColor.b, PlasmaCore.Theme.backgroundColor.b,
notificationWidget.hasNotifications ? 0.95 : 0.7) 0.95)
Behavior on color { ColorAnimation { duration: PlasmaCore.Units.longDuration } } Behavior on color { ColorAnimation { duration: PlasmaCore.Units.longDuration } }
opacity: Math.max(0, Math.min(1, actionDrawer.offset / root.minimizedQuickSettingsOffset)) opacity: Math.max(0, Math.min(1, actionDrawer.offset / root.minimizedQuickSettingsOffset))
} }
@ -114,11 +114,8 @@ PlasmaCore.ColorScope {
top: quickSettings.top top: quickSettings.top
topMargin: quickSettings.height + translate.y topMargin: quickSettings.height + translate.y
bottom: parent.bottom bottom: parent.bottom
bottomMargin: PlasmaCore.Units.largeSpacing
left: parent.left left: parent.left
leftMargin: PlasmaCore.Units.largeSpacing
right: parent.right right: parent.right
rightMargin: PlasmaCore.Units.largeSpacing
} }
opacity: applyMinMax(root.actionDrawer.offset / root.minimizedQuickSettingsOffset) opacity: applyMinMax(root.actionDrawer.offset / root.minimizedQuickSettingsOffset)
} }

View file

@ -6,7 +6,7 @@
*/ */
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.15
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import QtGraphicalEffects 1.12 import QtGraphicalEffects 1.12
@ -29,17 +29,17 @@ Item {
/** /**
* The notification model for the widget. * The notification model for the widget.
*/ */
property var historyModel: [] property var historyModel
/** /**
* The type of notification model used for the widget. * The type of notification model used for the widget.
*/ */
property int historyModelType: NotificationsModelType.NotificationsModel property int historyModelType
/** /**
* The notification model settings for the widget. * The notification model settings for the widget.
*/ */
property var notificationSettings: NotificationManager.Settings {} property var notificationSettings
/** /**
* Whether invoking notification actions requires authentiation of some sort. * Whether invoking notification actions requires authentiation of some sort.
@ -55,6 +55,8 @@ Item {
*/ */
readonly property bool hasNotifications: list.count > 0 readonly property bool hasNotifications: list.count > 0
readonly property bool doNotDisturbModeEnabled: !isNaN(notificationSettings.notificationsInhibitedUntil)
enum ModelType { enum ModelType {
NotificationsModel, // used in the logged-in shell NotificationsModel, // used in the logged-in shell
WatchedNotificationsModel // used on the lockscreen WatchedNotificationsModel // used on the lockscreen
@ -85,6 +87,27 @@ Item {
*/ */
function clearHistory() { function clearHistory() {
historyModel.clear(NotificationManager.Notifications.ClearExpired); historyModel.clear(NotificationManager.Notifications.ClearExpired);
if (historyModel.count === 0) {
backgroundClicked();
}
}
/**
* Toggles Do Not Disturb mode.
*/
function toggleDoNotDisturbMode() {
if (doNotDisturbModeEnabled) {
notificationSettings.defaults();
} else {
var until = new Date();
until.setFullYear(until.getFullYear() + 1);
notificationSettings.notificationsInhibitedUntil = until;
}
notificationSettings.save();
} }
/** /**
@ -102,35 +125,51 @@ Item {
intervalAlignment: PlasmaCore.Types.AlignToMinute intervalAlignment: PlasmaCore.Types.AlignToMinute
} }
// implement background clicking signal
MouseArea {
anchors.fill: parent
onClicked: backgroundClicked()
z: -1 // ensure that this is below notification items so we don't steal button clicks
}
ListView { ListView {
id: list id: list
model: historyModel model: historyModel
currentIndex: -1
clip: true
currentIndex: 0
property var pendingNotificationWithAction property var pendingNotificationWithAction
readonly property int animationDuration: MobileShell.MobileShellSettings.animationsEnabled ? PlasmaCore.Units.longDuration : 0
// If a screen overflow occurs, fix height in order to maintain tool buttons in place.
readonly property bool listOverflowing: contentItem.childrenRect.height + toolButtons.height + bottomMargin + spacing >= root.height
bottomMargin: spacing * 2
height: count === 0 ? 0 : listOverflowing ? root.height - toolButtons.height - bottomMargin : contentItem.childrenRect.height + spacing
anchors {
top: parent.top
left: parent.left
right: parent.right
}
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
anchors.fill: parent
// TODO keyboard focus // TODO keyboard focus
highlightMoveDuration: 0 highlightMoveDuration: 0
highlightResizeDuration: 0 highlightResizeDuration: 0
highlight: Item {} highlight: Item {}
section { section {
property: "isInGroup" property: "isGroup"
criteria: ViewSection.FullString criteria: ViewSection.FullString
} }
// implement background clicking signal
MouseArea {
anchors.fill: parent
onClicked: root.backgroundClicked()
z: -1 // ensure that this is below notification items so we don't steal button clicks
}
PlasmaExtras.PlaceholderMessage { PlasmaExtras.PlaceholderMessage {
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - (PlasmaCore.Units.largeSpacing * 4) width: parent.width - (PlasmaCore.Units.largeSpacing * 4)
@ -149,24 +188,13 @@ Item {
} }
} }
// 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 {
SequentialAnimation { NumberAnimation { property: "opacity"; from: 0; to: 1; duration: list.animationDuration }
PropertyAction { property: "opacity"; value: 0 }
PauseAnimation { duration: PlasmaCore.Units.longDuration }
ParallelAnimation {
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: PlasmaCore.Units.longDuration }
NumberAnimation { property: "height"; from: 0; duration: PlasmaCore.Units.longDuration }
}
}
} }
addDisplaced: Transition { // Run every time an item is displaced, such as when the order is scrambled due to a group expansion.
NumberAnimation { properties: "y"; duration: PlasmaCore.Units.longDuration } displaced: Transition {
} NumberAnimation { properties: "y"; duration: list.animationDuration }
removeDisplaced: Transition {
SequentialAnimation {
PauseAnimation { duration: PlasmaCore.Units.longDuration }
NumberAnimation { properties: "y"; duration: PlasmaCore.Units.longDuration }
}
} }
function isRowExpanded(row) { function isRowExpanded(row) {
@ -191,16 +219,35 @@ Item {
} }
} }
} }
// Instantly re-align items after group expansion.
forceLayout();
} }
delegate: Loader { delegate: Loader {
id: delegateLoader id: delegateLoader
width: list.width
anchors {
left: parent ? parent.left : undefined
leftMargin: PlasmaCore.Units.largeSpacing
right: parent ? parent.right : undefined
rightMargin: PlasmaCore.Units.largeSpacing
}
height: model.isGroup ? groupDelegate.height : notificationDelegate.height
sourceComponent: model.isGroup ? groupDelegate : notificationDelegate sourceComponent: model.isGroup ? groupDelegate : notificationDelegate
asynchronous: true
required property var model required property var model
required property int index required property int index
// We have to do this here in order to control the animation before the item is completely removed
ListView.onRemove: SequentialAnimation {
PropertyAction { target: delegateLoader; property: "ListView.delayRemove"; value: true }
NumberAnimation { target: delegateLoader; property: "opacity"; to: 0.0; duration: list.animationDuration }
PropertyAction { target: delegateLoader; property: "ListView.delayRemove"; value: false }
}
Component { Component {
id: groupDelegate id: groupDelegate
NotificationGroupHeader { NotificationGroupHeader {
@ -213,12 +260,16 @@ Item {
Component { Component {
id: notificationDelegate id: notificationDelegate
ColumnLayout {
Column {
spacing: PlasmaCore.Units.smallSpacing spacing: PlasmaCore.Units.smallSpacing
height: notificationItem.height + showMoreLoader.height
NotificationItem { NotificationItem {
id: notificationItem id: notificationItem
Layout.fillWidth: true width: parent.width
height: implicitHeight
model: delegateLoader.model model: delegateLoader.model
modelIndex: delegateLoader.index modelIndex: delegateLoader.index
@ -233,16 +284,37 @@ Item {
} }
} }
// Every item has got an instance of this loader, but it becomes active only for last ones that take place in big enough groups.
Loader { Loader {
id: showMoreLoader
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
opacity: 0.0
visible: active visible: active
asynchronous: true
active: { active: {
// if we have the WatchedNotificationsModel, we don't have notification grouping support // if we have the WatchedNotificationsModel, we don't have notification grouping support
if (typeof model.groupChildrenCount === 'undefined') if (typeof model.groupChildrenCount === 'undefined')
return false; return false;
return (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded) return (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded)
&& delegateLoader.ListView.nextSection !== delegateLoader.ListView.section && delegateLoader.ListView.nextSection != delegateLoader.ListView.section;
}
// state + transition: animates the item when it becomes visible. Fade off is handled by above ListView.onRemove.
states: State {
name: "VISIBLE"
when: showMoreLoader.status == Loader.Ready
PropertyChanges { target: showMoreLoader; opacity: 1.0 }
}
transitions: Transition {
to: "VISIBLE"
SequentialAnimation {
PauseAnimation { duration: list.animationDuration * 2 }
NumberAnimation { properties: "opacity"; duration: list.animationDuration }
}
} }
sourceComponent: PlasmaComponents3.ToolButton { sourceComponent: PlasmaComponents3.ToolButton {
@ -250,11 +322,86 @@ Item {
text: model.isGroupExpanded ? i18n("Show Fewer") text: model.isGroupExpanded ? i18n("Show Fewer")
: i18nc("Expand to show n more notifications", : i18nc("Expand to show n more notifications",
"Show %1 More", (model.groupChildrenCount - model.expandedGroupChildrenCount)) "Show %1 More", (model.groupChildrenCount - model.expandedGroupChildrenCount))
onClicked: list.setGroupExpanded(model.index, !model.isGroupExpanded) onClicked: {
list.setGroupExpanded(model.index, !model.isGroupExpanded)
}
} }
} }
} }
} }
} }
} }
Item {
id: toolButtons
visible: !root.actionsRequireUnlock
width: root.width
height: toolLayout.implicitHeight + spacer.height
anchors {
top: list.bottom
left: parent.left
right: parent.right
}
Rectangle {
id: spacer
visible: list.listOverflowing
anchors {
top: toolButtons.top
left: toolButtons.left
right: toolButtons.right
}
height: 1
opacity: 0.25
color: PlasmaCore.Theme.textColor
}
RowLayout {
id: toolLayout
anchors {
top: spacer.bottom
topMargin: list.spacing
left: parent.left
leftMargin: PlasmaCore.Units.smallSpacing
right: parent.right
rightMargin: PlasmaCore.Units.smallSpacing
bottom: parent.bottom
bottomMargin: list.spacing
}
PlasmaComponents3.ToolButton {
id: doNotDisturbButton
Layout.alignment: hasNotifications ? Qt.AlignLeft : Qt.AlignHCenter
font.bold: true
icon.name: doNotDisturbModeEnabled ? "notifications" : "notifications-disabled"
text: doNotDisturbModeEnabled ? "Enable Notifications" : "Do Not Disturb"
onClicked: toggleDoNotDisturbMode()
}
PlasmaComponents3.ToolButton {
id: clearButton
Layout.alignment: Qt.AlignRight
visible: hasNotifications
font.bold: true
icon.name: "edit-clear-history"
text: "Clear All Notifications"
onClicked: clearHistory()
}
}
}
} }

View file

@ -13,10 +13,13 @@ import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.notificationmanager 1.1 as Notifications import org.kde.notificationmanager 1.1 as Notifications
import org.kde.plasma.private.mobileshell 1.0 as MobileShell import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.notificationmanager 1.0 as NotificationManager
Rectangle { Rectangle {
id: root id: root
required property var lockScreenState required property var lockScreenState
property var notificationsModel: [] property var notificationsModel: []
property var notificationSettings: NotificationManager.Settings {}
readonly property bool notificationsShown: notificationsList.hasNotifications readonly property bool notificationsShown: notificationsList.hasNotifications
@ -61,6 +64,7 @@ Rectangle {
historyModelType: MobileShell.NotificationsModelType.WatchedNotificationsModel historyModelType: MobileShell.NotificationsModelType.WatchedNotificationsModel
actionsRequireUnlock: true actionsRequireUnlock: true
historyModel: root.notificationsModel historyModel: root.notificationsModel
notificationSettings: root.notificationSettings
property bool requestNotificationAction: false property bool requestNotificationAction: false