notifs/toasts: reworked notifications and toasts and how they display and work together. see pull request comment.

This commit is contained in:
ATMDA 2025-11-12 14:51:22 -05:00
parent 3e977f212d
commit c1510b5476
12 changed files with 627 additions and 27 deletions

View file

@ -140,6 +140,7 @@ Variants {
property bool dashboard
property bool utilities
property bool sidebar
property bool notifications
Component.onCompleted: Visibilities.load(scope.modelData, this)
}

View file

@ -198,6 +198,52 @@ CustomMouseArea {
utilitiesShortcutActive = false;
}
// Show notifications panel on hover
// Try using inTopPanel first (works when panel is visible), fallback to corner detection only when panel is collapsed
const panelHeight = panels.notifications.height || panels.notifications.implicitHeight || 0;
const panelWidth = panels.notifications.width || panels.notifications.implicitWidth || Config.notifs.sizes.width;
const panelX = bar.implicitWidth + panels.notifications.x;
const isPanelCollapsed = panelHeight < 10; // Consider collapsed if height is very small
let showNotifications = inTopPanel(panels.notifications, x, y);
// Only use fallback corner detection when panel is collapsed
if (!showNotifications && isPanelCollapsed) {
// Use panel's actual width and position for fallback, with some padding
const cornerPadding = Config.border.rounding || 20;
showNotifications = x >= panelX - cornerPadding &&
x <= panelX + panelWidth + cornerPadding &&
y < Config.border.thickness + cornerPadding;
}
// Check if mouse is over the clear all button area
// Button is positioned to the left of the notification panel
if (!showNotifications && panels.notifications.height > 0 && panels.clearAllButton && panels.clearAllButton.visible) {
const buttonX = bar.implicitWidth + panels.clearAllButton.x;
const buttonY = Config.border.thickness + panels.clearAllButton.y;
const buttonWidth = panels.clearAllButton.width;
const buttonHeight = panels.clearAllButton.height;
const inButtonArea = x >= buttonX &&
x <= buttonX + buttonWidth &&
y >= buttonY &&
y <= buttonY + buttonHeight;
if (inButtonArea) {
showNotifications = true;
}
}
// Show or hide notification panel based on hover
if (panels.notifications.content) {
if (showNotifications) {
panels.notifications.content.show();
} else {
// Hide if not hovering over panel or button
panels.notifications.content.shouldShow = false;
}
}
// Show popouts on hover
if (x < bar.implicitWidth) {
bar.checkPopout(y);

View file

@ -8,6 +8,10 @@ import qs.modules.bar.popouts as BarPopouts
import qs.modules.utilities as Utilities
import qs.modules.utilities.toasts as Toasts
import qs.modules.sidebar as Sidebar
import qs.components
import qs.components.controls
import qs.components.effects
import qs.services
import Quickshell
import QtQuick
@ -27,6 +31,7 @@ Item {
readonly property alias utilities: utilities
readonly property alias toasts: toasts
readonly property alias sidebar: sidebar
readonly property alias clearAllButton: clearAllButton
anchors.fill: parent
anchors.margins: Config.border.thickness
@ -54,6 +59,89 @@ Item {
anchors.right: parent.right
}
// Clear all notifications button - positioned to the left of the notification panel
Item {
id: clearAllButton
readonly property bool hasNotifications: Notifs.notClosed.length > 0
readonly property bool panelVisible: notifications.height > 0 || notifications.implicitHeight > 0
readonly property bool shouldShow: hasNotifications && panelVisible
anchors.top: notifications.top
anchors.right: notifications.left
anchors.rightMargin: Appearance.padding.normal
anchors.topMargin: Appearance.padding.large
width: button.implicitWidth
height: button.implicitHeight
enabled: shouldShow
IconButton {
id: button
icon: "clear_all"
radius: Appearance.rounding.normal
padding: Appearance.padding.normal
font.pointSize: Math.round(Appearance.font.size.large * 1.2)
onClicked: {
// Clear all notifications
for (const notif of Notifs.list.slice())
notif.close();
}
Elevation {
anchors.fill: parent
radius: parent.radius
z: -1
level: button.stateLayer.containsMouse ? 4 : 3
}
}
// Keep notification panel visible when hovering over the button
MouseArea {
anchors.fill: button
hoverEnabled: true
acceptedButtons: Qt.NoButton
onEntered: {
if (notifications.content && Notifs.notClosed.length > 0) {
notifications.content.show();
}
}
onExited: {
// Panel will be hidden by Interactions.qml if mouse is not over panel or button
}
}
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on scale {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
opacity: shouldShow ? 1 : 0
scale: shouldShow ? 1 : 0.5
}
Notifications.NotificationToasts {
id: notificationToasts
panels: root
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: Config.border.thickness
anchors.rightMargin: Config.border.thickness
}
Session.Wrapper {
id: session

View file

@ -22,7 +22,7 @@ ColumnLayout {
StyledText {
Layout.fillWidth: true
text: Notifs.list.length > 0 ? qsTr("%1 notification%2").arg(Notifs.list.length).arg(Notifs.list.length === 1 ? "" : "s") : qsTr("Notifications")
text: Notifs.notClosed.length > 0 ? qsTr("%1 notification%2").arg(Notifs.notClosed.length).arg(Notifs.notClosed.length === 1 ? "" : "s") : qsTr("Notifications")
color: Colours.palette.m3outline
font.family: Appearance.font.family.mono
font.weight: 500
@ -42,7 +42,7 @@ ColumnLayout {
anchors.centerIn: parent
asynchronous: true
active: opacity > 0
opacity: Notifs.list.length > 0 ? 0 : 1
opacity: Notifs.notClosed.length > 0 ? 0 : 1
sourceComponent: ColumnLayout {
spacing: Appearance.spacing.large

View file

@ -16,7 +16,7 @@ StyledRect {
required property string modelData
readonly property list<var> notifs: Notifs.list.filter(notif => notif.appName === modelData)
readonly property list<var> notifs: Notifs.notClosed.filter(notif => notif.appName === modelData)
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"

View file

@ -13,6 +13,8 @@ Item {
required property Item panels
readonly property int padding: Appearance.padding.large
property bool shouldShow: false
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
@ -20,13 +22,16 @@ Item {
implicitWidth: Config.notifs.sizes.width + padding * 2
implicitHeight: {
const count = list.count;
if (count === 0)
if (count === 0 || !shouldShow)
return 0;
let height = (count - 1) * Appearance.spacing.smaller;
for (let i = 0; i < count; i++)
height += list.itemAtIndex(i)?.nonAnimHeight ?? 0;
const screenHeight = QsWindow.window?.screen?.height ?? 0;
const maxHeight = Math.floor(screenHeight * 0.45);
if (visibilities && panels) {
if (visibilities.osd) {
const h = panels.osd.y - Config.border.rounding * 2 - padding * 2;
@ -41,7 +46,8 @@ Item {
}
}
return Math.min((QsWindow.window?.screen?.height ?? 0) - Config.border.thickness * 2, height + padding * 2);
const availableHeight = Math.min(maxHeight, screenHeight - Config.border.thickness * 2);
return Math.min(availableHeight, height + padding * 2);
}
ClippingWrapperRectangle {
@ -55,7 +61,7 @@ Item {
id: list
model: ScriptModel {
values: Notifs.popups.filter(n => !n.closed)
values: [...Notifs.notClosed]
}
anchors.fill: parent
@ -192,6 +198,52 @@ Item {
}
}
Timer {
id: hideTimer
interval: 5000
onTriggered: {
if (list.count > 0)
root.shouldShow = false;
}
}
function show(): void {
if (list.count > 0) {
shouldShow = true;
hideTimer.restart();
}
}
Connections {
target: list
function onCountChanged(): void {
if (list.count === 0) {
root.shouldShow = false;
hideTimer.stop();
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onEntered: {
if (list.count > 0) {
root.shouldShow = true;
hideTimer.restart();
}
}
onExited: {
if (list.count > 0) {
root.shouldShow = false;
hideTimer.stop();
}
}
}
Behavior on implicitHeight {
Anim {}
}

View file

@ -17,22 +17,35 @@ StyledRect {
required property Notifs.Notif modelData
readonly property bool hasImage: modelData.image.length > 0
readonly property bool hasAppIcon: modelData.appIcon.length > 0
readonly property int nonAnimHeight: summary.implicitHeight + (root.expanded ? appName.height + body.height + actions.height + actions.anchors.topMargin : bodyPreview.height) + inner.anchors.margins * 2
readonly property bool isCritical: modelData.urgency === NotificationUrgency.Critical
readonly property bool isLow: modelData.urgency === NotificationUrgency.Low
readonly property int nonAnimHeight: {
const baseHeight = summary.implicitHeight + inner.anchors.margins * 2;
return root.expanded
? baseHeight + appName.height + body.height + actions.height + actions.anchors.topMargin
: baseHeight + bodyPreview.height;
}
property bool expanded
property bool disableSlideIn: false
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3secondaryContainer : Colours.tPalette.m3surfaceContainer
color: root.isCritical
? Colours.palette.m3secondaryContainer
: Colours.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal
implicitWidth: Config.notifs.sizes.width
implicitHeight: inner.implicitHeight
x: Config.notifs.sizes.width
x: disableSlideIn ? 0 : Config.notifs.sizes.width
Component.onCompleted: {
x = 0;
if (!root.disableSlideIn) {
x = 0;
}
modelData.lock(this);
}
Component.onDestruction: modelData.unlock(this)
Behavior on x {
enabled: !disableSlideIn
Anim {
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
}
@ -134,8 +147,8 @@ StyledRect {
Loader {
id: appIcon
active: root.hasAppIcon || !root.hasImage
asynchronous: true
active: !root.hasImage || root.hasAppIcon
asynchronous: false
anchors.horizontalCenter: root.hasImage ? undefined : image.horizontalCenter
anchors.verticalCenter: root.hasImage ? undefined : image.verticalCenter
@ -144,7 +157,11 @@ StyledRect {
sourceComponent: StyledRect {
radius: Appearance.rounding.full
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colours.layer(Colours.palette.m3surfaceContainerHighest, 2) : Colours.palette.m3secondaryContainer
color: {
if (root.isCritical) return Colours.palette.m3error;
if (root.isLow) return Colours.layer(Colours.palette.m3surfaceContainerHighest, 2);
return Colours.palette.m3secondaryContainer;
}
implicitWidth: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
implicitHeight: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
@ -152,7 +169,8 @@ StyledRect {
id: icon
active: root.hasAppIcon
asynchronous: true
asynchronous: false
visible: active
anchors.centerIn: parent
@ -162,14 +180,19 @@ StyledRect {
sourceComponent: ColouredIcon {
anchors.fill: parent
source: Quickshell.iconPath(root.modelData.appIcon)
colour: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
colour: {
if (root.isCritical) return Colours.palette.m3onError;
if (root.isLow) return Colours.palette.m3onSurface;
return Colours.palette.m3onSecondaryContainer;
}
layer.enabled: root.modelData.appIcon.endsWith("symbolic")
}
}
Loader {
active: !root.hasAppIcon
asynchronous: true
asynchronous: false
visible: active
anchors.centerIn: parent
anchors.horizontalCenterOffset: -Appearance.font.size.large * 0.02
anchors.verticalCenterOffset: Appearance.font.size.large * 0.02
@ -177,7 +200,11 @@ StyledRect {
sourceComponent: MaterialIcon {
text: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency)
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
color: {
if (root.isCritical) return Colours.palette.m3onError;
if (root.isLow) return Colours.palette.m3onSurface;
return Colours.palette.m3onSecondaryContainer;
}
font.pointSize: Appearance.font.size.large
}
}
@ -322,7 +349,9 @@ StyledRect {
StateLayer {
radius: Appearance.rounding.full
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
color: root.isCritical
? Colours.palette.m3onSecondaryContainer
: Colours.palette.m3onSurface
function onClicked() {
root.expanded = !root.expanded;
@ -442,8 +471,12 @@ StyledRect {
required property var modelData
readonly property bool isCritical: root.isCritical
radius: Appearance.rounding.full
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3secondary : Colours.layer(Colours.palette.m3surfaceContainerHigh, 2)
color: isCritical
? Colours.palette.m3secondary
: Colours.layer(Colours.palette.m3surfaceContainerHigh, 2)
Layout.preferredWidth: actionText.width + Appearance.padding.normal * 2
Layout.preferredHeight: actionText.height + Appearance.padding.small * 2
@ -452,7 +485,9 @@ StyledRect {
StateLayer {
radius: Appearance.rounding.full
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onSecondary : Colours.palette.m3onSurface
color: isCritical
? Colours.palette.m3onSecondary
: Colours.palette.m3onSurface
function onClicked(): void {
action.modelData.invoke();
@ -464,7 +499,9 @@ StyledRect {
anchors.centerIn: parent
text: actionTextMetrics.elidedText
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onSecondary : Colours.palette.m3onSurfaceVariant
color: isCritical
? Colours.palette.m3onSecondary
: Colours.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
}

View file

@ -0,0 +1,154 @@
import qs.components
import qs.components.effects
import qs.services
import qs.config
import qs.utils
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Notifications
import QtQuick
import QtQuick.Layouts
StyledRect {
id: root
required property Notifs.Notif modelData
readonly property bool hasImage: modelData.image.length > 0
readonly property bool hasAppIcon: modelData.appIcon.length > 0
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: layout.implicitHeight + Appearance.padding.smaller * 2
radius: Appearance.rounding.normal
color: Colours.palette.m3surface
border.width: 1
border.color: Colours.palette.m3outlineVariant
Elevation {
anchors.fill: parent
radius: parent.radius
opacity: parent.opacity
z: -1
level: 3
}
RowLayout {
id: layout
anchors.fill: parent
anchors.margins: Appearance.padding.smaller
anchors.leftMargin: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.normal
spacing: Appearance.spacing.normal
Item {
Layout.preferredWidth: Config.notifs.sizes.image
Layout.preferredHeight: Config.notifs.sizes.image
Loader {
id: imageLoader
active: root.hasImage
asynchronous: true
anchors.fill: parent
sourceComponent: ClippingRectangle {
radius: Appearance.rounding.full
implicitWidth: Config.notifs.sizes.image
implicitHeight: Config.notifs.sizes.image
Image {
anchors.fill: parent
source: Qt.resolvedUrl(root.modelData.image)
fillMode: Image.PreserveAspectCrop
cache: false
asynchronous: true
}
}
}
Loader {
id: appIconLoader
active: root.hasAppIcon || !root.hasImage
asynchronous: true
anchors.horizontalCenter: root.hasImage ? undefined : parent.horizontalCenter
anchors.verticalCenter: root.hasImage ? undefined : parent.verticalCenter
anchors.right: root.hasImage ? parent.right : undefined
anchors.bottom: root.hasImage ? parent.bottom : undefined
sourceComponent: StyledRect {
radius: Appearance.rounding.full
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colours.layer(Colours.palette.m3surfaceContainerHighest, 2) : Colours.palette.m3secondaryContainer
implicitWidth: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
implicitHeight: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
Loader {
id: appIcon
active: root.hasAppIcon
asynchronous: true
anchors.centerIn: parent
width: Math.round(parent.width * 0.6)
height: Math.round(parent.width * 0.6)
sourceComponent: ColouredIcon {
anchors.fill: parent
source: Quickshell.iconPath(root.modelData.appIcon)
colour: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
layer.enabled: root.modelData.appIcon.endsWith("symbolic")
}
}
Loader {
active: !root.hasAppIcon
asynchronous: true
anchors.centerIn: parent
anchors.horizontalCenterOffset: -Appearance.font.size.large * 0.02
anchors.verticalCenterOffset: Appearance.font.size.large * 0.02
sourceComponent: MaterialIcon {
text: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency)
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
font.pointSize: Appearance.font.size.large
}
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
StyledText {
id: title
Layout.fillWidth: true
text: root.modelData.summary
color: Colours.palette.m3onSurface
font.pointSize: Appearance.font.size.normal
elide: Text.ElideRight
}
StyledText {
Layout.fillWidth: true
textFormat: Text.StyledText
text: root.modelData.body
color: Colours.palette.m3onSurface
opacity: 0.8
elide: Text.ElideRight
}
}
}
Behavior on border.color {
CAnim {}
}
}

View file

@ -0,0 +1,186 @@
pragma ComponentBehavior: Bound
import qs.components
import qs.config
import qs.services
import Quickshell
import Quickshell.Widgets
import QtQuick
Item {
id: root
required property Item panels
readonly property int spacing: Appearance.spacing.small
readonly property int maxToasts: 5
readonly property bool listVisible: panels.notifications.content.shouldShow
property bool flag
property var activeToasts: new Set()
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Appearance.padding.normal
implicitWidth: Config.notifs.sizes.width
implicitHeight: {
if (listVisible)
return 0;
let height = -spacing;
for (let i = 0; i < repeater.count; i++) {
const item = repeater.itemAt(i) as ToastWrapper;
if (item && !item.modelData.closed && !item.previewHidden)
height += item.implicitHeight + spacing;
}
return height;
}
opacity: listVisible ? 0 : 1
visible: opacity > 0
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
}
}
Repeater {
id: repeater
model: ScriptModel {
values: {
const toasts = [];
let visibleCount = 0;
for (const notif of Notifs.list) {
if (notif.showAsToast) {
root.activeToasts.add(notif);
}
if (notif.closed) {
root.activeToasts.delete(notif);
}
}
for (const notif of Notifs.list) {
if (root.activeToasts.has(notif)) {
toasts.push(notif);
if (notif.showAsToast && !notif.closed) {
visibleCount++;
if (visibleCount > root.maxToasts)
break;
}
}
}
return toasts;
}
onValuesChanged: root.flagChanged()
}
ToastWrapper {}
}
component ToastWrapper: MouseArea {
id: toast
required property int index
required property Notifs.Notif modelData
readonly property bool previewHidden: {
let extraHidden = 0;
for (let i = 0; i < index; i++) {
const item = repeater.itemAt(i);
if (item && item.modelData.closed)
extraHidden++;
}
return index >= root.maxToasts + extraHidden;
}
opacity: modelData.closed || previewHidden || !modelData.showAsToast ? 0 : 1
scale: modelData.closed || previewHidden || !modelData.showAsToast ? 0.7 : 1
anchors.topMargin: {
root.flag;
let margin = 0;
for (let i = 0; i < index; i++) {
const item = repeater.itemAt(i) as ToastWrapper;
if (item && !item.modelData.closed && !item.previewHidden)
margin += item.implicitHeight + root.spacing;
}
return margin;
}
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
implicitHeight: toastInner.implicitHeight
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
onClicked: {
modelData.showAsToast = false;
modelData.close();
}
Component.onCompleted: modelData.lock(this)
onPreviewHiddenChanged: {
if (initAnim.running && previewHidden)
initAnim.stop();
}
Anim {
id: initAnim
Component.onCompleted: running = !toast.previewHidden
target: toast
properties: "opacity,scale"
from: 0
to: 1
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
ParallelAnimation {
running: toast.modelData.closed || (!toast.modelData.showAsToast && !toast.modelData.closed)
onStarted: toast.anchors.topMargin = toast.anchors.topMargin
onFinished: {
if (toast.modelData.closed)
toast.modelData.unlock(toast);
}
Anim {
target: toast
property: "opacity"
to: 0
}
Anim {
target: toast
property: "scale"
to: 0.7
}
}
NotificationToast {
id: toastInner
modelData: toast.modelData
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
Behavior on anchors.topMargin {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
}
}

View file

@ -8,6 +8,8 @@ Item {
required property var visibilities
required property Item panels
readonly property alias content: content
visible: height > 0
implicitWidth: Math.max(panels.sidebar.width, content.implicitWidth)
implicitHeight: content.implicitHeight

View file

@ -28,14 +28,13 @@ StyledRect {
border.width: 1
border.color: {
let colour = Colours.palette.m3outlineVariant;
if (root.modelData.type === Toast.Success)
colour = Colours.palette.m3success;
return Colours.palette.m3success;
if (root.modelData.type === Toast.Warning)
colour = Colours.palette.m3secondaryContainer;
return Colours.palette.m3secondaryContainer;
if (root.modelData.type === Toast.Error)
colour = Colours.palette.m3error;
return Qt.alpha(colour, 0.3);
return Colours.palette.m3error;
return Colours.palette.m3outlineVariant;
}
Elevation {

View file

@ -77,8 +77,10 @@ Singleton {
onNotification: notif => {
notif.tracked = true;
const shouldShowAsToast = !props.dnd && ![...Visibilities.screens.values()].some(v => v.sidebar);
const comp = notifComp.createObject(root, {
popup: !props.dnd && ![...Visibilities.screens.values()].some(v => v.sidebar),
popup: shouldShowAsToast,
showAsToast: shouldShowAsToast,
notification: notif
});
root.list = [comp, ...root.list];
@ -143,6 +145,7 @@ Singleton {
property bool popup
property bool closed
property bool showAsToast: false
property var locks: new Set()
property date time: new Date()
@ -177,6 +180,38 @@ Singleton {
property list<var> actions
readonly property Timer timer: Timer {
id: toastTimer
running: notif.showAsToast
interval: {
let timeout = notif.expireTimeout;
if (timeout <= 0) {
switch (notif.urgency) {
case NotificationUrgency.Critical:
timeout = 10000;
break;
case NotificationUrgency.Normal:
timeout = 5000;
break;
case NotificationUrgency.Low:
timeout = 5000;
break;
default:
timeout = 5000;
}
}
return timeout;
}
onTriggered: {
if (Config.notifs.expire)
notif.popup = false;
if (notif.showAsToast) {
notif.showAsToast = false;
}
}
}
readonly property Timer popupTimer: Timer {
running: true
interval: notif.expireTimeout > 0 ? notif.expireTimeout : Config.notifs.defaultExpireTimeout
onTriggered: {