diff options
| -rw-r--r-- | modules/sidebar/Notif.qml | 6 | ||||
| -rw-r--r-- | modules/sidebar/NotifActionList.qml | 198 | ||||
| -rw-r--r-- | modules/sidebar/NotifDock.qml | 1 | ||||
| -rw-r--r-- | modules/sidebar/NotifGroup.qml | 26 | ||||
| -rw-r--r-- | services/Notifs.qml | 19 |
5 files changed, 232 insertions, 18 deletions
diff --git a/modules/sidebar/Notif.qml b/modules/sidebar/Notif.qml index 3aecc59..6d930b9 100644 --- a/modules/sidebar/Notif.qml +++ b/modules/sidebar/Notif.qml @@ -106,7 +106,7 @@ StyledRect { anchors.topMargin: Appearance.spacing.small / 2 sourceComponent: ColumnLayout { - spacing: Math.floor(Appearance.spacing.small / 2) + spacing: Appearance.spacing.smaller StyledText { Layout.fillWidth: true @@ -115,6 +115,10 @@ StyledRect { color: root.modelData.urgency === "critical" ? Colours.palette.m3secondary : Colours.palette.m3outline wrapMode: Text.WordWrap } + + NotifActionList { + notif: root.modelData + } } } diff --git a/modules/sidebar/NotifActionList.qml b/modules/sidebar/NotifActionList.qml new file mode 100644 index 0000000..a8f75cc --- /dev/null +++ b/modules/sidebar/NotifActionList.qml @@ -0,0 +1,198 @@ +pragma ComponentBehavior: Bound + +import qs.components +import qs.components.containers +import qs.components.effects +import qs.services +import qs.config +import Quickshell +import Quickshell.Widgets +import QtQuick +import QtQuick.Layouts + +Item { + id: root + + required property Notifs.Notif notif + + Layout.fillWidth: true + implicitHeight: flickable.contentHeight + + layer.enabled: true + layer.smooth: true + layer.effect: OpacityMask { + maskSource: gradientMask + } + + Item { + id: gradientMask + + anchors.fill: parent + layer.enabled: true + visible: false + + Rectangle { + anchors.fill: parent + + gradient: Gradient { + orientation: Gradient.Horizontal + + GradientStop { + position: 0 + color: Qt.rgba(0, 0, 0, 0) + } + GradientStop { + position: 0.1 + color: Qt.rgba(0, 0, 0, 1) + } + GradientStop { + position: 0.9 + color: Qt.rgba(0, 0, 0, 1) + } + GradientStop { + position: 1 + color: Qt.rgba(0, 0, 0, 0) + } + } + } + + Rectangle { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + + implicitWidth: parent.width / 2 + opacity: flickable.contentX > 0 ? 0 : 1 + + Behavior on opacity { + Anim {} + } + } + + Rectangle { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + + implicitWidth: parent.width / 2 + opacity: flickable.contentX < flickable.contentWidth - parent.width ? 0 : 1 + + Behavior on opacity { + Anim {} + } + } + } + + StyledFlickable { + id: flickable + + anchors.fill: parent + contentWidth: Math.max(width, actionList.implicitWidth) + contentHeight: actionList.implicitHeight + + RowLayout { + id: actionList + + anchors.fill: parent + spacing: Appearance.spacing.small + + Repeater { + model: [ + { + isClose: true + }, + ...root.notif.actions, + { + isCopy: true + } + ] + + StyledRect { + id: action + + required property var modelData + + Layout.fillWidth: true + Layout.fillHeight: true + implicitWidth: actionInner.implicitWidth + Appearance.padding.normal * 2 + implicitHeight: actionInner.implicitHeight + Appearance.padding.small * 2 + + Layout.preferredWidth: implicitWidth + (actionStateLayer.pressed ? Appearance.padding.large : 0) + radius: actionStateLayer.pressed ? Appearance.rounding.small / 2 : Appearance.rounding.small + color: Colours.layer(Colours.palette.m3surfaceContainerHighest, 4) + + Timer { + id: copyTimer + + interval: 3000 + onTriggered: actionInner.item.text = "content_copy" + } + + StateLayer { + id: actionStateLayer + + function onClicked(): void { + if (action.modelData.isClose) { + root.notif.close(); + } else if (action.modelData.isCopy) { + Quickshell.clipboardText = root.notif.body; + actionInner.item.text = "inventory"; + copyTimer.start(); + } else if (action.modelData.invoke) { + action.modelData.invoke(); + } else if (!root.notif.resident) { + root.notif.close(); + } + } + } + + Loader { + id: actionInner + + anchors.centerIn: parent + sourceComponent: action.modelData.isClose || action.modelData.isCopy ? iconBtn : root.notif.hasActionIcons ? iconComp : textComp + } + + Component { + id: iconBtn + + MaterialIcon { + animate: action.modelData.isCopy ?? false + text: action.modelData.isCopy ? "content_copy" : "close" + } + } + + Component { + id: iconComp + + IconImage { + source: Quickshell.iconPath(action.modelData.identifier) + } + } + + Component { + id: textComp + + StyledText { + text: action.modelData.text + } + } + + Behavior on Layout.preferredWidth { + Anim { + duration: Appearance.anim.durations.expressiveFastSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial + } + } + + Behavior on radius { + Anim { + duration: Appearance.anim.durations.expressiveFastSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial + } + } + } + } + } + } +} diff --git a/modules/sidebar/NotifDock.qml b/modules/sidebar/NotifDock.qml index 7ddee30..c34229e 100644 --- a/modules/sidebar/NotifDock.qml +++ b/modules/sidebar/NotifDock.qml @@ -92,7 +92,6 @@ Item { anchors.fill: parent spacing: Appearance.spacing.small - clip: true model: ScriptModel { values: [...new Set(Notifs.list.filter(n => !n.closed).map(n => n.appName))].reverse() diff --git a/modules/sidebar/NotifGroup.qml b/modules/sidebar/NotifGroup.qml index 83cffcd..a87deb6 100644 --- a/modules/sidebar/NotifGroup.qml +++ b/modules/sidebar/NotifGroup.qml @@ -6,7 +6,6 @@ import qs.services import qs.config import qs.utils import Quickshell -import Quickshell.Widgets import Quickshell.Services.Notifications import QtQuick import QtQuick.Layouts @@ -20,7 +19,7 @@ StyledRect { readonly property list<var> notifs: Notifs.list.filter(notif => notif.appName === modelData).reverse() readonly property string image: notifs.find(n => n.image.length > 0)?.image ?? "" readonly property string appIcon: notifs.find(n => n.appIcon.length > 0)?.appIcon ?? "" - readonly property string urgency: notifs.some(n => n.urgency === NotificationUrgency.Critical) ? "critical" : notifs.some(n => n.urgency === NotificationUrgency.Normal) ? "normal" : "low" + readonly property int urgency: notifs.some(n => n.urgency === NotificationUrgency.Critical) ? NotificationUrgency.Critical : notifs.some(n => n.urgency === NotificationUrgency.Normal) ? NotificationUrgency.Normal : NotificationUrgency.Low readonly property bool expanded: props.expandedNotifs.includes(modelData) @@ -37,9 +36,8 @@ StyledRect { anchors.right: parent?.right implicitHeight: content.implicitHeight + Appearance.padding.normal * 2 - clip: true radius: Appearance.rounding.normal - color: root.urgency === "critical" ? Colours.palette.m3secondaryContainer : Colours.layer(Colours.palette.m3surfaceContainer, 2) + color: Colours.layer(Colours.palette.m3surfaceContainer, 2) RowLayout { id: content @@ -75,7 +73,7 @@ StyledRect { ColouredIcon { implicitSize: Math.round(Config.notifs.sizes.image * 0.6) source: Quickshell.iconPath(root.appIcon) - colour: root.urgency === "critical" ? Colours.palette.m3onError : root.urgency === "low" ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer + colour: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer layer.enabled: root.appIcon.endsWith("symbolic") } } @@ -85,14 +83,14 @@ StyledRect { MaterialIcon { text: Icons.getNotifIcon(root.notifs[0]?.summary, root.urgency) - color: root.urgency === "critical" ? Colours.palette.m3onError : root.urgency === "low" ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer + color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer font.pointSize: Appearance.font.size.large } } - ClippingRectangle { + StyledClippingRect { anchors.fill: parent - color: root.urgency === "critical" ? Colours.palette.m3error : root.urgency === "low" ? Colours.layer(Colours.palette.m3surfaceContainerHigh, 3) : Colours.palette.m3secondaryContainer + color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.urgency === NotificationUrgency.Low ? Colours.layer(Colours.palette.m3surfaceContainerHigh, 3) : Colours.palette.m3secondaryContainer radius: Appearance.rounding.full Loader { @@ -112,14 +110,14 @@ StyledRect { implicitWidth: Config.notifs.sizes.badge implicitHeight: Config.notifs.sizes.badge - color: root.urgency === "critical" ? Colours.palette.m3error : root.urgency === "low" ? Colours.palette.m3surfaceContainerHigh : Colours.palette.m3secondaryContainer + color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.urgency === NotificationUrgency.Low ? Colours.palette.m3surfaceContainerHigh : Colours.palette.m3secondaryContainer radius: Appearance.rounding.full ColouredIcon { anchors.centerIn: parent implicitSize: Math.round(Config.notifs.sizes.badge * 0.6) source: Quickshell.iconPath(root.appIcon) - colour: root.urgency === "critical" ? Colours.palette.m3onError : root.urgency === "low" ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer + colour: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer layer.enabled: root.appIcon.endsWith("symbolic") } } @@ -158,11 +156,11 @@ StyledRect { implicitWidth: expandBtn.implicitWidth + Appearance.padding.smaller * 2 implicitHeight: groupCount.implicitHeight + Appearance.padding.small - color: root.urgency === "critical" ? Colours.palette.m3error : Colours.layer(Colours.palette.m3surfaceContainerHigh, 3) + color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : Colours.layer(Colours.palette.m3surfaceContainerHigh, 3) radius: Appearance.rounding.full StateLayer { - color: root.urgency === "critical" ? Colours.palette.m3onError : Colours.palette.m3onSurface + color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : Colours.palette.m3onSurface function onClicked(): void { root.toggleExpand(!root.expanded); @@ -181,7 +179,7 @@ StyledRect { Layout.leftMargin: Appearance.padding.small / 2 animate: true text: root.notifs.reduce((acc, n) => n.closed ? acc : acc + 1, 0) - color: root.urgency === "critical" ? Colours.palette.m3onError : Colours.palette.m3onSurface + color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : Colours.palette.m3onSurface font.pointSize: Appearance.font.size.small } @@ -189,7 +187,7 @@ StyledRect { Layout.rightMargin: -Appearance.padding.small / 2 animate: true text: root.expanded ? "expand_less" : "expand_more" - color: root.urgency === "critical" ? Colours.palette.m3onError : Colours.palette.m3onSurface + color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : Colours.palette.m3onSurface } } } diff --git a/services/Notifs.qml b/services/Notifs.qml index b651442..c5f0700 100644 --- a/services/Notifs.qml +++ b/services/Notifs.qml @@ -30,7 +30,10 @@ Singleton { appName: n.appName, image: n.image, expireTimeout: n.expireTimeout, - urgency: n.urgency + urgency: n.urgency, + resident: n.resident, + hasActionIcons: n.hasActionIcons, + actions: n.actions })))); } @@ -73,6 +76,12 @@ Singleton { root.list.push(notifComp.createObject(root, notif)); root.loaded = true; } + onLoadFailed: err => { + if (err === FileViewError.FileNotFound) { + root.loaded = true; + setText("[]"); + } + } } CustomShortcut { @@ -138,7 +147,13 @@ Singleton { property string image: notification?.image ?? "" property real expireTimeout: notification?.expireTimeout ?? Config.notifs.defaultExpireTimeout property int urgency: notification?.urgency ?? NotificationUrgency.Normal - readonly property list<NotificationAction> actions: notification?.actions ?? [] + property bool resident: notification?.resident ?? false + property bool hasActionIcons: notification?.hasActionIcons ?? false + property list<var> actions: notification?.actions.map(a => ({ + identifier: a.identifier, + text: a.text, + invoke: () => a.invoke() + })) ?? [] readonly property Timer timer: Timer { running: true |