mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-15 07:44:01 -04:00
340 lines
12 KiB
QML
340 lines
12 KiB
QML
import QtQuick
|
|
import "." as Center
|
|
import Quickshell
|
|
import qs.Modules.Notifications
|
|
import qs.DankCommon.Common as DC
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
DankListView {
|
|
id: listView
|
|
|
|
property var keyboardController: null
|
|
property bool keyboardActive: false
|
|
property bool focusAllowed: true
|
|
property bool autoScrollDisabled: false
|
|
property bool isAnimatingExpansion: false
|
|
property alias listContentHeight: listView.contentHeight
|
|
property real stableContentHeight: 0
|
|
property bool cardAnimateExpansion: true
|
|
property bool trackStableContentHeight: true
|
|
property bool trackSessionContentHeight: false
|
|
property bool listInitialized: false
|
|
property real swipeBleed: 0
|
|
property real sessionContentHeight: 0
|
|
property var transientSurfaceTracker: null
|
|
property bool nested: false
|
|
readonly property real estimatedCollapsedCardHeight: NotificationMetrics.estimatedCardHeight
|
|
|
|
Timer {
|
|
interval: 0
|
|
running: true
|
|
onTriggered: {
|
|
listView.listInitialized = true;
|
|
listView.syncStableContentHeight(false);
|
|
listView.syncSessionContentHeight();
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: sessionHeightTimer
|
|
interval: 0
|
|
onTriggered: listView.syncSessionContentHeight()
|
|
}
|
|
|
|
Timer {
|
|
id: stableHeightTimer
|
|
property bool useTarget: false
|
|
interval: 0
|
|
onTriggered: listView.syncStableContentHeight(useTarget || listView.isAnimatingExpansion)
|
|
}
|
|
|
|
Timer {
|
|
id: ensureVisibleTimer
|
|
interval: 0
|
|
onTriggered: {
|
|
if (listView.keyboardController?.keyboardNavigationActive && !listView.autoScrollDisabled)
|
|
listView.keyboardController.ensureVisible();
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: expansionStateTimer
|
|
interval: 0
|
|
onTriggered: {
|
|
for (let i = 0; i < listView.count; i++) {
|
|
if (!listView.itemAtIndex(i)?.isCardAnimating)
|
|
continue;
|
|
listView.isAnimatingExpansion = true;
|
|
return;
|
|
}
|
|
listView.isAnimatingExpansion = false;
|
|
}
|
|
}
|
|
|
|
function estimateContentHeight(groupCount) {
|
|
if (groupCount <= 0)
|
|
return 0;
|
|
return topMargin + bottomMargin + groupCount * estimatedCollapsedCardHeight + Math.max(0, groupCount - 1) * spacing;
|
|
}
|
|
|
|
function estimateExpandedCardHeight(group) {
|
|
const count = Math.min(NotificationMetrics.expandedLimit, Math.max(1, group?.count || 1));
|
|
return Theme.iconButtonSize + count * (estimatedCollapsedCardHeight + Theme.groupedListGap);
|
|
}
|
|
|
|
function estimatedHeightForGroup(group) {
|
|
if (!group)
|
|
return estimatedCollapsedCardHeight;
|
|
if (NotificationService.expandedGroups[group.key])
|
|
return estimateExpandedCardHeight(group);
|
|
return estimatedCollapsedCardHeight;
|
|
}
|
|
|
|
function computeSessionContentHeight() {
|
|
const groups = NotificationService.groupedNotifications;
|
|
const count = groups ? groups.length : 0;
|
|
if (count <= 0)
|
|
return 0;
|
|
|
|
let total = topMargin + bottomMargin + Math.max(0, count - 1) * spacing;
|
|
for (let i = 0; i < count; i++) {
|
|
const item = itemAtIndex(i);
|
|
if (item && item.nonAnimHeight !== undefined && item.nonAnimHeight > 0)
|
|
total += item.nonAnimHeight;
|
|
else
|
|
total += estimatedHeightForGroup(groups[i]);
|
|
}
|
|
return Math.max(0, total);
|
|
}
|
|
|
|
function syncSessionContentHeight() {
|
|
if (!trackSessionContentHeight)
|
|
return;
|
|
const next = computeSessionContentHeight();
|
|
if (Math.abs(next - sessionContentHeight) <= 0.5)
|
|
return;
|
|
sessionContentHeight = next;
|
|
}
|
|
|
|
function queueSessionContentHeightUpdate() {
|
|
if (!trackSessionContentHeight || sessionHeightTimer.running)
|
|
return;
|
|
sessionHeightTimer.start();
|
|
}
|
|
|
|
function targetContentHeight() {
|
|
if (count <= 0)
|
|
return contentHeight;
|
|
|
|
let total = topMargin + bottomMargin + Math.max(0, count - 1) * spacing;
|
|
for (let i = 0; i < count; i++) {
|
|
const item = itemAtIndex(i);
|
|
if (!item || item.nonAnimHeight === undefined)
|
|
return contentHeight;
|
|
total += item.nonAnimHeight;
|
|
}
|
|
return Math.max(0, total);
|
|
}
|
|
|
|
function syncStableContentHeight(useTarget) {
|
|
if (!trackStableContentHeight)
|
|
return;
|
|
const nextHeight = useTarget ? targetContentHeight() : contentHeight;
|
|
if (Math.abs(nextHeight - stableContentHeight) <= 0.5)
|
|
return;
|
|
stableContentHeight = nextHeight;
|
|
}
|
|
|
|
function queueStableContentHeightUpdate(useTarget) {
|
|
if (!trackStableContentHeight || stableHeightTimer.running)
|
|
return;
|
|
stableHeightTimer.useTarget = useTarget;
|
|
stableHeightTimer.start();
|
|
}
|
|
|
|
onContentHeightChanged: {
|
|
if (trackStableContentHeight && !isAnimatingExpansion)
|
|
queueStableContentHeightUpdate(false);
|
|
}
|
|
|
|
onIsAnimatingExpansionChanged: {
|
|
if (!trackStableContentHeight)
|
|
return;
|
|
if (isAnimatingExpansion) {
|
|
syncStableContentHeight(true);
|
|
} else {
|
|
queueStableContentHeightUpdate(false);
|
|
}
|
|
}
|
|
|
|
clip: true
|
|
leftMargin: swipeBleed
|
|
rightMargin: swipeBleed
|
|
model: ScriptModel {
|
|
values: NotificationService.groupedNotifications.map(group => group.key)
|
|
}
|
|
add: NotificationMetrics.animationsEnabled ? DC.ListViewTransitions.add : null
|
|
remove: NotificationMetrics.animationsEnabled ? DC.ListViewTransitions.fadeRemove : null
|
|
displaced: NotificationMetrics.animationsEnabled ? DC.ListViewTransitions.displaced : null
|
|
move: NotificationMetrics.animationsEnabled ? DC.ListViewTransitions.move : null
|
|
spacing: Theme.groupedListGap
|
|
|
|
onIsUserScrollingChanged: {
|
|
if (isUserScrolling && keyboardController && keyboardController.keyboardNavigationActive) {
|
|
autoScrollDisabled = true;
|
|
}
|
|
}
|
|
|
|
function enableAutoScroll() {
|
|
autoScrollDisabled = false;
|
|
}
|
|
|
|
Timer {
|
|
id: expansionEnsureVisibleTimer
|
|
interval: Math.max(Theme.notificationExpandDuration, Theme.notificationInlineExpandDuration)
|
|
repeat: false
|
|
onTriggered: {
|
|
if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) {
|
|
keyboardController.ensureVisible();
|
|
}
|
|
}
|
|
}
|
|
|
|
NotificationEmptyState {
|
|
parent: listView
|
|
visible: listView.count === 0
|
|
}
|
|
|
|
NotificationSwipeGroup {
|
|
id: swipeGroup
|
|
}
|
|
|
|
onCountChanged: listView.queueSessionContentHeightUpdate()
|
|
|
|
onModelChanged: {
|
|
listView.queueSessionContentHeightUpdate();
|
|
if (!keyboardController || !keyboardController.keyboardNavigationActive)
|
|
return;
|
|
keyboardController.rebuildFlatNavigation();
|
|
ensureVisibleTimer.restart();
|
|
}
|
|
|
|
delegate: NotificationSwipeRow {
|
|
id: delegateRoot
|
|
required property string modelData
|
|
|
|
readonly property var notificationGroup: NotificationService.groupedNotifications.find(group => group.key === modelData)
|
|
readonly property bool isExpanded: NotificationService.expandedGroups[modelData] || false
|
|
readonly property real nonAnimHeight: notificationCard.targetHeight
|
|
|
|
readonly property bool isCardAnimating: notificationCard.isAnimating
|
|
|
|
Timer {
|
|
interval: 0
|
|
running: true
|
|
onTriggered: {
|
|
listView.queueStableContentHeightUpdate(listView.isAnimatingExpansion);
|
|
listView.queueSessionContentHeightUpdate();
|
|
}
|
|
}
|
|
|
|
group: swipeGroup
|
|
bleed: listView.swipeBleed
|
|
width: ListView.view.width - listView.swipeBleed * 2
|
|
height: notificationCard.height
|
|
onDismissed: NotificationService.dismissGroup(modelData)
|
|
|
|
Center.NotificationCard {
|
|
id: notificationCard
|
|
width: parent.width
|
|
x: delegateRoot.offset
|
|
topRoundness: delegateRoot.topRoundness
|
|
bottomRoundness: delegateRoot.bottomRoundness
|
|
contentOpacity: delegateRoot.contentOpacity
|
|
swipeBleed: listView.swipeBleed
|
|
notificationGroup: delegateRoot.notificationGroup
|
|
nested: listView.nested
|
|
firstInList: index === 0
|
|
lastInList: index === listView.count - 1
|
|
keyboardNavigationActive: listView.keyboardActive && listView.focusAllowed
|
|
animateExpansion: listView.cardAnimateExpansion && listView.listInitialized
|
|
transientSurfaceTracker: listView.transientSurfaceTracker
|
|
onIsAnimatingChanged: {
|
|
if (!listView.trackStableContentHeight)
|
|
return;
|
|
if (!isAnimating) {
|
|
expansionStateTimer.restart();
|
|
return;
|
|
}
|
|
listView.isAnimatingExpansion = true;
|
|
listView.syncStableContentHeight(true);
|
|
}
|
|
|
|
onTargetHeightChanged: {
|
|
listView.queueSessionContentHeightUpdate();
|
|
if (!listView.trackStableContentHeight)
|
|
return;
|
|
if (isAnimating || listView.isAnimatingExpansion)
|
|
listView.syncStableContentHeight(true);
|
|
else
|
|
listView.queueStableContentHeightUpdate(false);
|
|
}
|
|
|
|
isGroupSelected: {
|
|
if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive || !listView.focusAllowed)
|
|
return false;
|
|
keyboardController.selectionVersion;
|
|
const selection = keyboardController.getCurrentSelection();
|
|
return selection.type === "group" && selection.groupIndex === index;
|
|
}
|
|
|
|
selectedNotificationIndex: {
|
|
if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive || !listView.focusAllowed)
|
|
return -1;
|
|
keyboardController.selectionVersion;
|
|
const selection = keyboardController.getCurrentSelection();
|
|
return (selection.type === "notification" && selection.groupIndex === index) ? selection.notificationIndex : -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
readonly property var serviceGroupedNotifications: NotificationService.groupedNotifications
|
|
readonly property var serviceExpandedGroups: NotificationService.expandedGroups
|
|
readonly property var serviceExpandedMessages: NotificationService.expandedMessages
|
|
|
|
onServiceGroupedNotificationsChanged: {
|
|
queueSessionContentHeightUpdate();
|
|
if (!keyboardController) {
|
|
return;
|
|
}
|
|
|
|
if (keyboardController.isTogglingGroup) {
|
|
keyboardController.rebuildFlatNavigation();
|
|
return;
|
|
}
|
|
|
|
keyboardController.rebuildFlatNavigation();
|
|
|
|
if (keyboardController.keyboardNavigationActive) {
|
|
ensureVisibleTimer.restart();
|
|
}
|
|
}
|
|
|
|
onServiceExpandedGroupsChanged: {
|
|
queueSessionContentHeightUpdate();
|
|
keyboardController?.rebuildFlatNavigation();
|
|
if (!keyboardController || !keyboardController.keyboardNavigationActive)
|
|
return;
|
|
expansionEnsureVisibleTimer.restart();
|
|
}
|
|
|
|
onServiceExpandedMessagesChanged: {
|
|
queueSessionContentHeightUpdate();
|
|
if (!keyboardController || !keyboardController.keyboardNavigationActive)
|
|
return;
|
|
expansionEnsureVisibleTimer.restart();
|
|
}
|
|
}
|