notifications: more androidy physics

fixes #3636
This commit is contained in:
bbedward
2026-09-30 14:02:48 -04:00
parent adccc05397
commit 8e9970efee
11 changed files with 280 additions and 215 deletions
+4 -1
View File
@@ -224,7 +224,9 @@ DankModal {
KeyboardNavigatedNotificationList {
id: notificationList
width: parent.width
x: -swipeBleed
width: parent.width + swipeBleed * 2
swipeBleed: Theme.spacingL
height: parent.height - y
visible: notificationHeader.currentTab === 0
keyboardController: modalKeyboardController
@@ -243,6 +245,7 @@ DankModal {
width: parent.width
height: parent.height - y
visible: notificationHeader.currentTab === 1
swipeBleed: Theme.spacingL
Component.onCompleted: notificationModal.historyListRef = historyList
}
}
@@ -14,6 +14,7 @@ FocusScope {
property string entryId: "notifications"
property int currentTab: 0
property var transientSurfaceTracker: null
property real swipeBleed: 0
readonly property var surfaces: transientSurfaceTracker ?? localSurfaces
readonly property bool hasNotifications: list.count > 0
readonly property Item focusTarget: root
@@ -85,7 +86,6 @@ FocusScope {
implicitHeight: DashMetrics.tabMinHeight
enabled: interactive
clip: true
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
@@ -128,8 +128,11 @@ FocusScope {
KeyboardNavigatedNotificationList {
id: list
anchors.fill: parent
anchors.leftMargin: -root.swipeBleed
anchors.rightMargin: -root.swipeBleed
nested: root.nested
anchors.bottomMargin: footer.height + Theme.spacingM
swipeBleed: root.swipeBleed
visible: root.currentTab === 0
showScrollBar: false
keyboardController: keyboard
@@ -140,13 +143,15 @@ FocusScope {
Loader {
id: historyLoader
anchors.fill: list
anchors.fill: parent
anchors.bottomMargin: footer.height + Theme.spacingM
active: root.currentTab === 1
visible: active
sourceComponent: HistoryNotificationList {
focusAllowed: root.activeFocus
showScrollBar: false
nested: root.nested
swipeBleed: root.swipeBleed
}
}
@@ -24,6 +24,7 @@ Card {
live: root.live
interactive: root.interactive
nested: true
swipeBleed: root.pad
transientSurfaceTracker: root.transientSurfaceTracker
}
}
@@ -16,6 +16,7 @@ Item {
property int selectedIndex: -1
property bool showKeyboardHints: false
property bool nested: false
property real swipeBleed: 0
property alias showScrollBar: historyListView.showScrollBar
function getStartOfDay(date) {
@@ -237,9 +238,12 @@ Item {
DankListView {
id: historyListView
width: parent.width
x: -root.swipeBleed
width: parent.width + root.swipeBleed * 2
height: parent.height - filterChips.height - Theme.spacingS
clip: true
leftMargin: root.swipeBleed
rightMargin: root.swipeBleed
spacing: Theme.groupedListGap
add: NotificationMetrics.animationsEnabled ? DC.ListViewTransitions.add : null
@@ -257,90 +261,33 @@ Item {
visible: historyListView.count === 0
}
delegate: Item {
NotificationSwipeGroup {
id: historySwipe
}
delegate: NotificationSwipeRow {
id: delegateRoot
required property var modelData
required property int index
property real swipeOffset: 0
property bool isDismissing: false
readonly property real dismissThreshold: width * NotificationMetrics.swipeThreshold
property bool __delegateInitialized: false
Timer {
interval: 0
running: true
onTriggered: delegateRoot.__delegateInitialized = true
}
width: ListView.view.width
group: historySwipe
bleed: root.swipeBleed
width: ListView.view.width - root.swipeBleed * 2
height: historyCard.height
clip: true
onDismissed: root.removeWithScrollPreserve(modelData?.id || "")
HistoryNotificationCard {
id: historyCard
width: parent.width
x: delegateRoot.swipeOffset
x: delegateRoot.offset
historyItem: modelData
nested: root.nested
firstInGroup: index === 0
lastInGroup: index === historyListView.count - 1
topRoundness: delegateRoot.topRoundness
bottomRoundness: delegateRoot.bottomRoundness
contentOpacity: delegateRoot.contentOpacity
isSelected: root.keyboardActive && root.focusAllowed && root.selectedIndex === index
keyboardNavigationActive: root.keyboardActive && root.focusAllowed
opacity: Math.max(0, 1 - Math.abs(delegateRoot.swipeOffset) / delegateRoot.width)
Behavior on x {
enabled: !swipeDragHandler.active && !delegateRoot.isDismissing && delegateRoot.__delegateInitialized && NotificationMetrics.animationsEnabled
NumberAnimation {
duration: Theme.notificationExitDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: NotificationMetrics.dismissCurve
}
}
Behavior on opacity {
enabled: delegateRoot.__delegateInitialized && NotificationMetrics.animationsEnabled
NumberAnimation {
duration: delegateRoot.__delegateInitialized ? Theme.notificationExitDuration : 0
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
}
}
}
NumberAnimation {
id: swipeDismissAnimation
target: delegateRoot
property: "swipeOffset"
to: delegateRoot.swipeOffset > 0 ? delegateRoot.width : -delegateRoot.width
duration: NotificationMetrics.animationsEnabled ? Theme.notificationExitDuration : 0
easing.type: Easing.BezierSpline
easing.bezierCurve: NotificationMetrics.dismissCurve
onFinished: root.removeWithScrollPreserve(delegateRoot.modelData?.id || "")
}
DragHandler {
id: swipeDragHandler
target: null
yAxis.enabled: false
xAxis.enabled: true
onActiveChanged: {
if (active || delegateRoot.isDismissing)
return;
if (Math.abs(delegateRoot.swipeOffset) <= delegateRoot.dismissThreshold) {
delegateRoot.swipeOffset = 0;
return;
}
delegateRoot.isDismissing = true;
swipeDismissAnimation.restart();
}
onTranslationChanged: {
if (delegateRoot.isDismissing)
return;
delegateRoot.swipeOffset = translation.x;
}
}
}
}
@@ -21,8 +21,7 @@ DankListView {
property bool trackStableContentHeight: true
property bool trackSessionContentHeight: false
property bool listInitialized: false
property int swipingCardIndex: -1
property real swipingCardOffset: 0
property real swipeBleed: 0
property real sessionContentHeight: 0
property var transientSurfaceTracker: null
property bool nested: false
@@ -171,6 +170,8 @@ DankListView {
}
clip: true
leftMargin: swipeBleed
rightMargin: swipeBleed
model: ScriptModel {
values: NotificationService.groupedNotifications.map(group => group.key)
}
@@ -206,6 +207,10 @@ DankListView {
visible: listView.count === 0
}
NotificationSwipeGroup {
id: swipeGroup
}
onCountChanged: listView.queueSessionContentHeightUpdate()
onModelChanged: {
@@ -216,21 +221,12 @@ DankListView {
ensureVisibleTimer.restart();
}
delegate: Item {
delegate: NotificationSwipeRow {
id: delegateRoot
required property string modelData
required property int index
readonly property var notificationGroup: NotificationService.groupedNotifications.find(group => group.key === modelData)
readonly property bool isExpanded: NotificationService.expandedGroups[modelData] || false
property real swipeOffset: 0
property bool isDismissing: false
readonly property real dismissThreshold: width * NotificationMetrics.swipeThreshold
readonly property bool isAdjacentToSwipe: listView.count >= 2 && listView.swipingCardIndex !== -1 && (index === listView.swipingCardIndex - 1 || index === listView.swipingCardIndex + 1)
readonly property real adjacentSwipeInfluence: isAdjacentToSwipe ? listView.swipingCardOffset * NotificationMetrics.adjacentSwipeInfluence : 0
readonly property real swipeFadeStartOffset: width * NotificationMetrics.swipeFadeStart
readonly property real swipeFadeDistance: Math.max(1, width - swipeFadeStartOffset)
readonly property real nonAnimHeight: notificationCard.targetHeight
readonly property bool isCardAnimating: notificationCard.isAnimating
@@ -244,14 +240,20 @@ DankListView {
}
}
width: ListView.view.width
group: swipeGroup
bleed: listView.swipeBleed
width: ListView.view.width - listView.swipeBleed * 2
height: notificationCard.height
clip: true
onDismissed: NotificationService.dismissGroup(modelData)
Center.NotificationCard {
id: notificationCard
width: parent.width
x: delegateRoot.swipeOffset + delegateRoot.adjacentSwipeInfluence
x: delegateRoot.offset
topRoundness: delegateRoot.topRoundness
bottomRoundness: delegateRoot.bottomRoundness
contentOpacity: delegateRoot.contentOpacity
swipeBleed: listView.swipeBleed
notificationGroup: delegateRoot.notificationGroup
nested: listView.nested
firstInList: index === 0
@@ -259,13 +261,6 @@ DankListView {
keyboardNavigationActive: listView.keyboardActive && listView.focusAllowed
animateExpansion: listView.cardAnimateExpansion && listView.listInitialized
transientSurfaceTracker: listView.transientSurfaceTracker
opacity: {
const swipeAmount = Math.abs(delegateRoot.swipeOffset);
if (swipeAmount <= delegateRoot.swipeFadeStartOffset)
return 1;
const fadeProgress = (swipeAmount - delegateRoot.swipeFadeStartOffset) / delegateRoot.swipeFadeDistance;
return Math.max(0, 1 - fadeProgress);
}
onIsAnimatingChanged: {
if (!listView.trackStableContentHeight)
return;
@@ -302,65 +297,6 @@ DankListView {
const selection = keyboardController.getCurrentSelection();
return (selection.type === "notification" && selection.groupIndex === index) ? selection.notificationIndex : -1;
}
Behavior on x {
enabled: NotificationMetrics.animationsEnabled && !swipeDragHandler.active && !delegateRoot.isDismissing && (listView.swipingCardIndex === -1 || !delegateRoot.isAdjacentToSwipe) && listView.listInitialized
NumberAnimation {
duration: Theme.notificationExitDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: NotificationMetrics.heightCurve
}
}
Behavior on opacity {
enabled: NotificationMetrics.animationsEnabled && listView.listInitialized
NumberAnimation {
duration: listView.listInitialized ? Theme.notificationExitDuration : 0
}
}
}
DragHandler {
id: swipeDragHandler
target: null
yAxis.enabled: false
xAxis.enabled: true
onActiveChanged: {
if (active) {
listView.swipingCardIndex = index;
return;
}
listView.swipingCardIndex = -1;
listView.swipingCardOffset = 0;
if (delegateRoot.isDismissing)
return;
if (Math.abs(delegateRoot.swipeOffset) > delegateRoot.dismissThreshold) {
delegateRoot.isDismissing = true;
swipeDismissAnim.to = delegateRoot.swipeOffset > 0 ? delegateRoot.width : -delegateRoot.width;
swipeDismissAnim.start();
} else {
delegateRoot.swipeOffset = 0;
}
}
onTranslationChanged: {
if (delegateRoot.isDismissing)
return;
delegateRoot.swipeOffset = translation.x;
listView.swipingCardOffset = translation.x;
}
}
NumberAnimation {
id: swipeDismissAnim
target: delegateRoot
property: "swipeOffset"
to: 0
duration: NotificationMetrics.animationsEnabled ? Theme.notificationExitDuration : 0
easing.type: Easing.BezierSpline
easing.bezierCurve: NotificationMetrics.dismissCurve
onFinished: NotificationService.dismissGroup(delegateRoot.modelData)
}
}
@@ -19,6 +19,10 @@ Item {
property bool firstInList: true
property bool lastInList: true
property bool nested: false
property real topRoundness: 0
property real bottomRoundness: 0
property real contentOpacity: 1
property real swipeBleed: 0
readonly property color cardSurfaceColor: Theme.foregroundColor(nested ? Theme.chipSurface : Theme.cardSurface, Theme.isFloatingWindow(root))
readonly property color cardChipColor: nested ? Theme.withAlpha(Theme.onSurface, Theme.stateLayerFocus) : Theme.chipSurface
readonly property real expandedTargetHeight: {
@@ -35,7 +39,7 @@ Item {
width: parent ? parent.width : NotificationMetrics.popupWidth
height: targetHeight
clip: true
clip: isAnimating
function toggleGroup() {
NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
@@ -55,6 +59,10 @@ Item {
Component.onDestruction: transientSurfaceTracker?.unregister(root)
NotificationSwipeGroup {
id: rowSwipe
}
Connections {
target: root.transientSurfaceTracker
ignoreUnknownSignals: true
@@ -84,6 +92,9 @@ Item {
descriptionExpanded: NotificationService.expandedMessages[(notificationData?.notification?.id || "") + "_desc"] || false
firstInGroup: root.firstInList
lastInGroup: root.lastInList
topRoundness: root.topRoundness
bottomRoundness: root.bottomRoundness
contentOpacity: root.contentOpacity
keyboardSelected: root.keyboardNavigationActive && root.isGroupSelected
keyboardHints: keyboardSelected
animateHeight: false
@@ -104,10 +115,9 @@ Item {
Column {
id: expandedContent
objectName: "expandedContent"
property int swipingIndex: -1
property real swipingOffset: 0
width: parent.width
visible: root.expanded
opacity: root.contentOpacity
spacing: Theme.groupedListGap
Rectangle {
@@ -165,30 +175,34 @@ Item {
values: root.expanded ? (root.notificationGroup?.notifications?.slice(0, NotificationMetrics.expandedLimit) || []) : []
}
Item {
NotificationSwipeRow {
id: row
required property var modelData
required property int index
property real swipeOffset: 0
property bool dismissing: false
property bool collapsing: false
property var notificationToDismiss: null
readonly property real targetHeight: collapsing ? 0 : message.targetHeight
readonly property bool adjacentToSwipe: expandedContent.swipingIndex !== -1 && Math.abs(index - expandedContent.swipingIndex) === 1
readonly property real adjacentSwipeInfluence: adjacentToSwipe ? expandedContent.swipingOffset * NotificationMetrics.adjacentSwipeInfluence : 0
group: rowSwipe
bleed: root.swipeBleed
width: expandedContent.width
height: message.height
clip: true
clip: collapsing
onDismissed: {
notificationToDismiss = modelData;
collapseAnimation.start();
}
Notifications.NotificationCard {
id: message
surfaceColor: root.cardSurfaceColor
chipColor: root.cardChipColor
x: row.swipeOffset + row.adjacentSwipeInfluence
x: row.offset
width: parent.width
notificationData: row.modelData
firstInGroup: row.index === 0
lastInGroup: row.index === notificationRepeater.count - 1
topRoundness: row.topRoundness
bottomRoundness: row.bottomRoundness
contentOpacity: row.contentOpacity
keyboardSelected: root.keyboardNavigationActive && root.selectedNotificationIndex === row.index
keyboardHints: keyboardSelected
descriptionExpanded: NotificationService.expandedMessages[(notificationData?.notification?.id || "") + "_desc"] || false
@@ -197,56 +211,11 @@ Item {
onActionRequested: action => root.invokeAction(action)
onBodyClicked: root.invokeAction(contextActions.defaultAction(notificationData))
onContextMenuRequested: (x, y) => root.openContextMenu(message, x, y)
Behavior on x {
enabled: !swipe.active && !row.dismissing && !row.adjacentToSwipe && NotificationMetrics.animationsEnabled
NumberAnimation {
duration: Theme.notificationExitDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: NotificationMetrics.dismissCurve
}
}
}
DragHandler {
id: swipe
target: null
yAxis.enabled: false
grabPermissions: PointerHandler.CanTakeOverFromItems | PointerHandler.CanTakeOverFromHandlersOfDifferentType
onTranslationChanged: {
if (row.dismissing)
return;
row.swipeOffset = translation.x;
expandedContent.swipingOffset = translation.x;
}
onActiveChanged: {
if (active) {
expandedContent.swipingIndex = row.index;
return;
}
expandedContent.swipingIndex = -1;
expandedContent.swipingOffset = 0;
if (row.dismissing)
return;
if (Math.abs(row.swipeOffset) <= row.width * NotificationMetrics.swipeThreshold) {
row.swipeOffset = 0;
return;
}
row.notificationToDismiss = row.modelData;
row.dismissing = true;
dismissAnimation.start();
}
}
SequentialAnimation {
id: dismissAnimation
id: collapseAnimation
NumberAnimation {
target: row
property: "swipeOffset"
to: row.swipeOffset > 0 ? row.width : -row.width
duration: NotificationMetrics.animationsEnabled ? Theme.notificationExitDuration : 0
easing.type: Easing.BezierSpline
easing.bezierCurve: NotificationMetrics.dismissCurve
}
PropertyAction {
target: row
property: "collapsing"
@@ -122,6 +122,9 @@ Item {
objectName: "notificationList"
anchors.fill: parent
anchors.leftMargin: -PopoutMetrics.contentPadding
anchors.rightMargin: -PopoutMetrics.contentPadding
swipeBleed: PopoutMetrics.contentPadding
cardAnimateExpansion: root.host.animateCardExpansion ?? true
trackStableContentHeight: !root.hostOwnsHeight
trackSessionContentHeight: root.hostOwnsHeight
@@ -135,6 +138,7 @@ Item {
visible: notificationHeader.currentTab === 1
width: parent.width
height: parent.height - root.cachedHeaderHeight - contentColumnInner.spacing
swipeBleed: PopoutMetrics.contentPadding
}
}
}
@@ -30,6 +30,9 @@ Rectangle {
property real outerRadius: Theme.groupedListOuterRadius
property bool firstInGroup: true
property bool lastInGroup: true
property real topRoundness: 0
property real bottomRoundness: 0
property real contentOpacity: 1
readonly property bool hasMoreText: bodyText.truncated || summaryText.truncated
readonly property bool hasBody: (notificationData?.htmlBody || "").replace(/<[^>]*>/g, "").trim().length > 0
readonly property string appIcon: NotificationService.notificationAppIcon(notificationData?.appIcon || "", notificationData?.desktopEntry || "")
@@ -69,9 +72,9 @@ Rectangle {
implicitHeight: targetHeight
height: targetHeight
radius: Theme.groupedListInnerRadius
topLeftRadius: firstInGroup ? outerRadius : radius
topLeftRadius: radius + (outerRadius - radius) * (firstInGroup ? 1 : topRoundness)
topRightRadius: topLeftRadius
bottomLeftRadius: lastInGroup ? outerRadius : radius
bottomLeftRadius: radius + (outerRadius - radius) * (lastInGroup ? 1 : bottomRoundness)
bottomRightRadius: bottomLeftRadius
color: keyboardSelected ? Theme.selectedContainer : surfaceColor
border.width: Theme.layerOutlineWidth
@@ -127,6 +130,7 @@ Rectangle {
height: parent.height - NotificationMetrics.cardPadding * 2
radius: Theme.fullRadius(width, height)
color: Theme.primary
opacity: root.contentOpacity
visible: root.notificationData?.urgency === NotificationUrgency.Critical
}
@@ -151,6 +155,7 @@ Rectangle {
anchors.left: parent.left
anchors.leftMargin: NotificationMetrics.cardPadding
y: NotificationMetrics.cardPadding
opacity: root.contentOpacity
Image {
id: appImage
@@ -184,6 +189,7 @@ Rectangle {
anchors.rightMargin: NotificationMetrics.cardPadding
y: NotificationMetrics.cardPadding
spacing: NotificationMetrics.contentSpacing
opacity: root.contentOpacity
Item {
width: parent.width
@@ -35,7 +35,15 @@ Singleton {
readonly property real unreadDotSize: Theme.spacingXS + Theme.spacingXXS
readonly property real swipeThreshold: 0.35
readonly property real swipeFadeStart: 0.75
readonly property real adjacentSwipeInfluence: 0.1
readonly property real swipeContentFadeEnd: 0.6
readonly property real swipeDetachDistance: 72
readonly property real swipeAttachDistance: 56
readonly property var swipePull: [0.04, 0.12, 0.5, 0.12, 0.04]
readonly property var swipeRoundness: [0.5, 0.7, 0.9, 1, 0.9, 0.7, 0.5]
readonly property real swipePullRoundnessMax: 0.8
readonly property var swipeDetachSpring: swipeSpring(800, 0.95)
readonly property var swipeSnapSpring: swipeSpring(550, 0.6)
readonly property var swipeAttachSpring: swipeSpring(850, 0.95)
readonly property int expandedLimit: 10
readonly property int collapsedLines: compact ? 1 : 2
readonly property real summarySize: SettingsData.notificationSummaryFontSize || Theme.fontSizeMedium
@@ -49,4 +57,11 @@ Singleton {
readonly property var stackSpring: Theme.springPreset("default", Theme.notificationStackShiftDuration)
readonly property var expandCurve: Theme.expressiveCurves.expressiveDefaultSpatial
readonly property var dismissCurve: Theme.expressiveCurves.expressiveFastSpatial
function swipeSpring(stiffness, dampingRatio) {
return {
"stiffness": stiffness,
"damping": 2 * dampingRatio * Math.sqrt(stiffness)
};
}
}
@@ -0,0 +1,34 @@
import QtQuick
QtObject {
property Item row: null
property real translation: 0
property bool detached: false
function begin(item) {
if (row)
return;
translation = 0;
detached = false;
row = item;
}
function drag(item, value) {
if (row !== item)
return;
const distance = Math.abs(value);
if (!detached && distance >= NotificationMetrics.swipeDetachDistance)
detached = true;
else if (detached && distance <= NotificationMetrics.swipeAttachDistance)
detached = false;
translation = value;
}
function end(item) {
if (row !== item)
return;
row = null;
detached = false;
translation = 0;
}
}
@@ -0,0 +1,145 @@
import QtQuick
import qs.Common
Item {
id: root
required property int index
property NotificationSwipeGroup group: null
property real bleed: 0
property bool dismissing: false
property real topRoundness: roundness(true)
property real bottomRoundness: roundness(false)
readonly property real offset: motion.value
readonly property real contentOpacity: 1 - Math.min(1, Math.abs(motion.value) / Math.max(1, width * NotificationMetrics.swipeContentFadeEnd))
readonly property bool magnetActive: group !== null && group.row !== null
readonly property bool magnetDetached: group !== null && group.detached
readonly property real magnetTranslation: group ? group.translation : 0
signal dismissed
function slot(values, distance) {
const i = distance + (values.length - 1) / 2;
return i >= 0 && i < values.length ? values[i] : 0;
}
function roundness(top) {
if (dismissing)
return 1;
if (!group?.row)
return 0;
const distance = index - group.row.index;
if (group.detached)
return distance === 0 || distance === (top ? 1 : -1) ? 1 : 0;
const reach = (NotificationMetrics.swipeRoundness.length - 1) / 2;
if (distance === (top ? -reach : reach))
return 0;
const pull = Math.abs(slot(NotificationMetrics.swipePull, 0) * group.translation) / NotificationMetrics.swipeDetachDistance;
return slot(NotificationMetrics.swipeRoundness, distance) * Math.min(NotificationMetrics.swipePullRoundnessMax, pull);
}
function magnetTarget() {
if (!group?.row)
return 0;
const distance = index - group.row.index;
if (group.detached)
return distance === 0 ? group.translation : 0;
return slot(NotificationMetrics.swipePull, distance) * group.translation;
}
function springTo(spring, target) {
if (dismissing)
return;
motion.stiffness = spring.stiffness;
motion.damping = spring.damping;
motion.retarget(target);
}
function release() {
if (group?.row !== root)
return;
if (Math.abs(group.translation) > width * NotificationMetrics.swipeThreshold) {
dismissing = true;
motion.snapTo(motion.value);
dismissAnimation.to = (group.translation > 0 ? 1 : -1) * (width + bleed);
dismissAnimation.start();
}
group.end(root);
}
onMagnetTranslationChanged: {
if (dismissing)
return;
if (motion.running) {
motion.retarget(magnetTarget());
return;
}
motion.snapTo(magnetTarget());
}
onMagnetDetachedChanged: {
if (!group?.row)
return;
if (!group.detached) {
springTo(NotificationMetrics.swipeAttachSpring, magnetTarget());
return;
}
springTo(group.row === root ? NotificationMetrics.swipeDetachSpring : NotificationMetrics.swipeSnapSpring, magnetTarget());
}
onMagnetActiveChanged: {
if (magnetActive)
return;
springTo(NotificationMetrics.swipeSnapSpring, 0);
}
Component.onDestruction: group?.end(root)
Behavior on topRoundness {
enabled: NotificationMetrics.animationsEnabled
NumberAnimation {
duration: Theme.expressiveDurations.expressiveEffects
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
}
}
Behavior on bottomRoundness {
enabled: NotificationMetrics.animationsEnabled
NumberAnimation {
duration: Theme.expressiveDurations.expressiveEffects
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
}
}
SpringMotion {
id: motion
reducedMotion: !NotificationMetrics.animationsEnabled
}
NumberAnimation {
id: dismissAnimation
target: motion
property: "value"
duration: NotificationMetrics.animationsEnabled ? Theme.notificationExitDuration : 0
easing.type: Easing.BezierSpline
easing.bezierCurve: NotificationMetrics.dismissCurve
onFinished: root.dismissed()
}
DragHandler {
target: null
yAxis.enabled: false
enabled: root.group !== null && !root.dismissing
grabPermissions: PointerHandler.CanTakeOverFromItems | PointerHandler.CanTakeOverFromHandlersOfDifferentType
onActiveChanged: {
if (active) {
root.group.begin(root);
return;
}
root.release();
}
onTranslationChanged: root.group?.drag(root, translation.x)
}
}