mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
widgets/notifications: Cleanup, fix some visual issues, and add jobs
Note: There was a lot of whitespace that my IDE is now removing. Fixes job notifications not being dismissable (#208), and imports an implementation from workspace for the progress bar and actions. Fixes notification contents not being clipped when being dragged (https://invent.kde.org/teams/plasma-mobile/issues/-/issues/287) Also fixes notification text being spread over multiple lines unnecessarily (https://invent.kde.org/teams/plasma-mobile/issues/-/issues/302).
This commit is contained in:
parent
b426a7d59e
commit
5d84e6e47d
9 changed files with 659 additions and 160 deletions
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||
*
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
|
|
@ -18,25 +18,25 @@ import org.kde.coreaddons 1.0 as KCoreAddons
|
|||
|
||||
Item {
|
||||
id: notificationItem
|
||||
|
||||
|
||||
required property var notificationsModel
|
||||
|
||||
|
||||
required property int notificationsModelType
|
||||
|
||||
|
||||
/**
|
||||
* Whether the notification is allowed to invoke any action, or if it should instead
|
||||
* emit the runActionRequested(action) signal, containing the code to run.
|
||||
*
|
||||
*
|
||||
* This is useful for cases like the lockscreen, where actions should only be run after
|
||||
* the user logs in.
|
||||
*/
|
||||
property bool requestToInvoke: false
|
||||
|
||||
|
||||
property var model
|
||||
property int modelIndex
|
||||
|
||||
|
||||
property P5Support.DataSource timeSource
|
||||
|
||||
|
||||
readonly property int notificationType: model.type
|
||||
|
||||
readonly property bool inGroup: model.isInGroup || false
|
||||
|
|
@ -54,7 +54,7 @@ Item {
|
|||
readonly property string replyPlaceholderText: model.replyPlaceholderText || ""
|
||||
readonly property string replySubmitButtonText: model.replySubmitButtonText || ""
|
||||
readonly property string replySubmitButtonIconName: model.replySubmitButtonIconName || ""
|
||||
|
||||
|
||||
// configure button on every single notifications is a bit overwhelming
|
||||
readonly property bool configurable: !inGroup && model.configurable
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ Item {
|
|||
readonly property int jobError: model.jobError || 0
|
||||
readonly property bool suspendable: !!model.suspendable
|
||||
readonly property bool killable: !!model.killable
|
||||
|
||||
|
||||
readonly property QtObject jobDetails: model.jobDetails || null
|
||||
|
||||
readonly property string configureActionLabel: model.configureActionLabel || ""
|
||||
|
|
@ -97,13 +97,13 @@ Item {
|
|||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This signal is emitted and intended for the parent to make its own decision
|
||||
* on whether to run the requested notification action.
|
||||
*/
|
||||
signal runActionRequested()
|
||||
|
||||
|
||||
signal actionInvoked(string actionName)
|
||||
signal replied(string text)
|
||||
signal openUrl(string url)
|
||||
|
|
@ -112,7 +112,7 @@ Item {
|
|||
signal suspendJobClicked
|
||||
signal resumeJobClicked
|
||||
signal killJobClicked
|
||||
|
||||
|
||||
function expire() {
|
||||
if (model.resident) {
|
||||
model.expired = true;
|
||||
|
|
@ -132,17 +132,17 @@ Item {
|
|||
notificationsModel.close(notificationsModel.index(modelIndex, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO call
|
||||
function configure() {
|
||||
notificationsModel.configure(notificationsModel.index(modelIndex, 0))
|
||||
}
|
||||
|
||||
|
||||
property var pendingAction: () => {}
|
||||
function runPendingAction() {
|
||||
pendingAction();
|
||||
}
|
||||
|
||||
|
||||
onActionInvoked: {
|
||||
let action = () => {
|
||||
if (notificationsModelType === NotificationsModelType.WatchedNotificationsModel) {
|
||||
|
|
@ -153,14 +153,14 @@ Item {
|
|||
}
|
||||
} else if (notificationsModelType === NotificationsModelType.NotificationsModel) {
|
||||
if (actionName === "default") {
|
||||
notificationsModel.invokeDefaultAction(notificationsModel.index(modelIndex, 0), NotificationManager.Close); // notification closes
|
||||
notificationsModel.invokeDefaultAction(notificationsModel.index(modelIndex, 0), NotificationManager.Close); // notification closes
|
||||
} else {
|
||||
notificationsModel.invokeAction(notificationsModel.index(modelIndex, 0), actionName, NotificationManager.Close); // notification closes
|
||||
notificationsModel.invokeAction(notificationsModel.index(modelIndex, 0), actionName, NotificationManager.Close); // notification closes
|
||||
}
|
||||
}
|
||||
expire();
|
||||
}
|
||||
|
||||
|
||||
if (notificationItem.requestToInvoke) {
|
||||
pendingAction = action;
|
||||
runActionRequested();
|
||||
|
|
@ -168,13 +168,13 @@ Item {
|
|||
action();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onOpenUrl: {
|
||||
let action = () => {
|
||||
Qt.openUrlExternally(url);
|
||||
expire();
|
||||
}
|
||||
|
||||
|
||||
if (notificationItem.requestToInvoke) {
|
||||
pendingAction = action;
|
||||
runActionRequested();
|
||||
|
|
@ -182,7 +182,7 @@ Item {
|
|||
action();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onFileActionInvoked: {
|
||||
let action = () => {
|
||||
if (action.objectName === "movetotrash" || action.objectName === "deletefile") {
|
||||
|
|
@ -191,7 +191,7 @@ Item {
|
|||
expire();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (notificationItem.requestToInvoke) {
|
||||
pendingAction = action;
|
||||
runActionRequested();
|
||||
|
|
@ -199,10 +199,10 @@ Item {
|
|||
action();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onSuspendJobClicked: {
|
||||
let action = () => notificationsModel.suspendJob(notificationsModel.index(modelIndex, 0));
|
||||
|
||||
|
||||
if (notificationItem.requestToInvoke) {
|
||||
pendingAction = action;
|
||||
runActionRequested();
|
||||
|
|
@ -210,10 +210,10 @@ Item {
|
|||
action();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onResumeJobClicked: {
|
||||
let action = () => notificationsModel.resumeJob(notificationsModel.index(modelIndex, 0));
|
||||
|
||||
|
||||
if (notificationItem.requestToInvoke) {
|
||||
pendingAction = action;
|
||||
runActionRequested();
|
||||
|
|
@ -221,10 +221,10 @@ Item {
|
|||
action();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onKillJobClicked: {
|
||||
let action = () => notificationsModel.killJob(notificationsModel.index(modelIndex, 0));
|
||||
|
||||
|
||||
if (notificationItem.requestToInvoke) {
|
||||
pendingAction = action;
|
||||
runActionRequested();
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
* SPDX-FileCopyrightText: 2011 Marco Martin <notmart@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2014, 2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||
*
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
|
||||
|
|
|
|||
|
|
@ -10,52 +10,54 @@ import org.kde.plasma.components 3.0 as PlasmaComponents
|
|||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
|
||||
default property Item contentItem
|
||||
|
||||
|
||||
property bool tapEnabled: false
|
||||
|
||||
|
||||
property bool swipeGestureEnabled: false
|
||||
|
||||
|
||||
property real dragOffset: 0
|
||||
|
||||
|
||||
signal tapped()
|
||||
signal dismissRequested()
|
||||
signal configureClicked() // TODO implement settings button
|
||||
|
||||
|
||||
onContentItemChanged: {
|
||||
contentItem.parent = contentParent;
|
||||
contentItem.anchors.fill = contentParent;
|
||||
contentItem.anchors.margins = Kirigami.Units.largeSpacing;
|
||||
contentParent.children.push(contentItem);
|
||||
}
|
||||
|
||||
|
||||
implicitHeight: contentParent.implicitHeight
|
||||
|
||||
|
||||
NumberAnimation on dragOffset {
|
||||
id: dragAnim
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.OutCubic
|
||||
onFinished: {
|
||||
if (to !== 0) {
|
||||
root.dismissRequested();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// shadow
|
||||
MultiEffect {
|
||||
anchors.fill: mainCard
|
||||
visible: Math.abs(dragOffset) !== root.width
|
||||
source: mainCard
|
||||
source: simpleShadow
|
||||
blurMax: 16
|
||||
shadowEnabled: true
|
||||
shadowVerticalOffset: 1
|
||||
shadowOpacity: 0.5
|
||||
shadowColor: Qt.lighter(Kirigami.Theme.backgroundColor, 0.1)
|
||||
shadowOpacity: 0.3
|
||||
shadowColor: Qt.lighter(Kirigami.Theme.backgroundColor, 0.2)
|
||||
}
|
||||
|
||||
// shadow
|
||||
Rectangle {
|
||||
id: simpleShadow
|
||||
visible: Math.abs(dragOffset) !== root.width
|
||||
anchors.fill: mainCard
|
||||
anchors.leftMargin: -1
|
||||
|
|
@ -74,12 +76,14 @@ Item {
|
|||
anchors.right: parent.right
|
||||
anchors.rightMargin: root.dragOffset < 0 ? -root.dragOffset : 0
|
||||
anchors.top: parent.top
|
||||
|
||||
|
||||
color: (root.tapEnabled && mouseArea.pressed) ? Qt.darker(Kirigami.Theme.backgroundColor, 1.1) : Kirigami.Theme.backgroundColor
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
implicitHeight: contentParent.implicitHeight
|
||||
clip: true
|
||||
|
||||
|
||||
// clip
|
||||
layer.enabled: true
|
||||
|
||||
// ensure this is behind the content to not interfere
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
|
@ -90,28 +94,28 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// content parent
|
||||
Item {
|
||||
id: contentParent
|
||||
anchors.top: parent.top
|
||||
anchors.left: root.dragOffset > 0 ? parent.left : undefined
|
||||
anchors.right: root.dragOffset < 0 ? parent.right : undefined
|
||||
|
||||
|
||||
width: root.width
|
||||
implicitHeight: contentItem.implicitHeight + contentItem.anchors.topMargin + contentItem.anchors.bottomMargin
|
||||
implicitHeight: contentItem.implicitHeight + contentItem.anchors.topMargin + contentItem.anchors.bottomMargin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DragHandler {
|
||||
id: dragHandler
|
||||
enabled: root.swipeGestureEnabled
|
||||
yAxis.enabled: false
|
||||
|
||||
|
||||
property real startDragOffset: 0
|
||||
property real startPosition: 0
|
||||
property bool startActive: false
|
||||
|
||||
|
||||
onTranslationChanged: {
|
||||
if (startActive) {
|
||||
startDragOffset = root.dragOffset;
|
||||
|
|
@ -120,11 +124,11 @@ Item {
|
|||
}
|
||||
root.dragOffset = startDragOffset + (translation.x - startPosition);
|
||||
}
|
||||
|
||||
|
||||
onActiveChanged: {
|
||||
dragAnim.stop();
|
||||
startActive = active;
|
||||
|
||||
|
||||
if (!active) { // release event
|
||||
let threshold = Kirigami.Units.gridUnit * 5; // drag threshold
|
||||
if (root.dragOffset > threshold) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
import QtQuick 2.8
|
||||
|
||||
import org.kde.plasma.extras 2.0 as PlasmaExtras
|
||||
|
||||
import org.kde.kquickcontrolsaddons 2.0 as KQCAddons
|
||||
|
||||
PlasmaExtras.Menu {
|
||||
id: contextMenu
|
||||
|
||||
signal closed
|
||||
|
||||
property QtObject __clipboard: KQCAddons.Clipboard { }
|
||||
|
||||
// can be a Text or TextEdit
|
||||
property Item target
|
||||
|
||||
property string link
|
||||
|
||||
onStatusChanged: {
|
||||
if (status === PlasmaExtras.Menu.Closed) {
|
||||
closed();
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaExtras.MenuItem {
|
||||
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "@action:inmenu", "Copy Link Address")
|
||||
icon: "edit-copy-symbolic"
|
||||
onClicked: __clipboard.content = contextMenu.link
|
||||
visible: contextMenu.link !== ""
|
||||
}
|
||||
|
||||
PlasmaExtras.MenuItem {
|
||||
separator: true
|
||||
visible: contextMenu.link !== ""
|
||||
}
|
||||
|
||||
PlasmaExtras.MenuItem {
|
||||
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "@action:inmenu", "Copy")
|
||||
icon: "edit-copy-symbolic"
|
||||
enabled: typeof target.selectionStart !== "undefined"
|
||||
? target.selectionStart !== target.selectionEnd
|
||||
: (target.text || "").length > 0
|
||||
onClicked: {
|
||||
if (typeof target.copy === "function") {
|
||||
target.copy();
|
||||
} else {
|
||||
__clipboard.content = target.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaExtras.MenuItem {
|
||||
id: selectAllAction
|
||||
icon: "edit-select-all-symbolic"
|
||||
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "@action:inmenu", "Select All")
|
||||
onClicked: target.selectAll()
|
||||
visible: typeof target.selectAll === "function"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||
*
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
|
|
@ -20,21 +20,11 @@ import org.kde.coreaddons 1.0 as KCoreAddons
|
|||
|
||||
RowLayout {
|
||||
id: notificationHeading
|
||||
property int notificationType
|
||||
|
||||
property var applicationIconSource
|
||||
property string applicationName
|
||||
property string originName
|
||||
|
||||
property var time
|
||||
property P5Support.DataSource timeSource
|
||||
|
||||
property int jobState
|
||||
property QtObject jobDetails
|
||||
|
||||
property real timeout: 5000
|
||||
property real remainingTime: 0
|
||||
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
Layout.preferredHeight: Math.max(applicationNameLabel.implicitHeight, Kirigami.Units.iconSizes.small)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||
*
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ import org.kde.coreaddons 1.0 as KCoreAddons
|
|||
BaseNotificationItem {
|
||||
id: notificationItem
|
||||
implicitHeight: mainCard.implicitHeight + mainCard.anchors.topMargin + notificationHeading.height
|
||||
|
||||
|
||||
// notification heading for groups with one element
|
||||
NotificationGroupHeader {
|
||||
id: notificationHeading
|
||||
|
|
@ -40,15 +40,8 @@ BaseNotificationItem {
|
|||
applicationName: notificationItem.applicationName
|
||||
applicationIconSource: notificationItem.applicationIconSource
|
||||
originName: notificationItem.originName
|
||||
|
||||
notificationType: notificationItem.notificationType
|
||||
jobState: notificationItem.jobState
|
||||
jobDetails: notificationItem.jobDetails
|
||||
|
||||
time: notificationItem.time
|
||||
timeSource: notificationItem.timeSource
|
||||
}
|
||||
|
||||
|
||||
// notification
|
||||
NotificationCard {
|
||||
id: mainCard
|
||||
|
|
@ -56,21 +49,21 @@ BaseNotificationItem {
|
|||
anchors.top: notificationHeading.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
|
||||
tapEnabled: notificationItem.hasDefaultAction
|
||||
onTapped: notificationItem.actionInvoked("default");
|
||||
swipeGestureEnabled: notificationItem.notificationType != NotificationManager.Notifications.JobType
|
||||
swipeGestureEnabled: notificationItem.closable
|
||||
onDismissRequested: notificationItem.close()
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
spacing: 0
|
||||
|
||||
|
||||
// notification summary row
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
|
||||
|
||||
// notification summary
|
||||
PlasmaComponents.Label {
|
||||
id: summaryLabel
|
||||
|
|
@ -83,19 +76,19 @@ BaseNotificationItem {
|
|||
visible: text !== ""
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
|
||||
|
||||
// notification timestamp
|
||||
NotificationTimeText {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
notificationType: notificationItem.notificationType
|
||||
jobState: notificationItem.jobState
|
||||
jobDetails: notificationItem.jobDetails
|
||||
|
||||
|
||||
time: notificationItem.time
|
||||
timeSource: notificationItem.timeSource
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// notification contents
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -105,17 +98,11 @@ BaseNotificationItem {
|
|||
NotificationBodyLabel {
|
||||
id: bodyLabel
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
|
||||
// HACK RichText does not allow to specify link color and since LineEdit
|
||||
// does not support StyledText, we have to inject some CSS to force the color,
|
||||
// cf. QTBUG-81463 and to some extent QTBUG-80354
|
||||
text: "<style>a { color: " + Kirigami.Theme.linkColor + "; }</style>" + notificationItem.body
|
||||
Layout.preferredWidth: column.width - iconContainer.width - Kirigami.Units.smallSpacing
|
||||
|
||||
// Cannot do text !== "" because RichText adds some HTML tags even when empty
|
||||
visible: notificationItem.body !== ""
|
||||
text: notificationItem.body
|
||||
}
|
||||
|
||||
|
||||
// notification icon
|
||||
Item {
|
||||
id: iconContainer
|
||||
|
|
@ -138,13 +125,41 @@ BaseNotificationItem {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Job progress reporting
|
||||
Loader {
|
||||
id: jobLoader
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: item ? item.implicitHeight : 0
|
||||
active: notificationItem.notificationType === NotificationManager.Notifications.JobType
|
||||
visible: active
|
||||
sourceComponent: NotificationJobItem {
|
||||
iconContainerItem: iconContainer
|
||||
|
||||
jobState: notificationItem.jobState
|
||||
jobError: notificationItem.jobError
|
||||
percentage: notificationItem.percentage
|
||||
suspendable: notificationItem.suspendable
|
||||
killable: notificationItem.killable
|
||||
|
||||
jobDetails: notificationItem.jobDetails
|
||||
|
||||
onSuspendJobClicked: notificationItem.suspendJobClicked()
|
||||
onResumeJobClicked: notificationItem.resumeJobClicked()
|
||||
onKillJobClicked: notificationItem.killJobClicked()
|
||||
|
||||
onOpenUrl: notificationItem.openUrl(url)
|
||||
onFileActionInvoked: notificationItem.fileActionInvoked(action)
|
||||
}
|
||||
}
|
||||
|
||||
// notification actions
|
||||
NotificationFooterActions {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
notification: notificationItem
|
||||
}
|
||||
|
||||
|
||||
// thumbnails
|
||||
Loader {
|
||||
id: thumbnailStripLoader
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
// SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||
// SPDX-FileCopyrightText: 2024 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Layouts 1.1
|
||||
|
||||
import org.kde.plasma.extras 2.0 as PlasmaExtras
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.coreaddons 1.0 as KCoreAddons
|
||||
|
||||
GridLayout {
|
||||
id: detailsGrid
|
||||
|
||||
property QtObject jobDetails
|
||||
|
||||
columns: 2
|
||||
rowSpacing: Math.round(Kirigami.Units.smallSpacing / 2)
|
||||
columnSpacing: Kirigami.Units.smallSpacing
|
||||
|
||||
// once you use Layout.column/Layout.row *all* of the items in the Layout have to use them
|
||||
Repeater {
|
||||
model: [1, 2]
|
||||
|
||||
PlasmaExtras.DescriptiveLabel {
|
||||
Layout.column: 0
|
||||
Layout.row: index
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignRight
|
||||
text: jobDetails["descriptionLabel" + modelData] && jobDetails["descriptionValue" + modelData]
|
||||
? i18ndc("plasma_applet_org.kde.plasma.notifications", "Row description, e.g. Source", "%1:", jobDetails["descriptionLabel" + modelData]) : ""
|
||||
font: Kirigami.Theme.smallFont
|
||||
textFormat: Text.PlainText
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: [1, 2]
|
||||
|
||||
PlasmaExtras.DescriptiveLabel {
|
||||
id: descriptionValueLabel
|
||||
Layout.column: 1
|
||||
Layout.row: index
|
||||
Layout.fillWidth: true
|
||||
font: Kirigami.Theme.smallFont
|
||||
elide: Text.ElideMiddle
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
verticalAlignment: Text.AlignTop
|
||||
maximumLineCount: 5
|
||||
visible: text !== ""
|
||||
|
||||
// Only let the label grow, never shrink, to avoid repeatedly resizing the dialog when copying many files
|
||||
onImplicitHeightChanged: {
|
||||
if (implicitHeight > Layout.preferredHeight) {
|
||||
Layout.preferredHeight = implicitHeight;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: bindText()
|
||||
function bindText() {
|
||||
text = Qt.binding(function() {
|
||||
return jobDetails["descriptionValue" + modelData] || "";
|
||||
});
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onPressed: mouse => {
|
||||
// break binding so it doesn't update while the menu is opened
|
||||
descriptionValueLabel.text = descriptionValueLabel.text;
|
||||
descriptionValueMenu.open(mouse.x, mouse.y)
|
||||
}
|
||||
}
|
||||
|
||||
NotificationEditContextMenu {
|
||||
id: descriptionValueMenu
|
||||
target: descriptionValueLabel
|
||||
// defer re-binding until after the "Copy" action in the menu has triggered
|
||||
onClosed: Qt.callLater(descriptionValueLabel.bindText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ["Bytes", "Files", "Directories", "Items"]
|
||||
|
||||
PlasmaExtras.DescriptiveLabel {
|
||||
Layout.column: 1
|
||||
Layout.row: 2 + index
|
||||
Layout.fillWidth: true
|
||||
text: {
|
||||
var processed = jobDetails["processed" + modelData];
|
||||
var total = jobDetails["total" + modelData];
|
||||
|
||||
if (processed > 0 || total > 1) {
|
||||
if (processed > 0 && total > 0 && processed <= total) {
|
||||
switch(modelData) {
|
||||
case "Bytes":
|
||||
return i18ndc("plasma_applet_org.kde.plasma.notifications", "How many bytes have been copied", "%2 of %1",
|
||||
KCoreAddons.Format.formatByteSize(total),
|
||||
KCoreAddons.Format.formatByteSize(processed))
|
||||
case "Files":
|
||||
return i18ndcp("plasma_applet_org.kde.plasma.notifications", "How many files have been copied", "%2 of %1 file", "%2 of %1 files",
|
||||
total, processed);
|
||||
case "Directories":
|
||||
return i18ndcp("plasma_applet_org.kde.plasma.notifications", "How many dirs have been copied", "%2 of %1 folder", "%2 of %1 folders",
|
||||
total, processed);
|
||||
case "Items":
|
||||
return i18ndcp("plasma_applet_org.kde.plasma.notifications", "How many items (that includes files and dirs) have been copied", "%2 of %1 item", "%2 of %1 items",
|
||||
total, processed);
|
||||
}
|
||||
} else {
|
||||
switch(modelData) {
|
||||
case "Bytes":
|
||||
return KCoreAddons.Format.formatByteSize(processed || total)
|
||||
case "Files":
|
||||
return i18ndp("plasma_applet_org.kde.plasma.notifications", "%1 file", "%1 files", (processed || total));
|
||||
case "Directories":
|
||||
return i18ndp("plasma_applet_org.kde.plasma.notifications", "%1 folder", "%1 folders", (processed || total));
|
||||
case "Items":
|
||||
return i18ndp("plasma_applet_org.kde.plasma.notifications", "%1 item", "%1 items", (processed || total));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
font: Kirigami.Theme.smallFont
|
||||
textFormat: Text.PlainText
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaExtras.DescriptiveLabel {
|
||||
Layout.column: 1
|
||||
Layout.row: 2 + 4
|
||||
Layout.fillWidth: true
|
||||
text: jobDetails.speed > 0 ? i18ndc("plasma_applet_org.kde.plasma.notifications", "Bytes per second", "%1/s",
|
||||
KCoreAddons.Format.formatByteSize(jobDetails.speed)) : ""
|
||||
font: Kirigami.Theme.smallFont
|
||||
textFormat: Text.PlainText
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
// SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||
// SPDX-FileCopyrightText: 2024 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQml 2.15
|
||||
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents3
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.notificationmanager as NotificationManager
|
||||
|
||||
import org.kde.plasma.private.notifications 2.0 as Notifications
|
||||
|
||||
ColumnLayout {
|
||||
id: jobItem
|
||||
|
||||
property int jobState
|
||||
property int jobError
|
||||
|
||||
property alias percentage: progressBar.value
|
||||
property alias suspendable: suspendButton.visible
|
||||
property alias killable: killButton.visible
|
||||
|
||||
property QtObject jobDetails
|
||||
|
||||
readonly property int totalFiles: jobItem.jobDetails && jobItem.jobDetails.totalFiles || 0
|
||||
readonly property var url: {
|
||||
if (jobItem.jobState !== NotificationManager.Notifications.JobStateStopped
|
||||
|| jobItem.jobError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// For a single file show actions for it
|
||||
// Otherwise the destination folder all of them were copied into
|
||||
const url = totalFiles === 1 ? jobItem.jobDetails.descriptionUrl
|
||||
: jobItem.jobDetails.destUrl;
|
||||
|
||||
// Don't offer opening files in Trash
|
||||
if (url && url.toString().startsWith("trash:")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
property alias iconContainerItem: busyIndicatorContainer.parent
|
||||
|
||||
readonly property alias menuOpen: otherFileActionsMenu.visible
|
||||
|
||||
signal suspendJobClicked
|
||||
signal resumeJobClicked
|
||||
signal killJobClicked
|
||||
|
||||
signal openUrl(string url)
|
||||
signal fileActionInvoked(QtObject action)
|
||||
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Notifications.FileInfo {
|
||||
id: fileInfo
|
||||
url: jobItem.totalFiles === 1 && jobItem.url ? jobItem.url : ""
|
||||
}
|
||||
|
||||
Item {
|
||||
id: busyIndicatorContainer
|
||||
width: parent ? parent.width : 0
|
||||
height: parent ? parent.height : 0
|
||||
|
||||
PlasmaComponents3.BusyIndicator {
|
||||
id: busyIndicator
|
||||
anchors.centerIn: parent
|
||||
running: fileInfo.busy && !delayBusyTimer.running
|
||||
visible: running
|
||||
|
||||
// Avoid briefly flashing the busy indicator
|
||||
Timer {
|
||||
id: delayBusyTimer
|
||||
interval: 500
|
||||
repeat: false
|
||||
running: fileInfo.busy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: progressRow
|
||||
Layout.fillWidth: true
|
||||
// Even when indeterminate, we want to reserve the height for the text, otherwise it's too tightly spaced
|
||||
Layout.minimumHeight: progressText.implicitHeight
|
||||
// We want largeSpacing between the progress bar and the label
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
PlasmaComponents3.ProgressBar {
|
||||
id: progressBar
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 100
|
||||
// TODO do we actually need the window visible check? perhaps I do because it can be in popup or expanded plasmoid
|
||||
indeterminate: visible && Window.window && Window.window.visible && percentage < 1
|
||||
&& jobItem.jobState === NotificationManager.Notifications.JobStateRunning
|
||||
// is this too annoying?
|
||||
&& (jobItem.jobDetails.processedBytes === 0 || jobItem.jobDetails.totalBytes === 0)
|
||||
&& jobItem.jobDetails.processedFiles === 0
|
||||
//&& jobItem.jobDetails.processedDirectories === 0
|
||||
}
|
||||
|
||||
PlasmaComponents3.Label {
|
||||
id: progressText
|
||||
|
||||
visible: !progressBar.indeterminate
|
||||
// the || "0" is a workaround for the fact that 0 as number is falsey, and is wrongly considered a missing argument
|
||||
// BUG: 451807
|
||||
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "Percentage of a job", "%1%", jobItem.percentage || "0")
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: jobActionsRow
|
||||
Layout.fillWidth: true
|
||||
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
PlasmaComponents3.Button {
|
||||
id: expandButton
|
||||
|
||||
icon.name: checked ? "collapse-symbolic" : "expand-symbolic"
|
||||
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "Hides/expands item details", "Details")
|
||||
checkable: true
|
||||
enabled: jobItem.jobDetails && jobItem.jobDetails.hasDetails
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
PlasmaComponents3.Button {
|
||||
id: suspendButton
|
||||
|
||||
icon.name: "media-playback-pause-symbolic"
|
||||
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "Pause running job", "Pause")
|
||||
onClicked: jobItem.jobState === NotificationManager.Notifications.JobStateSuspended ? jobItem.resumeJobClicked()
|
||||
: jobItem.suspendJobClicked()
|
||||
}
|
||||
|
||||
PlasmaComponents3.Button {
|
||||
id: killButton
|
||||
|
||||
icon.name: "dialog-cancel-symbolic"
|
||||
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "Cancel running job", "Cancel")
|
||||
onClicked: jobItem.killJobClicked()
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: item ? item.implicitHeight : 0
|
||||
active: expandButton.checked
|
||||
// Loader doesn't reset its height when unloaded, just hide it altogether
|
||||
visible: active
|
||||
sourceComponent: NotificationJobDetails {
|
||||
jobDetails: jobItem.jobDetails
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: fileActionsRow
|
||||
Layout.fillWidth: true
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
// We want the actions to be right-aligned but Row also reverses
|
||||
// the order of items, so we put them in reverse order
|
||||
layoutDirection: Qt.RightToLeft
|
||||
visible: jobItem.url && jobItem.url.toString() !== "" && !fileInfo.error
|
||||
|
||||
PlasmaComponents3.Button {
|
||||
id: otherFileActionsButton
|
||||
height: Math.max(implicitHeight, openButton.implicitHeight)
|
||||
icon.name: "application-menu-symbolic"
|
||||
checkable: true
|
||||
text: openButton.visible ? "" : Accessible.name
|
||||
Accessible.name: i18nd("plasma_applet_org.kde.plasma.notifications", "More Options…")
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
checked = Qt.binding(function() {
|
||||
return otherFileActionsMenu.visible;
|
||||
});
|
||||
otherFileActionsMenu.visualParent = this;
|
||||
// -1 tells it to "align bottom left of visualParent (this)"
|
||||
otherFileActionsMenu.open(-1, -1);
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaComponents3.ToolTip {
|
||||
text: parent.Accessible.name
|
||||
enabled: parent.text === ""
|
||||
}
|
||||
|
||||
Notifications.FileMenu {
|
||||
id: otherFileActionsMenu
|
||||
url: jobItem.url || ""
|
||||
onActionTriggered: jobItem.fileActionInvoked(action)
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaComponents3.Button {
|
||||
id: openButton
|
||||
width: Math.min(implicitWidth, jobItem.width - otherFileActionsButton.width - fileActionsRow.spacing)
|
||||
height: Math.max(implicitHeight, otherFileActionsButton.implicitHeight)
|
||||
text: i18nd("plasma_applet_org.kde.plasma.notifications", "Open")
|
||||
onClicked: jobItem.openUrl(jobItem.url)
|
||||
|
||||
states: [
|
||||
State {
|
||||
when: jobItem.jobDetails && jobItem.jobDetails.totalFiles !== 1
|
||||
PropertyChanges {
|
||||
target: openButton
|
||||
text: i18nd("plasma_applet_org.kde.plasma.notifications", "Open Containing Folder")
|
||||
icon.name: "folder-open-symbolic"
|
||||
}
|
||||
},
|
||||
State {
|
||||
when: fileInfo.openAction
|
||||
PropertyChanges {
|
||||
target: openButton
|
||||
text: fileInfo.openAction.text
|
||||
icon.name: fileInfo.openActionIconName
|
||||
visible: fileInfo.openAction.enabled
|
||||
onClicked: {
|
||||
fileInfo.openAction.trigger();
|
||||
jobItem.fileActionInvoked(fileInfo.openAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
when: jobItem.jobState === NotificationManager.Notifications.JobStateRunning
|
||||
PropertyChanges {
|
||||
target: suspendButton
|
||||
// Explicitly set it to false so it unchecks when pausing from applet
|
||||
// and then the job unpauses programmatically elsewhere.
|
||||
checked: false
|
||||
}
|
||||
},
|
||||
State {
|
||||
when: jobItem.jobState === NotificationManager.Notifications.JobStateSuspended
|
||||
PropertyChanges {
|
||||
target: suspendButton
|
||||
checked: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: progressBar
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
State {
|
||||
when: jobItem.jobState === NotificationManager.Notifications.JobStateStopped
|
||||
PropertyChanges {
|
||||
target: progressRow
|
||||
visible: false
|
||||
}
|
||||
PropertyChanges {
|
||||
target: jobActionsRow
|
||||
visible: false
|
||||
}
|
||||
PropertyChanges {
|
||||
target: expandButton
|
||||
checked: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -26,69 +26,69 @@ import org.kde.notificationmanager as NotificationManager
|
|||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
|
||||
/**
|
||||
* The notification model for the widget.
|
||||
*/
|
||||
property var historyModel
|
||||
|
||||
|
||||
/**
|
||||
* The type of notification model used for the widget.
|
||||
*/
|
||||
property int historyModelType
|
||||
|
||||
|
||||
/**
|
||||
* The notification model settings for the widget.
|
||||
*/
|
||||
property var notificationSettings
|
||||
|
||||
|
||||
/**
|
||||
* Whether invoking notification actions requires authentiation of some sort.
|
||||
*
|
||||
*
|
||||
* If set to true, any attempted invoking will trigger the unlockRequested() signal.
|
||||
* Any consumers can then call the runPendingAction() function if authenticated to proceed
|
||||
* executing the notification action.
|
||||
*/
|
||||
property bool actionsRequireUnlock: false
|
||||
|
||||
|
||||
/**
|
||||
* Whether the widget has notifications.
|
||||
*/
|
||||
readonly property bool hasNotifications: list.count > 0
|
||||
|
||||
|
||||
readonly property bool doNotDisturbModeEnabled: !isNaN(notificationSettings.notificationsInhibitedUntil)
|
||||
|
||||
|
||||
enum ModelType {
|
||||
NotificationsModel, // used in the logged-in shell
|
||||
WatchedNotificationsModel // used on the lockscreen
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Signal emitted when authentication is requested for an action.
|
||||
* Listeners should call runPendingAction() if authentication is successful.
|
||||
*
|
||||
*
|
||||
* Only emitted if actionsRequireUnlock is enabled.
|
||||
*/
|
||||
signal unlockRequested()
|
||||
|
||||
|
||||
/**
|
||||
* Emitted when the background is clicked (not a notification or other element).
|
||||
*/
|
||||
signal backgroundClicked()
|
||||
|
||||
|
||||
/**
|
||||
* Run pending action that was pending for authentication when unlockRequested() was emitted.
|
||||
*/
|
||||
function runPendingAction() {
|
||||
list.pendingNotificationWithAction.runPendingAction();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clears the history of the notification model.
|
||||
*/
|
||||
function clearHistory() {
|
||||
historyModel.clear(NotificationManager.Notifications.ClearExpired);
|
||||
|
||||
|
||||
if (historyModel.count === 0) {
|
||||
backgroundClicked();
|
||||
}
|
||||
|
|
@ -102,15 +102,15 @@ Item {
|
|||
notificationSettings.defaults();
|
||||
} else {
|
||||
var until = new Date();
|
||||
|
||||
|
||||
until.setFullYear(until.getFullYear() + 1);
|
||||
|
||||
|
||||
notificationSettings.notificationsInhibitedUntil = until;
|
||||
}
|
||||
|
||||
|
||||
notificationSettings.save();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Open the system notification settings.
|
||||
*/
|
||||
|
|
@ -125,7 +125,7 @@ Item {
|
|||
interval: 60000 // 1 min
|
||||
intervalAlignment: P5Support.Types.AlignToMinute
|
||||
}
|
||||
|
||||
|
||||
// implement background clicking signal
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
|
@ -136,40 +136,40 @@ Item {
|
|||
ListView {
|
||||
id: list
|
||||
model: historyModel
|
||||
|
||||
|
||||
clip: true
|
||||
|
||||
|
||||
currentIndex: 0
|
||||
|
||||
|
||||
property var pendingNotificationWithAction
|
||||
|
||||
readonly property int animationDuration: ShellSettings.Settings.animationsEnabled ? Kirigami.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 + spacing >= root.height
|
||||
|
||||
|
||||
bottomMargin: spacing
|
||||
height: count === 0 ? 0 : (listOverflowing ? root.height - toolButtons.height : contentItem.childrenRect.height + bottomMargin)
|
||||
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
// TODO keyboard focus
|
||||
highlightMoveDuration: 0
|
||||
highlightResizeDuration: 0
|
||||
highlight: Item {}
|
||||
highlight: Item {}
|
||||
|
||||
section {
|
||||
property: "isGroup"
|
||||
criteria: ViewSection.FullString
|
||||
}
|
||||
|
||||
|
||||
PlasmaExtras.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - (Kirigami.Units.gridUnit * 4)
|
||||
|
|
@ -187,7 +187,7 @@ Item {
|
|||
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 {
|
||||
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: list.animationDuration }
|
||||
|
|
@ -196,7 +196,7 @@ Item {
|
|||
displaced: Transition {
|
||||
NumberAnimation { properties: "y"; duration: list.animationDuration }
|
||||
}
|
||||
|
||||
|
||||
function isRowExpanded(row) {
|
||||
var idx = historyModel.index(row, 0);
|
||||
return historyModel.data(idx, NotificationManager.Notifications.IsGroupExpandedRole);
|
||||
|
|
@ -219,90 +219,89 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Instantly re-align items after group expansion.
|
||||
forceLayout();
|
||||
}
|
||||
|
||||
|
||||
delegate: Loader {
|
||||
id: delegateLoader
|
||||
|
||||
|
||||
anchors {
|
||||
left: parent ? parent.left : undefined
|
||||
leftMargin: Kirigami.Units.gridUnit
|
||||
right: parent ? parent.right : undefined
|
||||
rightMargin: Kirigami.Units.gridUnit
|
||||
}
|
||||
|
||||
|
||||
height: model.isGroup ? groupDelegate.height : notificationDelegate.height
|
||||
sourceComponent: model.isGroup ? groupDelegate : notificationDelegate
|
||||
asynchronous: true
|
||||
|
||||
|
||||
required property var model
|
||||
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 {
|
||||
id: groupDelegate
|
||||
NotificationGroupHeader {
|
||||
NotificationGroupHeader {
|
||||
applicationName: model.applicationName
|
||||
applicationIconSource: model.applicationIconName
|
||||
originName: model.originName || ""
|
||||
timeSource: timeDataSource
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: notificationDelegate
|
||||
|
||||
|
||||
Column {
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
|
||||
height: notificationItem.height + showMoreLoader.height
|
||||
|
||||
|
||||
NotificationItem {
|
||||
id: notificationItem
|
||||
width: parent.width
|
||||
height: implicitHeight
|
||||
|
||||
|
||||
model: delegateLoader.model
|
||||
modelIndex: delegateLoader.index
|
||||
notificationsModel: root.historyModel
|
||||
notificationsModelType: root.historyModelType
|
||||
timeSource: timeDataSource
|
||||
|
||||
|
||||
requestToInvoke: root.actionsRequireUnlock
|
||||
onRunActionRequested: {
|
||||
list.pendingNotificationWithAction = notificationItem;
|
||||
root.unlockRequested();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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 {
|
||||
id: showMoreLoader
|
||||
|
||||
|
||||
height: visible ? implicitHeight : 0
|
||||
opacity: 0.0
|
||||
visible: active
|
||||
|
||||
|
||||
asynchronous: true
|
||||
|
||||
|
||||
active: {
|
||||
// if we have the WatchedNotificationsModel, we don't have notification grouping support
|
||||
if (typeof model.groupChildrenCount === 'undefined')
|
||||
return false;
|
||||
|
||||
|
||||
return (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded)
|
||||
&& 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"
|
||||
|
|
@ -316,7 +315,7 @@ Item {
|
|||
NumberAnimation { properties: "opacity"; duration: list.animationDuration }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sourceComponent: PlasmaComponents3.ToolButton {
|
||||
icon.name: model.isGroupExpanded ? "arrow-up" : "arrow-down"
|
||||
text: model.isGroupExpanded ? i18n("Show Fewer")
|
||||
|
|
@ -331,31 +330,31 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
id: toolButtons
|
||||
height: visible ? spacer.height + toolLayout.height + toolLayout.anchors.topMargin + toolLayout.anchors.bottomMargin : 0
|
||||
|
||||
|
||||
// do not show on lockscreen
|
||||
visible: !root.actionsRequireUnlock
|
||||
|
||||
|
||||
anchors {
|
||||
top: list.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: spacer
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
|
||||
visible: list.listOverflowing
|
||||
height: 1
|
||||
height: 1
|
||||
opacity: 0.25
|
||||
color: Kirigami.Theme.textColor
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
id: toolLayout
|
||||
|
||||
|
|
@ -368,17 +367,17 @@ Item {
|
|||
topMargin: list.spacing
|
||||
bottomMargin: list.spacing
|
||||
}
|
||||
|
||||
|
||||
PlasmaComponents3.ToolButton {
|
||||
id: clearButton
|
||||
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
|
||||
visible: hasNotifications
|
||||
|
||||
|
||||
font.bold: true
|
||||
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||
|
||||
|
||||
icon.name: "edit-clear-history"
|
||||
text: i18n("Clear All Notifications")
|
||||
onClicked: clearHistory()
|
||||
|
|
|
|||
Loading…
Reference in a new issue