feat: better notification storage and image caching + blobs
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 11s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Failing after 27s
Rust / build (pull_request) Successful in 54s
Python / verify (pull_request) Failing after 1m26s
Rust / fmt (pull_request) Successful in 46s
Rust / clippy (pull_request) Successful in 1m6s

This commit is contained in:
2026-11-02 19:00:43 +01:00
parent 789305286a
commit 11e7aac195
56 changed files with 5003 additions and 2168 deletions
+119 -115
View File
@@ -4,8 +4,10 @@ import QtQuick
import Quickshell
import ZShell.Components
import ZShell.Config
import qs.Daemons
import ZShell.Services
import qs.Components
import qs.Components.Notifs
import qs.Daemons
import qs.Services
LazyListView {
@@ -16,121 +18,12 @@ LazyListView {
anchors.left: parent?.left
anchors.right: parent?.right
asynchronous: true
cacheBuffer: 400
implicitHeight: contentHeight
readyDelay: 1
removeDuration: Tokens.anim.durations.normal
spacing: Tokens.spacing.small
useCustomViewport: true
viewport: Qt.rect(0, container.contentY, width, container.height)
delegate: Component {
MouseArea {
id: notif
readonly property bool closed: notifInner.notifCount === 0
required property int index
required property string modelData
property int startY
function closeAll(): void {
clearTimer.start();
}
LazyListView.preferredHeight: closed ? 0 : notifInner.nonAnimHeight
LazyListView.trackViewport: !notifInner.expanded && notifInner.nonAnimHeight < notifInner.implicitHeight
LazyListView.visibleHeight: notifInner.implicitHeight
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
cursorShape: pressed ? Qt.ClosedHandCursor : undefined
drag.axis: Drag.XAxis
drag.target: this
enabled: !closed
hoverEnabled: true
implicitHeight: notifInner.implicitHeight
opacity: LazyListView.removing || closed || LazyListView.adding ? 0 : 1
preventStealing: true
scale: LazyListView.removing || closed ? 0.6 : LazyListView.adding ? 0 : 1
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on scale {
Anim {
}
}
Behavior on x {
Anim {
}
}
Behavior on y {
enabled: notif.LazyListView.ready
Anim {
}
}
onPositionChanged: event => {
if (pressed) {
const diffY = event.y - startY;
if (Math.abs(diffY) > Config.notifs.expandThreshold)
notifInner.toggleExpand(diffY > 0);
}
}
onPressed: event => {
startY = event.y;
if (event.button === Qt.RightButton)
notifInner.toggleExpand(!notifInner.expanded);
else if (event.button === Qt.MiddleButton)
closeAll();
}
onReleased: event => {
if (Math.abs(x) < width * Config.notifs.clearThreshold)
x = 0;
else
closeAll();
}
Timer {
id: clearTimer
interval: 15
repeat: true
triggeredOnStart: true
onTriggered: {
const notifs = NotifServer.notClosed.filter(n => n.appName === notif.modelData);
if (notifs.length === 0) {
stop();
return;
}
for (const n of notifs.slice(0, 30))
n.close();
}
}
NotifGroup {
id: notifInner
container: root.container
modelData: notif.modelData
screenState: root.screenState
}
}
}
model: ScriptModel {
values: {
const map = new Map();
for (const n of NotifServer.notClosed)
map.set(n.appName, null);
for (const n of NotifServer.list)
map.set(n.appName, null);
return [...map.keys()];
}
}
readyDelay: 1
cacheBuffer: 400
asynchronous: true
onViewportAdjustNeeded: d => {
if (contentYAnim.running)
@@ -139,10 +32,121 @@ LazyListView {
contentYAnim.start();
}
useCustomViewport: true
viewport: Qt.rect(0, container.contentY, width, container.height)
removeDuration: Tokens.anim.durations.normal
model: ScriptModel {
values: [...NotifServer.groups]
}
delegate: Component {
NotifSwipeArea {
id: notif
required property int index
required property NotificationGroup modelData
readonly property bool closed: card.notifs.length === 0
function toggleExpand(expand: bool): void {
if (expand) {
if (!expanded)
root.screenState.expandedNotifs.push(modelData.appName);
} else if (expanded) {
root.screenState.expandedNotifs.splice(root.screenState.expandedNotifs.indexOf(modelData.appName), 1);
}
}
function closeAll(): void {
clearTimer.start();
}
Component.onDestruction: {
if (closed && expanded)
toggleExpand(false);
}
LazyListView.trackViewport: !expanded && card.nonAnimHeight < card.implicitHeight
LazyListView.preferredHeight: closed ? 0 : card.nonAnimHeight
LazyListView.visibleHeight: card.implicitHeight
implicitHeight: card.implicitHeight
opacity: LazyListView.removing || closed || LazyListView.adding ? 0 : 1
scale: LazyListView.adding ? 0 : 1
expanded: root.screenState.expandedNotifs.includes(modelData.appName)
enabled: !closed
onClosedChanged: {
if (closed)
slideOut.start();
}
onRequestExpand: expand => toggleExpand(expand)
onSwiped: closeAll()
onDismissed: closeAll()
Anim {
id: slideOut
target: notif
property: "x"
to: notif.x >= 0 ? notif.width : -notif.width
}
Timer {
id: clearTimer
interval: 15
repeat: true
triggeredOnStart: true
onTriggered: {
const notifs = [...notif.modelData.activeNotifs];
if (notifs.length === 0) {
stop();
return;
}
for (const n of notifs.slice(0, 30))
n.dismiss();
}
}
NotifGroupCard {
id: card
group: notif.modelData
container: root.container
expanded: notif.expanded
baseLayer: 1
onRequestExpand: expand => notif.toggleExpand(expand)
onSwiped: notif => notif.dismiss()
onLinkActivated: root.screenState.sidebar = false
}
Behavior on y {
enabled: notif.LazyListView.ready
Anim {}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on scale {
Anim {}
}
}
}
Anim {
id: contentYAnim
property: "contentY"
target: root.container
property: "contentY"
}
}