import QtQuick import QtQuick.Layouts import QtQuick.Controls import Quickshell import Quickshell.Wayland import Quickshell.Services.Notifications import qs.Commons import qs.Services.System import qs.Widgets import qs.Modules.MainScreen // Notification History panel SmartPanel { id: root preferredWidth: Math.round(420 * Style.uiScaleRatio) preferredHeight: Math.round(540 * Style.uiScaleRatio) onOpened: function () { NotificationService.updateLastSeenTs() } panelContent: Rectangle { id: notificationRect color: Color.transparent ColumnLayout { id: mainColumn anchors.fill: parent anchors.margins: Style.marginL spacing: Style.marginM // Header section NBox { Layout.fillWidth: true implicitHeight: headerRow.implicitHeight + (Style.marginM * 2) RowLayout { id: headerRow anchors.fill: parent anchors.margins: Style.marginM spacing: Style.marginM NIcon { icon: "bell" pointSize: Style.fontSizeXXL color: Color.mPrimary } NText { text: I18n.tr("notifications.panel.title") pointSize: Style.fontSizeL font.weight: Style.fontWeightBold color: Color.mOnSurface Layout.fillWidth: true } NIconButton { icon: NotificationService.doNotDisturb ? "bell-off" : "bell" tooltipText: NotificationService.doNotDisturb ? I18n.tr("tooltips.do-not-disturb-enabled") : I18n.tr("tooltips.do-not-disturb-disabled") baseSize: Style.baseWidgetSize * 0.8 onClicked: NotificationService.doNotDisturb = !NotificationService.doNotDisturb } NIconButton { icon: "trash" tooltipText: I18n.tr("tooltips.clear-history") baseSize: Style.baseWidgetSize * 0.8 onClicked: { NotificationService.clearHistory() // Close panel as there is nothing more to see. root.close() } } NIconButton { icon: "close" tooltipText: I18n.tr("tooltips.close") baseSize: Style.baseWidgetSize * 0.8 onClicked: root.close() } } } // Empty state when no notifications ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignHCenter visible: NotificationService.historyList.count === 0 spacing: Style.marginL Item { Layout.fillHeight: true } NIcon { icon: "bell-off" pointSize: 48 color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } NText { text: I18n.tr("notifications.panel.no-notifications") pointSize: Style.fontSizeL color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } NText { text: I18n.tr("notifications.panel.description") pointSize: Style.fontSizeS color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter } Item { Layout.fillHeight: true } } // Notification list NScrollView { id: scrollView Layout.fillWidth: true Layout.fillHeight: true ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.vertical.policy: ScrollBar.AsNeeded clip: true visible: NotificationService.historyList.count > 0 // Track which notification is expanded property string expandedId: "" contentWidth: availableWidth Column { width: scrollView.width spacing: Style.marginM Repeater { model: NotificationService.historyList delegate: Item { id: notificationDelegate width: parent.width height: contentColumn.height + (Style.marginM * 2) property string notificationId: model.id property bool isExpanded: scrollView.expandedId === notificationId property bool canExpand: summaryText.truncated || bodyText.truncated Rectangle { anchors.fill: parent radius: Style.radiusM color: Color.mSurfaceVariant border.color: Qt.alpha(Color.mOutline, Style.opacityMedium) border.width: Style.borderS Behavior on color { enabled: !Settings.data.general.animationDisabled ColorAnimation { duration: Style.animationFast } } } // Click to expand/collapse MouseArea { anchors.fill: parent anchors.rightMargin: Style.baseWidgetSize enabled: notificationDelegate.canExpand cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor onClicked: { if (scrollView.expandedId === notificationId) { scrollView.expandedId = "" } else { scrollView.expandedId = notificationId } } } Column { id: contentColumn anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.margins: Style.marginM spacing: Style.marginM Row { width: parent.width spacing: Style.marginM // Icon NImageCircled { width: Math.round(40 * Style.uiScaleRatio) height: Math.round(40 * Style.uiScaleRatio) anchors.verticalCenter: parent.verticalCenter imagePath: model.cachedImage || model.originalImage || "" borderColor: Color.transparent borderWidth: 0 fallbackIcon: "bell" fallbackIconSize: 24 } // Content Column { width: parent.width - Math.round(40 * Style.uiScaleRatio) - Style.marginM - Style.baseWidgetSize spacing: Style.marginXS // Header row with app name and timestamp Row { width: parent.width spacing: Style.marginS // Urgency indicator Rectangle { width: 6 height: 6 anchors.verticalCenter: parent.verticalCenter radius: 3 visible: model.urgency !== 1 color: { if (model.urgency === 2) return Color.mError else if (model.urgency === 0) return Color.mOnSurfaceVariant else return Color.transparent } } NText { text: model.appName || "Unknown App" pointSize: Style.fontSizeXS color: Color.mSecondary } NText { text: Time.formatRelativeTime(model.timestamp) pointSize: Style.fontSizeXS color: Color.mSecondary } } // Summary NText { id: summaryText width: parent.width text: model.summary || I18n.tr("general.no-summary") pointSize: Style.fontSizeM font.weight: Font.Medium color: Color.mOnSurface textFormat: Text.PlainText wrapMode: Text.Wrap maximumLineCount: notificationDelegate.isExpanded ? 999 : 2 elide: Text.ElideRight } // Body NText { id: bodyText width: parent.width text: model.body || "" pointSize: Style.fontSizeS color: Color.mOnSurfaceVariant textFormat: Text.PlainText wrapMode: Text.Wrap maximumLineCount: notificationDelegate.isExpanded ? 999 : 3 elide: Text.ElideRight visible: text.length > 0 } // Expand indicator Row { width: parent.width visible: !notificationDelegate.isExpanded && notificationDelegate.canExpand spacing: Style.marginXS Item { width: parent.width - expandText.width - expandIcon.width - Style.marginXS height: 1 } NText { id: expandText text: I18n.tr("notifications.panel.click-to-expand") || "Click to expand" pointSize: Style.fontSizeXS color: Color.mPrimary font.weight: Font.Medium } NIcon { id: expandIcon icon: "chevron-down" pointSize: Style.fontSizeS color: Color.mPrimary } } } // Delete button NIconButton { icon: "trash" tooltipText: I18n.tr("tooltips.delete-notification") baseSize: Style.baseWidgetSize * 0.7 anchors.verticalCenter: parent.verticalCenter onClicked: { NotificationService.removeFromHistory(notificationId) } } } } } } } } } } }