shift-shell/components/mobileshell/qml/widgets/notifications/NotificationCard.qml
Marco Allegretti 2e8ab6a741 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.
2026-05-24 15:48:49 +02:00

230 lines
6.9 KiB
QML

// SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Effects
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import org.kde.kirigami as Kirigami
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.mobileshell as MobileShell
Item {
id: root
/**
* The content that goes inside the notification card
*/
default property Item contentItem
/**
* The panel background type for this notification.
*/
property int panelType: MobileShell.PanelBackground.PanelType.Drawer
/**
* Whether this is a popup notification.
*/
property bool popupNotification: false
/**
* Whether this popup notification is tucked underneath the current popup.
*/
property bool inPopupDrawer: false
/**
* Whether this notification is within the lockscreen.
*/
property bool inLockScreen: false
/**
* The current notification popup height.
*/
property int currentPopupHeight: 0
/**
* The remaining time before the notification popup is dismissed.
*/
property real remainingTimeProgress: 1
/**
* Whether the timer for dismissing the notification popup is running.
*/
property bool closeTimerRunning: false
/**
* Whether tapping on this notification is enabled.
*/
property bool tapEnabled: false
/**
* Whether swipping on this notification is enabled.
*/
property bool swipeGestureEnabled: false
/**
* The current drag offset for this notification.
*/
property real dragOffset: 0
readonly property int longAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.Standard)
readonly property int veryLongAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.SpatialSlow)
signal tapped()
signal dismissRequested()
signal configureClicked() // TODO implement settings button
signal dragStart()
signal dragEnd()
onContentItemChanged: {
if (!contentItem) {
return;
}
contentItem.parent = contentParent;
contentItem.anchors.fill = contentParent;
contentItem.anchors.margins = Kirigami.Units.largeSpacing;
}
implicitHeight: contentParent.implicitHeight
MobileShell.MotionNumberAnimation on dragOffset {
id: dragAnim
type: MobileShell.Motion.StandardDecel
duration: root.longAnimationDuration
onFinished: {
if (to !== 0) {
root.dismissRequested();
}
}
}
MobileShell.PanelBackground {
anchors.fill: mainCard
animate: true
panelType: root.panelType
}
// card
Item {
id: mainCard
anchors.left: parent.left
anchors.leftMargin: root.dragOffset > 0 ? root.dragOffset : 0
anchors.right: parent.right
anchors.rightMargin: root.dragOffset < 0 ? -root.dragOffset : 0
anchors.top: parent.top
implicitHeight: inPopupDrawer ? currentPopupHeight : contentParent.implicitHeight
Behavior on implicitHeight {
MobileShell.MotionNumberAnimation {
duration: root.veryLongAnimationDuration
type: MobileShell.Motion.SpatialSlow
}
}
ProgressBar {
id: progress
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
width: root.width
height: 2
value: remainingTimeProgress
opacity: closeTimerRunning ? 1 : 0
Behavior on opacity {
MobileShell.MotionNumberAnimation {
duration: root.longAnimationDuration
type: MobileShell.Motion.StandardDecel
}
}
background: Item
contentItem: Item {
implicitWidth: parent.width
height: parent.height
clip: true
Rectangle {
width: Math.min(progress.visualPosition * (parent.width + root.dragOffset), parent.width)
height: Math.max(Kirigami.Units.cornerRadius * 2, parent.height)
topLeftRadius: Kirigami.Units.cornerRadius
topRightRadius: Kirigami.Units.cornerRadius
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.highlightColor, Kirigami.Theme.backgroundColor, 0.8)
}
Rectangle {
width: Math.min(progress.visualPosition * (parent.width + root.dragOffset), parent.width - Kirigami.Units.cornerRadius)
height: Math.max(Kirigami.Units.cornerRadius * 2, parent.height)
topLeftRadius: Kirigami.Units.cornerRadius
color: Kirigami.ColorUtils.linearInterpolation (Kirigami.Theme.highlightColor, Kirigami.Theme.backgroundColor, 0.8)
}
}
}
// clip
layer.enabled: true
// ensure this is behind the content to not interfere
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: {
if (root.tapEnabled) {
root.tapped()
}
}
}
// 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
}
}
DragHandler {
id: dragHandler
enabled: root.swipeGestureEnabled
yAxis.enabled: false
xAxis.enabled: !inPopupDrawer
property real startDragOffset: 0
property real startPosition: 0
property bool startActive: false
onTranslationChanged: {
if (startActive) {
startDragOffset = root.dragOffset;
startPosition = translation.x;
startActive = false;
}
root.dragOffset = startDragOffset + (translation.x - startPosition);
}
onActiveChanged: {
dragAnim.stop();
startActive = active;
if (!active) { // release event
root.dragEnd()
let threshold = Kirigami.Units.gridUnit * 5; // drag threshold
if (root.dragOffset > threshold) {
dragAnim.to = root.width;
} else if (root.dragOffset < -threshold) {
dragAnim.to = -root.width;
} else {
dragAnim.to = 0;
}
dragAnim.restart();
} else {
root.dragStart()
}
}
}
}