summaryrefslogtreecommitdiff
path: root/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications')
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml277
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/HistoryNotificationList.qml387
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/KeyboardNavigatedNotificationList.qml311
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationCard.qml1073
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationCenterPopout.qml321
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationEmptyState.qml34
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationHeader.qml158
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationKeyboardController.qml556
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationKeyboardHints.qml50
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationSettings.qml449
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Popup/NotificationPopup.qml1164
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml239
12 files changed, 0 insertions, 5019 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml
deleted file mode 100644
index 5e033e5..0000000
--- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml
+++ /dev/null
@@ -1,277 +0,0 @@
-import QtQuick
-import QtQuick.Effects
-import Quickshell
-import qs.Common
-import qs.Services
-import qs.Widgets
-
-Rectangle {
- id: root
-
- required property var historyItem
- property bool isSelected: false
- property bool keyboardNavigationActive: false
- property bool descriptionExpanded: NotificationService.expandedMessages[historyItem?.id ? (historyItem.id + "_hist") : ""] || false
- property bool __initialized: false
-
- Component.onCompleted: {
- Qt.callLater(() => {
- if (root)
- root.__initialized = true;
- });
- }
-
- readonly property bool compactMode: SettingsData.notificationCompactMode
- readonly property real cardPadding: compactMode ? Theme.notificationCardPaddingCompact : Theme.notificationCardPadding
- readonly property real iconSize: compactMode ? Theme.notificationIconSizeCompact : Theme.notificationIconSizeNormal
- readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS
- readonly property real collapsedContentHeight: iconSize + cardPadding
- readonly property real baseCardHeight: cardPadding * 2 + collapsedContentHeight
-
- width: parent ? parent.width : 400
- height: baseCardHeight + contentItem.extraHeight
- radius: Theme.cornerRadius
- clip: false
- readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
-
- ElevationShadow {
- id: shadowLayer
- anchors.fill: parent
- z: -1
- level: Theme.elevationLevel1
- fallbackOffset: 1
- targetRadius: root.radius
- targetColor: root.color
- borderColor: root.border.color
- borderWidth: root.border.width
- shadowEnabled: root.shadowsAllowed
- }
-
- color: {
- if (isSelected && keyboardNavigationActive)
- return Theme.primaryPressed;
- return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency);
- }
- border.color: {
- if (isSelected && keyboardNavigationActive)
- return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5);
- if (historyItem.urgency === 2)
- return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
- return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05);
- }
- border.width: {
- if (isSelected && keyboardNavigationActive)
- return 1.5;
- if (historyItem.urgency === 2)
- return 2;
- return 0;
- }
-
- Behavior on border.color {
- enabled: root.__initialized
- ColorAnimation {
- duration: root.__initialized ? Theme.shortDuration : 0
- easing.type: Theme.standardEasing
- }
- }
-
- Rectangle {
- anchors.fill: parent
- radius: parent.radius
- visible: historyItem.urgency === 2
- gradient: Gradient {
- orientation: Gradient.Horizontal
- GradientStop {
- position: 0.0
- color: Theme.primary
- }
- GradientStop {
- position: 0.02
- color: Theme.primary
- }
- GradientStop {
- position: 0.021
- color: "transparent"
- }
- }
- }
-
- Item {
- id: contentItem
-
- readonly property real expandedTextHeight: descriptionText.contentHeight
- readonly property real twoLineHeight: descriptionText.font.pixelSize * 1.2 * 2
- readonly property real extraHeight: (descriptionExpanded && expandedTextHeight > twoLineHeight + 2) ? (expandedTextHeight - twoLineHeight) : 0
-
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.topMargin: cardPadding
- anchors.leftMargin: Theme.spacingL
- anchors.rightMargin: Theme.spacingL + Theme.notificationHoverRevealMargin
- height: collapsedContentHeight + extraHeight
-
- DankCircularImage {
- id: iconContainer
- readonly property string rawImage: historyItem.image || ""
- readonly property string iconFromImage: {
- if (rawImage.startsWith("image://icon/"))
- return rawImage.substring(13);
- return "";
- }
- readonly property bool imageHasSpecialPrefix: {
- const icon = iconFromImage;
- return icon.startsWith("material:") || icon.startsWith("svg:") || icon.startsWith("unicode:") || icon.startsWith("image:");
- }
- readonly property bool hasNotificationImage: rawImage !== "" && !rawImage.startsWith("image://icon/")
-
- width: iconSize
- height: iconSize
- anchors.left: parent.left
- anchors.top: parent.top
-
- imageSource: {
- if (hasNotificationImage)
- return historyItem.image;
- if (imageHasSpecialPrefix)
- return "";
- const appIcon = historyItem.appIcon;
- if (!appIcon)
- return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
- if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
- return appIcon;
- if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
- return "";
- return Paths.resolveIconPath(appIcon);
- }
-
- hasImage: hasNotificationImage
- fallbackIcon: {
- if (imageHasSpecialPrefix)
- return iconFromImage;
- return historyItem.appIcon || iconFromImage || "";
- }
- fallbackText: {
- const appName = historyItem.appName || "?";
- return appName.charAt(0).toUpperCase();
- }
-
- Rectangle {
- anchors.fill: parent
- anchors.margins: -2
- radius: width / 2
- color: "transparent"
- border.color: root.color
- border.width: 5
- visible: parent.hasImage
- antialiasing: true
- }
- }
-
- Rectangle {
- anchors.left: iconContainer.right
- anchors.leftMargin: Theme.spacingM
- anchors.right: parent.right
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- anchors.bottomMargin: contentSpacing
- color: "transparent"
-
- Column {
- width: parent.width
- anchors.top: parent.top
- spacing: Theme.notificationContentSpacing
-
- Row {
- width: parent.width
- spacing: Theme.spacingXS
- readonly property real reservedTrailingWidth: historySeparator.implicitWidth + Math.max(historyTimeText.implicitWidth, 72) + spacing
-
- StyledText {
- id: historyTitleText
- width: Math.min(implicitWidth, Math.max(0, parent.width - parent.reservedTrailingWidth))
- text: {
- let title = historyItem.summary || "";
- const appName = historyItem.appName || "";
- const prefix = appName + " • ";
- if (appName && title.toLowerCase().startsWith(prefix.toLowerCase())) {
- title = title.substring(prefix.length);
- }
- return title;
- }
- color: Theme.surfaceText
- font.pixelSize: Theme.fontSizeMedium
- font.weight: Font.Medium
- elide: Text.ElideRight
- maximumLineCount: 1
- visible: text.length > 0
- }
- StyledText {
- id: historySeparator
- text: (historyTitleText.text.length > 0 && historyTimeText.text.length > 0) ? " • " : ""
- color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Normal
- }
- StyledText {
- id: historyTimeText
- text: NotificationService.formatHistoryTime(historyItem.timestamp)
- color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Normal
- visible: text.length > 0
- }
- }
-
- StyledText {
- id: descriptionText
- property bool hasMoreText: truncated
-
- text: historyItem.htmlBody || historyItem.body || ""
- color: Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- width: parent.width
- elide: descriptionExpanded ? Text.ElideNone : Text.ElideRight
- maximumLineCount: descriptionExpanded ? -1 : (compactMode ? 1 : 2)
- wrapMode: Text.WordWrap
- visible: text.length > 0
- linkColor: Theme.primary
- onLinkActivated: link => Qt.openUrlExternally(link)
-
- MouseArea {
- anchors.fill: parent
- cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : (parent.hasMoreText || descriptionExpanded) ? Qt.PointingHandCursor : Qt.ArrowCursor
-
- onClicked: mouse => {
- if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) {
- const messageId = historyItem?.id ? (historyItem.id + "_hist") : "";
- NotificationService.toggleMessageExpansion(messageId);
- }
- }
-
- propagateComposedEvents: true
- onPressed: mouse => {
- if (parent.hoveredLink)
- mouse.accepted = false;
- }
- onReleased: mouse => {
- if (parent.hoveredLink)
- mouse.accepted = false;
- }
- }
- }
- }
- }
- }
-
- DankActionButton {
- anchors.top: parent.top
- anchors.right: parent.right
- anchors.topMargin: cardPadding
- anchors.rightMargin: Theme.spacingL
- iconName: "close"
- iconSize: compactMode ? 16 : 18
- buttonSize: compactMode ? 24 : 28
- onClicked: NotificationService.removeFromHistory(historyItem.id)
- }
-}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/HistoryNotificationList.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/HistoryNotificationList.qml
deleted file mode 100644
index 69fea41..0000000
--- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/HistoryNotificationList.qml
+++ /dev/null
@@ -1,387 +0,0 @@
-import QtQuick
-import Quickshell
-import qs.Common
-import qs.Services
-import qs.Widgets
-
-Item {
- id: root
-
- property string selectedFilterKey: "all"
- property var keyboardController: null
- property bool keyboardActive: false
- property int selectedIndex: -1
- property bool showKeyboardHints: false
-
- function getStartOfDay(date) {
- const d = new Date(date);
- d.setHours(0, 0, 0, 0);
- return d;
- }
-
- function getFilterRange(key) {
- const now = new Date();
- const startOfToday = getStartOfDay(now);
- const startOfYesterday = new Date(startOfToday.getTime() - 86400000);
-
- switch (key) {
- case "all":
- return {
- start: null,
- end: null
- };
- case "1h":
- return {
- start: new Date(now.getTime() - 3600000),
- end: null
- };
- case "today":
- return {
- start: startOfToday,
- end: null
- };
- case "yesterday":
- return {
- start: startOfYesterday,
- end: startOfToday
- };
- case "older":
- return {
- start: null,
- end: getOlderCutoff()
- };
- case "7d":
- return {
- start: new Date(now.getTime() - 7 * 86400000),
- end: null
- };
- case "30d":
- return {
- start: new Date(now.getTime() - 30 * 86400000),
- end: null
- };
- default:
- return {
- start: null,
- end: null
- };
- }
- }
-
- function countForFilter(key) {
- const range = getFilterRange(key);
- if (!range.start && !range.end)
- return NotificationService.historyList.length;
- return NotificationService.historyList.filter(n => {
- const ts = n.timestamp;
- if (range.start && ts < range.start.getTime())
- return false;
- if (range.end && ts >= range.end.getTime())
- return false;
- return true;
- }).length;
- }
-
- readonly property var allFilters: [
- {
- label: I18n.tr("All", "notification history filter"),
- key: "all",
- maxDays: 0
- },
- {
- label: I18n.tr("Last hour", "notification history filter"),
- key: "1h",
- maxDays: 1
- },
- {
- label: I18n.tr("Today", "notification history filter"),
- key: "today",
- maxDays: 1
- },
- {
- label: I18n.tr("Yesterday", "notification history filter"),
- key: "yesterday",
- maxDays: 2
- },
- {
- label: I18n.tr("7 days", "notification history filter"),
- key: "7d",
- maxDays: 7
- },
- {
- label: I18n.tr("30 days", "notification history filter"),
- key: "30d",
- maxDays: 30
- },
- {
- label: I18n.tr("Older", "notification history filter for content older than other filters"),
- key: "older",
- maxDays: 0
- }
- ]
-
- function filterRelevantForRetention(filter) {
- const retention = SettingsData.notificationHistoryMaxAgeDays;
- if (filter.key === "older") {
- if (retention === 0)
- return true;
- return retention > 2 && retention < 7 || retention > 30;
- }
- if (retention === 0)
- return true;
- if (filter.maxDays === 0)
- return true;
- return filter.maxDays <= retention;
- }
-
- function getOlderCutoff() {
- const retention = SettingsData.notificationHistoryMaxAgeDays;
- const now = new Date();
- if (retention === 0 || retention > 30)
- return new Date(now.getTime() - 30 * 86400000);
- if (retention >= 7)
- return new Date(now.getTime() - 7 * 86400000);
- const startOfToday = getStartOfDay(now);
- return new Date(startOfToday.getTime() - 86400000);
- }
-
- readonly property var visibleFilters: {
- const result = [];
- const retention = SettingsData.notificationHistoryMaxAgeDays;
- for (let i = 0; i < allFilters.length; i++) {
- const f = allFilters[i];
- if (!filterRelevantForRetention(f))
- continue;
- const count = countForFilter(f.key);
- if (f.key === "all" || count > 0) {
- result.push({
- label: f.label,
- key: f.key,
- count: count
- });
- }
- }
- return result;
- }
-
- onVisibleFiltersChanged: {
- let found = false;
- for (let i = 0; i < visibleFilters.length; i++) {
- if (visibleFilters[i].key === selectedFilterKey) {
- found = true;
- break;
- }
- }
- if (!found)
- selectedFilterKey = "all";
- }
-
- function getFilteredHistory() {
- const range = getFilterRange(selectedFilterKey);
- if (!range.start && !range.end)
- return NotificationService.historyList;
- return NotificationService.historyList.filter(n => {
- const ts = n.timestamp;
- if (range.start && ts < range.start.getTime())
- return false;
- if (range.end && ts >= range.end.getTime())
- return false;
- return true;
- });
- }
-
- function getChipIndex() {
- for (let i = 0; i < visibleFilters.length; i++) {
- if (visibleFilters[i].key === selectedFilterKey)
- return i;
- }
- return 0;
- }
-
- function enableAutoScroll() {
- }
-
- function removeWithScrollPreserve(itemId) {
- historyListView.savedY = historyListView.contentY;
- NotificationService.removeFromHistory(itemId);
- Qt.callLater(() => {
- historyListView.forceLayout();
- });
- }
-
- Column {
- anchors.fill: parent
- spacing: Theme.spacingS
-
- DankFilterChips {
- id: filterChips
- width: parent.width
- currentIndex: root.getChipIndex()
- showCounts: true
- model: root.visibleFilters
- onSelectionChanged: index => {
- if (index >= 0 && index < root.visibleFilters.length) {
- root.selectedFilterKey = root.visibleFilters[index].key;
- }
- }
- }
-
- DankListView {
- id: historyListView
- width: parent.width
- height: parent.height - filterChips.height - Theme.spacingS
- clip: true
- spacing: Theme.spacingS
- readonly property real horizontalShadowGutter: Theme.snap(Math.max(Theme.spacingXS, 4), 1)
- readonly property real verticalShadowGutter: Theme.snap(Math.max(Theme.spacingS, 8), 1)
- readonly property real delegateShadowGutter: Theme.snap(Math.max(Theme.spacingXS, 4), 1)
- topMargin: verticalShadowGutter
- bottomMargin: verticalShadowGutter
-
- model: ScriptModel {
- id: historyModel
- values: root.getFilteredHistory()
- objectProp: "id"
- }
-
- NotificationEmptyState {
- visible: historyListView.count === 0
- y: Theme.spacingL
- anchors.horizontalCenter: parent.horizontalCenter
- }
-
- delegate: Item {
- id: delegateRoot
- required property var modelData
- required property int index
-
- property real swipeOffset: 0
- property bool isDismissing: false
- readonly property real dismissThreshold: width * 0.35
- property bool __delegateInitialized: false
-
- Component.onCompleted: {
- Qt.callLater(() => {
- if (delegateRoot)
- delegateRoot.__delegateInitialized = true;
- });
- }
-
- width: ListView.view.width
- height: historyCard.height + historyListView.delegateShadowGutter
- clip: false
-
- HistoryNotificationCard {
- id: historyCard
- width: Math.max(0, parent.width - (historyListView.horizontalShadowGutter * 2))
- y: historyListView.delegateShadowGutter / 2
- x: historyListView.horizontalShadowGutter + delegateRoot.swipeOffset
- historyItem: modelData
- isSelected: root.keyboardActive && root.selectedIndex === index
- keyboardNavigationActive: root.keyboardActive
- opacity: 1 - Math.abs(delegateRoot.swipeOffset) / (delegateRoot.width * 0.5)
-
- Behavior on x {
- enabled: !swipeDragHandler.active && delegateRoot.__delegateInitialized
- NumberAnimation {
- duration: Theme.notificationExitDuration
- easing.type: Theme.standardEasing
- }
- }
-
- Behavior on opacity {
- enabled: delegateRoot.__delegateInitialized
- NumberAnimation {
- duration: delegateRoot.__delegateInitialized ? Theme.notificationExitDuration : 0
- }
- }
- }
-
- DragHandler {
- id: swipeDragHandler
- target: null
- yAxis.enabled: false
- xAxis.enabled: true
-
- onActiveChanged: {
- if (active || delegateRoot.isDismissing)
- return;
- if (Math.abs(delegateRoot.swipeOffset) > delegateRoot.dismissThreshold) {
- delegateRoot.isDismissing = true;
- root.removeWithScrollPreserve(delegateRoot.modelData?.id || "");
- } else {
- delegateRoot.swipeOffset = 0;
- }
- }
-
- onTranslationChanged: {
- if (delegateRoot.isDismissing)
- return;
- delegateRoot.swipeOffset = translation.x;
- }
- }
- }
- }
- }
-
- function selectNext() {
- if (historyModel.values.length === 0)
- return;
- keyboardActive = true;
- selectedIndex = Math.min(selectedIndex + 1, historyModel.values.length - 1);
- historyListView.positionViewAtIndex(selectedIndex, ListView.Contain);
- }
-
- function selectPrevious() {
- if (historyModel.values.length === 0)
- return;
- if (selectedIndex <= 0) {
- keyboardActive = false;
- selectedIndex = -1;
- return;
- }
- selectedIndex = Math.max(selectedIndex - 1, 0);
- historyListView.positionViewAtIndex(selectedIndex, ListView.Contain);
- }
-
- function clearSelected() {
- if (selectedIndex < 0 || selectedIndex >= historyModel.values.length)
- return;
- const item = historyModel.values[selectedIndex];
- NotificationService.removeFromHistory(item.id);
- if (historyModel.values.length === 0) {
- keyboardActive = false;
- selectedIndex = -1;
- } else {
- selectedIndex = Math.min(selectedIndex, historyModel.values.length - 1);
- }
- }
-
- function handleKey(event) {
- if (event.key === Qt.Key_Down || event.key === 16777237) {
- if (!keyboardActive) {
- keyboardActive = true;
- selectedIndex = 0;
- } else {
- selectNext();
- }
- event.accepted = true;
- } else if (event.key === Qt.Key_Up || event.key === 16777235) {
- if (keyboardActive) {
- selectPrevious();
- }
- event.accepted = true;
- } else if (keyboardActive && (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace)) {
- clearSelected();
- event.accepted = true;
- } else if ((event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) && (event.modifiers & Qt.ShiftModifier)) {
- NotificationService.clearHistory();
- keyboardActive = false;
- selectedIndex = -1;
- event.accepted = true;
- } else if (event.key === Qt.Key_F10) {
- showKeyboardHints = !showKeyboardHints;
- event.accepted = true;
- }
- }
-}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/KeyboardNavigatedNotificationList.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/KeyboardNavigatedNotificationList.qml
deleted file mode 100644
index 855163d..0000000
--- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/KeyboardNavigatedNotificationList.qml
+++ /dev/null
@@ -1,311 +0,0 @@
-import QtQuick
-import qs.Common
-import qs.Services
-import qs.Widgets
-
-DankListView {
- id: listView
-
- property var keyboardController: null
- property bool keyboardActive: false
- property bool autoScrollDisabled: false
- property bool isAnimatingExpansion: false
- property alias listContentHeight: listView.contentHeight
- property real stableContentHeight: 0
- property bool cardAnimateExpansion: true
- property bool listInitialized: false
- property int swipingCardIndex: -1
- property real swipingCardOffset: 0
- property real __pendingStableHeight: 0
- property real __heightUpdateThreshold: 20
- readonly property real shadowBlurPx: Theme.elevationEnabled ? ((Theme.elevationLevel1 && Theme.elevationLevel1.blurPx !== undefined) ? Theme.elevationLevel1.blurPx : 4) : 0
- readonly property real shadowHorizontalGutter: Theme.snap(Math.max(Theme.spacingS, Math.min(32, shadowBlurPx * 1.5 + 6)), 1)
- readonly property real shadowVerticalGutter: Theme.snap(Math.max(Theme.spacingXS, 6), 1)
- readonly property real delegateShadowGutter: Theme.snap(Math.max(Theme.spacingXS, 4), 1)
-
- Component.onCompleted: {
- Qt.callLater(() => {
- if (listView) {
- listView.listInitialized = true;
- listView.stableContentHeight = listView.contentHeight;
- }
- });
- }
-
- Timer {
- id: heightUpdateDebounce
- interval: Theme.mediumDuration + 20
- repeat: false
- onTriggered: {
- if (!listView.isAnimatingExpansion && Math.abs(listView.__pendingStableHeight - listView.stableContentHeight) > listView.__heightUpdateThreshold) {
- listView.stableContentHeight = listView.__pendingStableHeight;
- }
- }
- }
-
- onContentHeightChanged: {
- if (!isAnimatingExpansion) {
- __pendingStableHeight = contentHeight;
- if (Math.abs(contentHeight - stableContentHeight) > __heightUpdateThreshold) {
- heightUpdateDebounce.restart();
- } else {
- stableContentHeight = contentHeight;
- }
- }
- }
-
- onIsAnimatingExpansionChanged: {
- if (isAnimatingExpansion) {
- heightUpdateDebounce.stop();
- let delta = 0;
- for (let i = 0; i < count; i++) {
- const item = itemAtIndex(i);
- if (item && item.children[0] && item.children[0].isAnimating) {
- const targetDelegateHeight = item.children[0].targetHeight + listView.delegateShadowGutter;
- delta += targetDelegateHeight - item.height;
- }
- }
- const targetHeight = contentHeight + delta;
- // During expansion, always update immediately without threshold check
- stableContentHeight = targetHeight;
- } else {
- __pendingStableHeight = contentHeight;
- heightUpdateDebounce.stop();
- stableContentHeight = __pendingStableHeight;
- }
- }
-
- clip: true
- model: NotificationService.groupedNotifications
- spacing: Theme.spacingL
- topMargin: shadowVerticalGutter
- bottomMargin: shadowVerticalGutter
-
- onIsUserScrollingChanged: {
- if (isUserScrolling && keyboardController && keyboardController.keyboardNavigationActive) {
- autoScrollDisabled = true;
- }
- }
-
- function enableAutoScroll() {
- autoScrollDisabled = false;
- }
-
- Timer {
- id: positionPreservationTimer
- interval: 200
- running: keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled && !isAnimatingExpansion
- repeat: true
- onTriggered: {
- if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled && !isAnimatingExpansion) {
- keyboardController.ensureVisible();
- }
- }
- }
-
- Timer {
- id: expansionEnsureVisibleTimer
- interval: Theme.mediumDuration + 50
- repeat: false
- onTriggered: {
- if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) {
- keyboardController.ensureVisible();
- }
- }
- }
-
- NotificationEmptyState {
- visible: listView.count === 0
- y: 20
- anchors.horizontalCenter: parent.horizontalCenter
- }
-
- onModelChanged: {
- if (!keyboardController || !keyboardController.keyboardNavigationActive) {
- return;
- }
- keyboardController.rebuildFlatNavigation();
- Qt.callLater(() => {
- if (keyboardController && keyboardController.keyboardNavigationActive && !autoScrollDisabled) {
- keyboardController.ensureVisible();
- }
- });
- }
-
- delegate: Item {
- id: delegateRoot
- required property var modelData
- required property int index
-
- readonly property bool isExpanded: (NotificationService.expandedGroups[modelData && modelData.key] || false)
- property real swipeOffset: 0
- property bool isDismissing: false
- readonly property real dismissThreshold: width * 0.35
- property bool __delegateInitialized: false
-
- readonly property bool isAdjacentToSwipe: listView.count >= 2 && listView.swipingCardIndex !== -1 && (index === listView.swipingCardIndex - 1 || index === listView.swipingCardIndex + 1)
- readonly property real adjacentSwipeInfluence: isAdjacentToSwipe ? listView.swipingCardOffset * 0.10 : 0
- readonly property real adjacentScaleInfluence: isAdjacentToSwipe ? 1.0 - Math.abs(listView.swipingCardOffset) / width * 0.02 : 1.0
- readonly property real swipeFadeStartOffset: width * 0.75
- readonly property real swipeFadeDistance: Math.max(1, width - swipeFadeStartOffset)
-
- Component.onCompleted: {
- Qt.callLater(() => {
- if (delegateRoot)
- delegateRoot.__delegateInitialized = true;
- });
- }
-
- width: ListView.view.width
- height: notificationCard.height + listView.delegateShadowGutter
- clip: false
-
- NotificationCard {
- id: notificationCard
- width: Math.max(0, parent.width - (listView.shadowHorizontalGutter * 2))
- y: listView.delegateShadowGutter / 2
- x: listView.shadowHorizontalGutter + delegateRoot.swipeOffset + delegateRoot.adjacentSwipeInfluence
- listLevelAdjacentScaleInfluence: delegateRoot.adjacentScaleInfluence
- listLevelScaleAnimationsEnabled: listView.swipingCardIndex === -1 || !delegateRoot.isAdjacentToSwipe
- notificationGroup: modelData
- keyboardNavigationActive: listView.keyboardActive
- animateExpansion: listView.cardAnimateExpansion && listView.listInitialized
- opacity: {
- const swipeAmount = Math.abs(delegateRoot.swipeOffset);
- if (swipeAmount <= delegateRoot.swipeFadeStartOffset)
- return 1;
- const fadeProgress = (swipeAmount - delegateRoot.swipeFadeStartOffset) / delegateRoot.swipeFadeDistance;
- return Math.max(0, 1 - fadeProgress);
- }
- onIsAnimatingChanged: {
- if (isAnimating) {
- listView.isAnimatingExpansion = true;
- } else {
- Qt.callLater(() => {
- if (!notificationCard || !listView)
- return;
- let anyAnimating = false;
- for (let i = 0; i < listView.count; i++) {
- const item = listView.itemAtIndex(i);
- if (item && item.children[0] && item.children[0].isAnimating) {
- anyAnimating = true;
- break;
- }
- }
- listView.isAnimatingExpansion = anyAnimating;
- });
- }
- }
-
- isGroupSelected: {
- if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive)
- return false;
- keyboardController.selectionVersion;
- const selection = keyboardController.getCurrentSelection();
- return selection.type === "group" && selection.groupIndex === index;
- }
-
- selectedNotificationIndex: {
- if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive)
- return -1;
- keyboardController.selectionVersion;
- const selection = keyboardController.getCurrentSelection();
- return (selection.type === "notification" && selection.groupIndex === index) ? selection.notificationIndex : -1;
- }
-
- Behavior on x {
- enabled: !swipeDragHandler.active && !delegateRoot.isDismissing && (listView.swipingCardIndex === -1 || !delegateRoot.isAdjacentToSwipe) && listView.listInitialized
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- Behavior on opacity {
- enabled: listView.listInitialized
- NumberAnimation {
- duration: listView.listInitialized ? Theme.shortDuration : 0
- }
- }
- }
-
- DragHandler {
- id: swipeDragHandler
- target: null
- yAxis.enabled: false
- xAxis.enabled: true
-
- onActiveChanged: {
- if (active) {
- listView.swipingCardIndex = index;
- return;
- }
- listView.swipingCardIndex = -1;
- listView.swipingCardOffset = 0;
- if (delegateRoot.isDismissing)
- return;
- if (Math.abs(delegateRoot.swipeOffset) > delegateRoot.dismissThreshold) {
- delegateRoot.isDismissing = true;
- swipeDismissAnim.to = delegateRoot.swipeOffset > 0 ? delegateRoot.width : -delegateRoot.width;
- swipeDismissAnim.start();
- } else {
- delegateRoot.swipeOffset = 0;
- }
- }
-
- onTranslationChanged: {
- if (delegateRoot.isDismissing)
- return;
- delegateRoot.swipeOffset = translation.x;
- listView.swipingCardOffset = translation.x;
- }
- }
-
- NumberAnimation {
- id: swipeDismissAnim
- target: delegateRoot
- property: "swipeOffset"
- to: 0
- duration: Theme.notificationExitDuration
- easing.type: Easing.OutCubic
- onStopped: NotificationService.dismissGroup(delegateRoot.modelData?.key || "")
- }
- }
-
- Connections {
- target: NotificationService
-
- function onGroupedNotificationsChanged() {
- if (!keyboardController) {
- return;
- }
-
- if (keyboardController.isTogglingGroup) {
- keyboardController.rebuildFlatNavigation();
- return;
- }
-
- keyboardController.rebuildFlatNavigation();
-
- if (keyboardController.keyboardNavigationActive) {
- Qt.callLater(() => {
- if (!autoScrollDisabled) {
- keyboardController.ensureVisible();
- }
- });
- }
- }
-
- function onExpandedGroupsChanged() {
- if (!keyboardController || !keyboardController.keyboardNavigationActive)
- return;
- expansionEnsureVisibleTimer.restart();
- }
-
- function onExpandedMessagesChanged() {
- if (!keyboardController || !keyboardController.keyboardNavigationActive)
- return;
- expansionEnsureVisibleTimer.restart();
- }
- }
-}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationCard.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationCard.qml
deleted file mode 100644
index 7e404df..0000000
--- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationCard.qml
+++ /dev/null
@@ -1,1073 +0,0 @@
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Effects
-import Quickshell
-import Quickshell.Services.Notifications
-import qs.Common
-import qs.Services
-import qs.Widgets
-
-Rectangle {
- id: root
-
- property var notificationGroup
- property bool expanded: (NotificationService.expandedGroups[notificationGroup && notificationGroup.key] || false)
- property bool descriptionExpanded: (NotificationService.expandedMessages[(notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : ""] || false)
- property bool userInitiatedExpansion: false
- property bool isAnimating: false
- property bool animateExpansion: true
-
- property bool isGroupSelected: false
- property int selectedNotificationIndex: -1
- property bool keyboardNavigationActive: false
- property int swipingNotificationIndex: -1
- property real swipingNotificationOffset: 0
- property real listLevelAdjacentScaleInfluence: 1.0
- property bool listLevelScaleAnimationsEnabled: true
-
- readonly property bool compactMode: SettingsData.notificationCompactMode
- readonly property real cardPadding: compactMode ? Theme.notificationCardPaddingCompact : Theme.notificationCardPadding
- readonly property real iconSize: compactMode ? Theme.notificationIconSizeCompact : Theme.notificationIconSizeNormal
- readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS
- readonly property real collapsedDismissOffset: 5
- readonly property real badgeSize: compactMode ? 16 : 18
- readonly property real actionButtonHeight: compactMode ? 20 : 24
- readonly property real collapsedContentHeight: Math.max(iconSize, Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2))
- readonly property real baseCardHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + contentSpacing
-
- width: parent ? parent.width : 400
- height: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight)
- readonly property real targetHeight: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight)
- radius: Theme.cornerRadius
- scale: (cardHoverHandler.hovered ? 1.004 : 1.0) * listLevelAdjacentScaleInfluence
- readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
- readonly property var shadowElevation: Theme.elevationLevel1
- readonly property real baseShadowBlurPx: (shadowElevation && shadowElevation.blurPx !== undefined) ? shadowElevation.blurPx : 4
- readonly property real hoverShadowBlurBoost: cardHoverHandler.hovered ? Math.min(2, baseShadowBlurPx * 0.25) : 0
- property real shadowBlurPx: shadowsAllowed ? (baseShadowBlurPx + hoverShadowBlurBoost) : 0
- property real shadowOffsetXPx: shadowsAllowed ? Theme.elevationOffsetX(shadowElevation) : 0
- property real shadowOffsetYPx: shadowsAllowed ? (Theme.elevationOffsetY(shadowElevation, 1) + (cardHoverHandler.hovered ? 0.35 : 0)) : 0
- property bool __initialized: false
-
- Component.onCompleted: {
- Qt.callLater(() => {
- if (root)
- root.__initialized = true;
- });
- }
-
- Behavior on scale {
- enabled: listLevelScaleAnimationsEnabled
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- Behavior on shadowBlurPx {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- Behavior on shadowOffsetXPx {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- Behavior on shadowOffsetYPx {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- Behavior on border.color {
- enabled: root.__initialized
- ColorAnimation {
- duration: root.__initialized ? Theme.shortDuration : 0
- easing.type: Theme.standardEasing
- }
- }
-
- color: {
- if (isGroupSelected && keyboardNavigationActive) {
- return Theme.primaryPressed;
- }
- if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
- return Theme.primaryHoverLight;
- }
- return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency);
- }
- border.color: {
- if (isGroupSelected && keyboardNavigationActive) {
- return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5);
- }
- if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
- return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
- }
- if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) {
- return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
- }
- return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05);
- }
- border.width: {
- if (isGroupSelected && keyboardNavigationActive) {
- return 1.5;
- }
- if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
- return 1;
- }
- if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) {
- return 2;
- }
- return 0;
- }
- clip: false
-
- HoverHandler {
- id: cardHoverHandler
- }
-
- ElevationShadow {
- id: shadowLayer
- anchors.fill: parent
- z: -1
- level: root.shadowElevation
- targetRadius: root.radius
- targetColor: root.color
- borderColor: root.border.color
- borderWidth: root.border.width
- shadowBlurPx: root.shadowBlurPx
- shadowSpreadPx: 0
- shadowOffsetX: root.shadowOffsetXPx
- shadowOffsetY: root.shadowOffsetYPx
- shadowColor: root.shadowElevation ? Theme.elevationShadowColor(root.shadowElevation) : "transparent"
- shadowEnabled: root.shadowsAllowed
- }
-
- Rectangle {
- anchors.fill: parent
- radius: parent.radius
- visible: notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical
- gradient: Gradient {
- orientation: Gradient.Horizontal
- GradientStop {
- position: 0.0
- color: Theme.primary
- }
- GradientStop {
- position: 0.02
- color: Theme.primary
- }
- GradientStop {
- position: 0.021
- color: "transparent"
- }
- }
- opacity: 1.0
- }
-
- Item {
- id: collapsedContent
-
- readonly property real expandedTextHeight: descriptionText.contentHeight
- readonly property real collapsedLineCount: compactMode ? 1 : 2
- readonly property real collapsedLineHeight: Theme.fontSizeSmall * 1.2 * collapsedLineCount
- readonly property real extraHeight: (descriptionExpanded && expandedTextHeight > collapsedLineHeight + 2) ? (expandedTextHeight - collapsedLineHeight) : 0
-
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.topMargin: cardPadding
- anchors.leftMargin: Theme.spacingL
- anchors.rightMargin: Theme.spacingL + Theme.notificationHoverRevealMargin
- height: collapsedContentHeight + extraHeight
- visible: !expanded
-
- DankCircularImage {
- id: iconContainer
- readonly property string rawImage: notificationGroup?.latestNotification?.image || ""
- readonly property string iconFromImage: {
- if (rawImage.startsWith("image://icon/"))
- return rawImage.substring(13);
- return "";
- }
- readonly property bool imageHasSpecialPrefix: {
- const icon = iconFromImage;
- return icon.startsWith("material:") || icon.startsWith("svg:") || icon.startsWith("unicode:") || icon.startsWith("image:");
- }
- readonly property bool hasNotificationImage: rawImage !== "" && !rawImage.startsWith("image://icon/")
-
- width: iconSize
- height: iconSize
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.topMargin: descriptionExpanded ? Math.max(0, Theme.fontSizeSmall * 1.2 + (Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2)) / 2 - iconSize / 2) : Math.max(0, Theme.fontSizeSmall * 1.2 + (textContainer.height - Theme.fontSizeSmall * 1.2) / 2 - iconSize / 2)
-
- imageSource: {
- if (hasNotificationImage)
- return notificationGroup.latestNotification.cleanImage;
- if (imageHasSpecialPrefix)
- return "";
- const appIcon = notificationGroup?.latestNotification?.appIcon;
- if (!appIcon)
- return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
- if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
- return appIcon;
- if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
- return "";
- return Paths.resolveIconPath(appIcon);
- }
-
- hasImage: hasNotificationImage
- fallbackIcon: {
- if (imageHasSpecialPrefix)
- return iconFromImage;
- return notificationGroup?.latestNotification?.appIcon || iconFromImage || "";
- }
- fallbackText: {
- const appName = notificationGroup?.appName || "?";
- return appName.charAt(0).toUpperCase();
- }
-
- Rectangle {
- anchors.fill: parent
- anchors.margins: -2
- radius: width / 2
- color: "transparent"
- border.color: root.color
- border.width: 5
- visible: parent.hasImage
- antialiasing: true
- }
-
- Rectangle {
- width: badgeSize
- height: badgeSize
- radius: badgeSize / 2
- color: Theme.primary
- anchors.top: parent.top
- anchors.right: parent.right
- anchors.topMargin: -2
- anchors.rightMargin: -2
- visible: (notificationGroup?.count || 0) > 1
-
- StyledText {
- anchors.centerIn: parent
- text: (notificationGroup?.count || 0) > 99 ? "99+" : (notificationGroup?.count || 0).toString()
- color: Theme.primaryText
- font.pixelSize: compactMode ? 8 : 9
- font.weight: Font.Bold
- }
- }
- }
-
- Rectangle {
- id: textContainer
-
- anchors.left: iconContainer.right
- anchors.leftMargin: Theme.spacingM
- anchors.right: parent.right
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- anchors.bottomMargin: contentSpacing
- color: "transparent"
-
- Column {
- width: parent.width
- anchors.top: parent.top
- spacing: Theme.notificationContentSpacing
-
- Row {
- id: collapsedHeaderRow
- width: parent.width
- spacing: Theme.spacingXS
- visible: (collapsedHeaderAppNameText.text.length > 0 || collapsedHeaderTimeText.text.length > 0)
-
- StyledText {
- id: collapsedHeaderAppNameText
- text: notificationGroup?.appName || ""
- color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Normal
- elide: Text.ElideRight
- maximumLineCount: 1
- width: Math.min(implicitWidth, parent.width - collapsedHeaderSeparator.implicitWidth - collapsedHeaderTimeText.implicitWidth - parent.spacing * 2)
- }
-
- StyledText {
- id: collapsedHeaderSeparator
- text: (collapsedHeaderAppNameText.text.length > 0 && collapsedHeaderTimeText.text.length > 0) ? " • " : ""
- color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Normal
- }
-
- StyledText {
- id: collapsedHeaderTimeText
- text: notificationGroup?.latestNotification?.timeStr || ""
- color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Normal
- }
- }
-
- StyledText {
- id: collapsedTitleText
- width: parent.width
- text: notificationGroup?.latestNotification?.summary || ""
- color: Theme.surfaceText
- font.pixelSize: Theme.fontSizeMedium
- font.weight: Font.Medium
- elide: Text.ElideRight
- maximumLineCount: 1
- visible: text.length > 0
- }
-
- StyledText {
- id: descriptionText
- property string fullText: (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.htmlBody) || ""
- property bool hasMoreText: truncated
-
- text: fullText
- color: Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- width: parent.width
- elide: Text.ElideRight
- maximumLineCount: descriptionExpanded ? -1 : (compactMode ? 1 : 2)
- wrapMode: Text.WordWrap
- visible: text.length > 0
- linkColor: Theme.primary
- onLinkActivated: link => Qt.openUrlExternally(link)
-
- MouseArea {
- anchors.fill: parent
- cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : (parent.hasMoreText || descriptionExpanded) ? Qt.PointingHandCursor : Qt.ArrowCursor
-
- onClicked: mouse => {
- if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) {
- root.userInitiatedExpansion = true;
- const messageId = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : "";
- NotificationService.toggleMessageExpansion(messageId);
- Qt.callLater(() => {
- if (root && !root.isAnimating)
- root.userInitiatedExpansion = false;
- });
- }
- }
-
- propagateComposedEvents: true
- onPressed: mouse => {
- if (parent.hoveredLink)
- mouse.accepted = false;
- }
- onReleased: mouse => {
- if (parent.hoveredLink)
- mouse.accepted = false;
- }
- }
- }
- }
- }
- }
-
- Column {
- id: expandedContent
- objectName: "expandedContent"
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.topMargin: cardPadding
- anchors.leftMargin: Theme.spacingL
- anchors.rightMargin: Theme.spacingL
- spacing: compactMode ? Theme.spacingXS : Theme.spacingS
- visible: expanded
-
- Item {
- width: parent.width
- height: compactMode ? 32 : 40
-
- Row {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.rightMargin: Theme.spacingL + Theme.notificationHoverRevealMargin
- anchors.verticalCenter: parent.verticalCenter
- spacing: Theme.spacingS
-
- StyledText {
- text: notificationGroup?.appName || ""
- color: Theme.surfaceText
- font.pixelSize: Theme.fontSizeLarge
- font.weight: Font.Bold
- anchors.verticalCenter: parent.verticalCenter
- elide: Text.ElideRight
- maximumLineCount: 1
- }
-
- Rectangle {
- width: badgeSize
- height: badgeSize
- radius: badgeSize / 2
- color: Theme.primary
- visible: (notificationGroup?.count || 0) > 1
- anchors.verticalCenter: parent.verticalCenter
-
- StyledText {
- anchors.centerIn: parent
- text: (notificationGroup?.count || 0) > 99 ? "99+" : (notificationGroup?.count || 0).toString()
- color: Theme.primaryText
- font.pixelSize: compactMode ? 8 : 9
- font.weight: Font.Bold
- }
- }
- }
- }
-
- Column {
- width: parent.width
- spacing: compactMode ? Theme.spacingS : Theme.spacingL
-
- Repeater {
- id: notificationRepeater
- objectName: "notificationRepeater"
- model: notificationGroup?.notifications?.slice(0, 10) || []
-
- delegate: Item {
- id: expandedDelegateWrapper
- required property var modelData
- required property int index
- readonly property bool messageExpanded: NotificationService.expandedMessages[modelData?.notification?.id] || false
- readonly property bool isSelected: root.selectedNotificationIndex === index
- readonly property bool actionsVisible: true
- readonly property real expandedIconSize: compactMode ? Theme.notificationExpandedIconSizeCompact : Theme.notificationExpandedIconSizeNormal
-
- HoverHandler {
- id: expandedDelegateHoverHandler
- }
- readonly property real expandedItemPadding: compactMode ? Theme.spacingS : Theme.spacingM
- readonly property real expandedBaseHeight: expandedItemPadding * 2 + Math.max(expandedIconSize, Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * 2) + actionButtonHeight + contentSpacing * 2
- property bool __delegateInitialized: false
- property real swipeOffset: 0
- property bool isDismissing: false
- readonly property real dismissThreshold: width * 0.35
-
- Component.onCompleted: {
- Qt.callLater(() => {
- if (expandedDelegateWrapper)
- expandedDelegateWrapper.__delegateInitialized = true;
- });
- }
-
- width: parent.width
- height: delegateRect.height
- clip: true
-
- Rectangle {
- id: delegateRect
- width: parent.width
-
- readonly property bool isAdjacentToSwipe: root.swipingNotificationIndex !== -1 && (expandedDelegateWrapper.index === root.swipingNotificationIndex - 1 || expandedDelegateWrapper.index === root.swipingNotificationIndex + 1)
- readonly property real adjacentSwipeInfluence: isAdjacentToSwipe ? root.swipingNotificationOffset * 0.10 : 0
- readonly property real adjacentScaleInfluence: isAdjacentToSwipe ? 1.0 - Math.abs(root.swipingNotificationOffset) / width * 0.02 : 1.0
-
- x: expandedDelegateWrapper.swipeOffset + adjacentSwipeInfluence
- scale: adjacentScaleInfluence
- transformOrigin: Item.Center
-
- Behavior on x {
- enabled: !expandedSwipeHandler.active && !expandedDelegateWrapper.isDismissing
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- Behavior on scale {
- enabled: !expandedSwipeHandler.active
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- height: {
- if (!messageExpanded)
- return expandedBaseHeight;
- const twoLineHeight = bodyText.font.pixelSize * 1.2 * 2;
- if (bodyText.implicitHeight > twoLineHeight + 2)
- return expandedBaseHeight + bodyText.implicitHeight - twoLineHeight;
- return expandedBaseHeight;
- }
- radius: Theme.cornerRadius
- color: isSelected ? Theme.primaryPressed : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
- border.color: isSelected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
- border.width: 1
-
- Behavior on border.color {
- enabled: __delegateInitialized
- ColorAnimation {
- duration: __delegateInitialized ? Theme.shortDuration : 0
- easing.type: Theme.standardEasing
- }
- }
-
- Behavior on height {
- enabled: false
- }
-
- Item {
- anchors.fill: parent
- anchors.margins: compactMode ? Theme.spacingS : Theme.spacingM
- anchors.bottomMargin: contentSpacing
-
- DankCircularImage {
- id: messageIcon
-
- readonly property string rawImage: modelData?.image || ""
- readonly property string iconFromImage: {
- if (rawImage.startsWith("image://icon/"))
- return rawImage.substring(13);
- return "";
- }
- readonly property bool imageHasSpecialPrefix: {
- const icon = iconFromImage;
- return icon.startsWith("material:") || icon.startsWith("svg:") || icon.startsWith("unicode:") || icon.startsWith("image:");
- }
- readonly property bool hasNotificationImage: rawImage !== "" && !rawImage.startsWith("image://icon/")
-
- width: expandedIconSize
- height: expandedIconSize
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.topMargin: Theme.fontSizeSmall * 1.2 + (compactMode ? Theme.spacingXS : Theme.spacingS)
-
- imageSource: {
- if (hasNotificationImage)
- return modelData.cleanImage;
- if (imageHasSpecialPrefix)
- return "";
- const appIcon = modelData?.appIcon;
- if (!appIcon)
- return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
- if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
- return appIcon;
- if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
- return "";
- return Paths.resolveIconPath(appIcon);
- }
-
- fallbackIcon: {
- if (imageHasSpecialPrefix)
- return iconFromImage;
- return modelData?.appIcon || iconFromImage || "";
- }
-
- fallbackText: {
- const appName = modelData?.appName || "?";
- return appName.charAt(0).toUpperCase();
- }
- }
-
- Item {
- anchors.left: messageIcon.right
- anchors.leftMargin: Theme.spacingM
- anchors.right: parent.right
- anchors.rightMargin: Theme.spacingM
- anchors.top: parent.top
- anchors.bottom: parent.bottom
-
- Column {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: parent.top
- anchors.bottom: buttonArea.top
- anchors.bottomMargin: contentSpacing
- spacing: Theme.notificationContentSpacing
-
- Row {
- id: expandedDelegateHeaderRow
- width: parent.width
- spacing: Theme.spacingXS
- visible: (expandedDelegateHeaderAppNameText.text.length > 0 || expandedDelegateHeaderTimeText.text.length > 0)
-
- StyledText {
- id: expandedDelegateHeaderAppNameText
- text: modelData?.appName || ""
- color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Normal
- elide: Text.ElideRight
- maximumLineCount: 1
- width: Math.min(implicitWidth, parent.width - expandedDelegateHeaderSeparator.implicitWidth - expandedDelegateHeaderTimeText.implicitWidth - parent.spacing * 2)
- }
-
- StyledText {
- id: expandedDelegateHeaderSeparator
- text: (expandedDelegateHeaderAppNameText.text.length > 0 && expandedDelegateHeaderTimeText.text.length > 0) ? " • " : ""
- color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Normal
- }
-
- StyledText {
- id: expandedDelegateHeaderTimeText
- text: modelData?.timeStr || ""
- color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Normal
- }
- }
-
- StyledText {
- id: expandedDelegateTitleText
- width: parent.width
- text: modelData?.summary || ""
- color: Theme.surfaceText
- font.pixelSize: Theme.fontSizeMedium
- font.weight: Font.Medium
- elide: Text.ElideRight
- maximumLineCount: 1
- visible: text.length > 0
- }
-
- StyledText {
- id: bodyText
- property bool hasMoreText: truncated
-
- text: modelData?.htmlBody || ""
- color: Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- width: parent.width
- elide: messageExpanded ? Text.ElideNone : Text.ElideRight
- maximumLineCount: messageExpanded ? -1 : 2
- wrapMode: Text.WrapAtWordBoundaryOrAnywhere
- visible: text.length > 0
- linkColor: Theme.primary
- onLinkActivated: link => Qt.openUrlExternally(link)
- MouseArea {
- anchors.fill: parent
- cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : (bodyText.hasMoreText || messageExpanded) ? Qt.PointingHandCursor : Qt.ArrowCursor
-
- onClicked: mouse => {
- if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) {
- root.userInitiatedExpansion = true;
- NotificationService.toggleMessageExpansion(modelData?.notification?.id || "");
- Qt.callLater(() => {
- if (root && !root.isAnimating)
- root.userInitiatedExpansion = false;
- });
- }
- }
-
- propagateComposedEvents: true
- onPressed: mouse => {
- if (parent.hoveredLink) {
- mouse.accepted = false;
- }
- }
- onReleased: mouse => {
- if (parent.hoveredLink) {
- mouse.accepted = false;
- }
- }
- }
- }
- }
-
- Item {
- id: buttonArea
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- height: actionButtonHeight + contentSpacing
-
- Row {
- visible: expandedDelegateWrapper.actionsVisible
- opacity: visible ? 1 : 0
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- spacing: contentSpacing
-
- Behavior on opacity {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- Repeater {
- model: modelData?.actions || []
-
- Rectangle {
- property bool isHovered: false
-
- width: Math.max(expandedActionText.implicitWidth + Theme.spacingM, Theme.notificationActionMinWidth)
- height: actionButtonHeight
- radius: Theme.notificationButtonCornerRadius
- color: isHovered ? Theme.withAlpha(Theme.primary, Theme.stateLayerHover) : "transparent"
-
- StyledText {
- id: expandedActionText
- text: {
- const baseText = modelData.text || I18n.tr("Open");
- if (keyboardNavigationActive && (isGroupSelected || selectedNotificationIndex >= 0))
- return `${baseText} (${index + 1})`;
- return baseText;
- }
- color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Medium
- anchors.centerIn: parent
- elide: Text.ElideRight
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: parent.isHovered = true
- onExited: parent.isHovered = false
- onClicked: {
- if (modelData && modelData.invoke)
- modelData.invoke();
- }
- }
- }
- }
-
- Rectangle {
- id: expandedDelegateDismissBtn
- property bool isHovered: false
-
- visible: expandedDelegateWrapper.actionsVisible
- opacity: visible ? 1 : 0
- width: Math.max(expandedClearText.implicitWidth + Theme.spacingM, Theme.notificationActionMinWidth)
- height: actionButtonHeight
- radius: Theme.notificationButtonCornerRadius
- color: isHovered ? Theme.withAlpha(Theme.primary, Theme.stateLayerHover) : "transparent"
-
- Behavior on opacity {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- StyledText {
- id: expandedClearText
- text: I18n.tr("Dismiss")
- color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Medium
- anchors.centerIn: parent
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: parent.isHovered = true
- onExited: parent.isHovered = false
- onClicked: NotificationService.dismissNotification(modelData)
- }
- }
- }
- }
- }
- }
- }
-
- DragHandler {
- id: expandedSwipeHandler
- target: null
- xAxis.enabled: true
- yAxis.enabled: false
- grabPermissions: PointerHandler.CanTakeOverFromItems | PointerHandler.CanTakeOverFromHandlersOfDifferentType
-
- onActiveChanged: {
- if (active) {
- root.swipingNotificationIndex = expandedDelegateWrapper.index;
- } else {
- root.swipingNotificationIndex = -1;
- root.swipingNotificationOffset = 0;
- }
- if (active || expandedDelegateWrapper.isDismissing)
- return;
- if (Math.abs(expandedDelegateWrapper.swipeOffset) > expandedDelegateWrapper.dismissThreshold) {
- expandedDelegateWrapper.isDismissing = true;
- expandedSwipeDismissAnim.start();
- } else {
- expandedDelegateWrapper.swipeOffset = 0;
- }
- }
-
- onTranslationChanged: {
- if (expandedDelegateWrapper.isDismissing)
- return;
- expandedDelegateWrapper.swipeOffset = translation.x;
- root.swipingNotificationOffset = translation.x;
- }
- }
-
- NumberAnimation {
- id: expandedSwipeDismissAnim
- target: expandedDelegateWrapper
- property: "swipeOffset"
- to: expandedDelegateWrapper.swipeOffset > 0 ? expandedDelegateWrapper.width : -expandedDelegateWrapper.width
- duration: Theme.notificationExitDuration
- easing.type: Easing.OutCubic
- onStopped: NotificationService.dismissNotification(modelData)
- }
- }
- }
- }
- }
-
- Row {
- visible: !expanded
- anchors.right: clearButton.visible ? clearButton.left : parent.right
- anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL
- anchors.top: collapsedContent.bottom
- anchors.topMargin: contentSpacing + collapsedDismissOffset
- spacing: contentSpacing
-
- Repeater {
- model: notificationGroup?.latestNotification?.actions || []
-
- Rectangle {
- property bool isHovered: false
-
- width: Math.max(collapsedActionText.implicitWidth + Theme.spacingM, Theme.notificationActionMinWidth)
- height: actionButtonHeight
- radius: Theme.notificationButtonCornerRadius
- color: isHovered ? Theme.withAlpha(Theme.primary, Theme.stateLayerHover) : "transparent"
-
- StyledText {
- id: collapsedActionText
- text: {
- const baseText = modelData.text || I18n.tr("Open");
- if (keyboardNavigationActive && isGroupSelected) {
- return `${baseText} (${index + 1})`;
- }
- return baseText;
- }
- color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Medium
- anchors.centerIn: parent
- elide: Text.ElideRight
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: parent.isHovered = true
- onExited: parent.isHovered = false
- onClicked: {
- if (modelData && modelData.invoke) {
- modelData.invoke();
- }
- }
- }
- }
- }
- }
-
- Rectangle {
- id: clearButton
-
- property bool isHovered: false
- readonly property int actionCount: (notificationGroup?.latestNotification?.actions || []).length
-
- visible: !expanded && actionCount < 3
- anchors.right: parent.right
- anchors.rightMargin: Theme.spacingL
- anchors.top: collapsedContent.bottom
- anchors.topMargin: contentSpacing + collapsedDismissOffset
- width: Math.max(collapsedClearText.implicitWidth + Theme.spacingM, Theme.notificationActionMinWidth)
- height: actionButtonHeight
- radius: Theme.notificationButtonCornerRadius
- color: isHovered ? Theme.withAlpha(Theme.primary, Theme.stateLayerHover) : "transparent"
-
- StyledText {
- id: collapsedClearText
- text: I18n.tr("Dismiss")
- color: clearButton.isHovered ? Theme.primary : Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Medium
- anchors.centerIn: parent
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: clearButton.isHovered = true
- onExited: clearButton.isHovered = false
- onClicked: NotificationService.dismissGroup(notificationGroup?.key || "")
- }
- }
-
- MouseArea {
- anchors.fill: parent
- visible: !expanded && (notificationGroup?.count || 0) > 1 && !descriptionExpanded
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- root.userInitiatedExpansion = true;
- NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
- }
- z: -1
- }
-
- Item {
- id: fixedControls
- anchors.top: parent.top
- anchors.right: parent.right
- anchors.topMargin: cardPadding
- anchors.rightMargin: Theme.spacingL
- width: compactMode ? 52 : 60
- height: compactMode ? 24 : 28
-
- DankActionButton {
- anchors.left: parent.left
- anchors.top: parent.top
- visible: (notificationGroup?.count || 0) > 1
- iconName: expanded ? "expand_less" : "expand_more"
- iconSize: compactMode ? 16 : 18
- buttonSize: compactMode ? 24 : 28
- onClicked: {
- root.userInitiatedExpansion = true;
- NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
- }
- }
-
- DankActionButton {
- anchors.right: parent.right
- anchors.top: parent.top
- iconName: "close"
- iconSize: compactMode ? 16 : 18
- buttonSize: compactMode ? 24 : 28
- onClicked: NotificationService.dismissGroup(notificationGroup?.key || "")
- }
- }
-
- Behavior on height {
- enabled: root.__initialized && root.userInitiatedExpansion && root.animateExpansion
- NumberAnimation {
- duration: root.expanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration
- easing.type: Easing.BezierSpline
- easing.bezierCurve: Theme.expressiveCurves.emphasized
- onRunningChanged: {
- if (running) {
- root.isAnimating = true;
- } else {
- root.isAnimating = false;
- root.userInitiatedExpansion = false;
- }
- }
- }
- }
-
- Menu {
- id: notificationCardContextMenu
- width: 220
- closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
-
- background: Rectangle {
- color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
- radius: Theme.cornerRadius
- border.width: 0
- border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
- }
-
- MenuItem {
- id: setNotificationRulesItem
- text: I18n.tr("Set notification rules")
-
- contentItem: StyledText {
- text: parent.text
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- leftPadding: Theme.spacingS
- verticalAlignment: Text.AlignVCenter
- }
-
- background: Rectangle {
- color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
- radius: Theme.cornerRadius / 2
- }
-
- onTriggered: {
- const appName = notificationGroup?.appName || "";
- const desktopEntry = notificationGroup?.latestNotification?.desktopEntry || "";
- SettingsData.addNotificationRuleForNotification(appName, desktopEntry);
- PopoutService.openSettingsWithTab("notifications");
- }
- }
-
- MenuItem {
- id: muteUnmuteItem
- readonly property bool isMuted: SettingsData.isAppMuted(notificationGroup?.appName || "", notificationGroup?.latestNotification?.desktopEntry || "")
- text: isMuted ? I18n.tr("Unmute popups for %1").arg(notificationGroup?.appName || I18n.tr("this app")) : I18n.tr("Mute popups for %1").arg(notificationGroup?.appName || I18n.tr("this app"))
-
- contentItem: StyledText {
- text: parent.text
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- leftPadding: Theme.spacingS
- verticalAlignment: Text.AlignVCenter
- }
-
- background: Rectangle {
- color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
- radius: Theme.cornerRadius / 2
- }
-
- onTriggered: {
- const appName = notificationGroup?.appName || "";
- const desktopEntry = notificationGroup?.latestNotification?.desktopEntry || "";
- if (isMuted) {
- SettingsData.removeMuteRuleForApp(appName, desktopEntry);
- } else {
- SettingsData.addMuteRuleForApp(appName, desktopEntry);
- NotificationService.dismissGroup(notificationGroup?.key || "");
- }
- }
- }
-
- MenuItem {
- text: I18n.tr("Dismiss")
-
- contentItem: StyledText {
- text: parent.text
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- leftPadding: Theme.spacingS
- verticalAlignment: Text.AlignVCenter
- }
-
- background: Rectangle {
- color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
- radius: Theme.cornerRadius / 2
- }
-
- onTriggered: NotificationService.dismissGroup(notificationGroup?.key || "")
- }
- }
-
- MouseArea {
- anchors.fill: parent
- acceptedButtons: Qt.RightButton
- z: -2
- onClicked: mouse => {
- if (mouse.button === Qt.RightButton && notificationGroup) {
- notificationCardContextMenu.popup();
- }
- }
- }
-}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationCenterPopout.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationCenterPopout.qml
deleted file mode 100644
index b7b616b..0000000
--- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationCenterPopout.qml
+++ /dev/null
@@ -1,321 +0,0 @@
-import QtQuick
-import qs.Common
-import qs.Services
-import qs.Widgets
-
-DankPopout {
- id: root
-
- layerNamespace: "dms:notification-center-popout"
- fullHeightSurface: true
-
- property bool notificationHistoryVisible: false
- property var triggerScreen: null
- property real stablePopupHeight: 400
- property real _lastAlignedContentHeight: -1
- property bool _pendingSizedOpen: false
-
- function updateStablePopupHeight() {
- const item = contentLoader.item;
- if (item && !root.shouldBeVisible) {
- const notificationList = findChild(item, "notificationList");
- if (notificationList && typeof notificationList.forceLayout === "function") {
- notificationList.forceLayout();
- }
- }
- const target = item ? Theme.px(item.implicitHeight, dpr) : 400;
- if (Math.abs(target - _lastAlignedContentHeight) < 0.5)
- return;
- _lastAlignedContentHeight = target;
- stablePopupHeight = target;
- }
-
- NotificationKeyboardController {
- id: keyboardController
- listView: null
- isOpen: root.shouldBeVisible
- onClose: () => {
- notificationHistoryVisible = false;
- }
- }
-
- popupWidth: triggerScreen ? Math.min(500, Math.max(380, triggerScreen.width - 48)) : 400
- popupHeight: stablePopupHeight
- positioning: ""
- animationScaleCollapsed: 0.94
- animationOffset: 0
- suspendShadowWhileResizing: false
-
- screen: triggerScreen
-
- function toggle() {
- notificationHistoryVisible = !notificationHistoryVisible;
- }
-
- function openSized() {
- if (!notificationHistoryVisible)
- return;
-
- primeContent();
- if (contentLoader.item) {
- updateStablePopupHeight();
- _pendingSizedOpen = false;
- Qt.callLater(() => {
- if (!notificationHistoryVisible)
- return;
- updateStablePopupHeight();
- open();
- clearPrimedContent();
- });
- return;
- }
-
- _pendingSizedOpen = true;
- }
-
- onBackgroundClicked: {
- notificationHistoryVisible = false;
- }
-
- onNotificationHistoryVisibleChanged: {
- if (notificationHistoryVisible) {
- openSized();
- } else {
- _pendingSizedOpen = false;
- clearPrimedContent();
- close();
- }
- }
-
- function setupKeyboardNavigation() {
- if (!contentLoader.item)
- return;
- contentLoader.item.externalKeyboardController = keyboardController;
-
- const notificationList = findChild(contentLoader.item, "notificationList");
- const notificationHeader = findChild(contentLoader.item, "notificationHeader");
-
- if (notificationList) {
- keyboardController.listView = notificationList;
- notificationList.keyboardController = keyboardController;
- }
- if (notificationHeader) {
- notificationHeader.keyboardController = keyboardController;
- }
-
- keyboardController.reset();
- keyboardController.rebuildFlatNavigation();
- }
-
- Connections {
- target: contentLoader
- function onLoaded() {
- root.updateStablePopupHeight();
- if (root._pendingSizedOpen && root.notificationHistoryVisible) {
- Qt.callLater(() => {
- if (!root._pendingSizedOpen || !root.notificationHistoryVisible)
- return;
- root.updateStablePopupHeight();
- root._pendingSizedOpen = false;
- root.open();
- root.clearPrimedContent();
- });
- return;
- }
- if (root.shouldBeVisible)
- Qt.callLater(root.setupKeyboardNavigation);
- }
- }
-
- Connections {
- target: contentLoader.item
- function onImplicitHeightChanged() {
- root.updateStablePopupHeight();
- }
- }
-
- onDprChanged: updateStablePopupHeight()
-
- onShouldBeVisibleChanged: {
- if (shouldBeVisible) {
- NotificationService.onOverlayOpen();
- updateStablePopupHeight();
- if (contentLoader.item)
- Qt.callLater(setupKeyboardNavigation);
- } else {
- NotificationService.onOverlayClose();
- keyboardController.keyboardNavigationActive = false;
- NotificationService.expandedGroups = {};
- NotificationService.expandedMessages = {};
- }
- }
-
- function findChild(parent, objectName) {
- if (parent.objectName === objectName) {
- return parent;
- }
- for (let i = 0; i < parent.children.length; i++) {
- const child = parent.children[i];
- const result = findChild(child, objectName);
- if (result) {
- return result;
- }
- }
- return null;
- }
-
- content: Component {
- Rectangle {
- id: notificationContent
-
- LayoutMirroring.enabled: I18n.isRtl
- LayoutMirroring.childrenInherit: true
-
- property var externalKeyboardController: null
- property real cachedHeaderHeight: 32
- readonly property real settingsMaxHeight: {
- const screenH = root.screen ? root.screen.height : 1080;
- const maxPopupH = screenH * 0.8;
- const overhead = cachedHeaderHeight + Theme.spacingL * 2 + Theme.spacingM * 2;
- return Math.max(200, maxPopupH - overhead - 150);
- }
- implicitHeight: {
- let baseHeight = Theme.spacingL * 2;
- baseHeight += cachedHeaderHeight;
- baseHeight += Theme.spacingM * 2;
-
- const settingsHeight = notificationSettings.expanded ? Math.min(notificationSettings.naturalContentHeight, settingsMaxHeight) : 0;
- const currentListHeight = root.shouldBeVisible ? notificationList.stableContentHeight : notificationList.listContentHeight;
- let listHeight = notificationHeader.currentTab === 0 ? currentListHeight : Math.max(200, NotificationService.historyList.length * 80);
- if (notificationHeader.currentTab === 0 && NotificationService.groupedNotifications.length === 0) {
- listHeight = 200;
- }
- if (notificationHeader.currentTab === 1 && NotificationService.historyList.length === 0) {
- listHeight = 200;
- }
-
- const maxContentArea = 600;
- const availableListSpace = Math.max(200, maxContentArea - settingsHeight);
-
- baseHeight += settingsHeight;
- baseHeight += Math.min(listHeight, availableListSpace);
-
- const maxHeight = root.screen ? root.screen.height * 0.8 : Screen.height * 0.8;
- return Math.max(300, Math.min(baseHeight, maxHeight));
- }
-
- color: "transparent"
- focus: true
-
- Component.onCompleted: {
- if (root.shouldBeVisible) {
- forceActiveFocus();
- }
- }
-
- Keys.onPressed: event => {
- if (event.key === Qt.Key_Escape) {
- notificationHistoryVisible = false;
- event.accepted = true;
- return;
- }
-
- if (event.key === Qt.Key_Left) {
- if (notificationHeader.currentTab > 0) {
- notificationHeader.currentTab = 0;
- event.accepted = true;
- }
- return;
- }
-
- if (event.key === Qt.Key_Right) {
- if (notificationHeader.currentTab === 0 && SettingsData.notificationHistoryEnabled) {
- notificationHeader.currentTab = 1;
- event.accepted = true;
- }
- return;
- }
- if (notificationHeader.currentTab === 1) {
- historyList.handleKey(event);
- return;
- }
- if (externalKeyboardController) {
- externalKeyboardController.handleKey(event);
- }
- }
-
- Connections {
- function onShouldBeVisibleChanged() {
- if (root.shouldBeVisible) {
- Qt.callLater(() => {
- notificationContent.forceActiveFocus();
- });
- } else {
- notificationContent.focus = false;
- }
- }
- target: root
- }
-
- FocusScope {
- id: contentColumn
-
- anchors.fill: parent
- anchors.margins: Theme.spacingL
- focus: true
-
- Column {
- id: contentColumnInner
- anchors.fill: parent
- spacing: Theme.spacingM
-
- NotificationHeader {
- id: notificationHeader
- objectName: "notificationHeader"
- onHeightChanged: notificationContent.cachedHeaderHeight = height
- }
-
- NotificationSettings {
- id: notificationSettings
- expanded: notificationHeader.showSettings
- maxAllowedHeight: notificationContent.settingsMaxHeight
- }
-
- Item {
- visible: notificationHeader.currentTab === 0
- width: parent.width
- height: parent.height - notificationContent.cachedHeaderHeight - notificationSettings.height - contentColumnInner.spacing * 2
-
- KeyboardNavigatedNotificationList {
- id: notificationList
- objectName: "notificationList"
- anchors.fill: parent
- anchors.leftMargin: -shadowHorizontalGutter
- anchors.rightMargin: -shadowHorizontalGutter
- anchors.topMargin: -(shadowVerticalGutter + delegateShadowGutter / 2)
- anchors.bottomMargin: -(shadowVerticalGutter + delegateShadowGutter / 2)
- cardAnimateExpansion: true
- }
- }
-
- HistoryNotificationList {
- id: historyList
- visible: notificationHeader.currentTab === 1
- width: parent.width
- height: parent.height - notificationContent.cachedHeaderHeight - notificationSettings.height - contentColumnInner.spacing * 2
- }
- }
- }
-
- NotificationKeyboardHints {
- id: keyboardHints
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.margins: Theme.spacingL
- showHints: notificationHeader.currentTab === 0 ? (externalKeyboardController && externalKeyboardController.showKeyboardHints) || false : historyList.showKeyboardHints
- z: 200
- }
- }
- }
-}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationEmptyState.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationEmptyState.qml
deleted file mode 100644
index da8746e..0000000
--- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationEmptyState.qml
+++ /dev/null
@@ -1,34 +0,0 @@
-import QtQuick
-import qs.Common
-import qs.Services
-import qs.Widgets
-
-Item {
- id: root
-
- width: parent.width
- height: 200
- visible: NotificationService.notifications.length === 0
-
- Column {
- anchors.centerIn: parent
- spacing: Theme.spacingXS
- width: parent.width * 0.8
-
- DankIcon {
- anchors.horizontalCenter: parent.horizontalCenter
- name: "notifications_none"
- size: Theme.iconSizeLarge + 16
- color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
- }
-
- StyledText {
- anchors.horizontalCenter: parent.horizontalCenter
- text: I18n.tr("Nothing to see here")
- font.pixelSize: Theme.fontSizeLarge
- color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
- font.weight: Font.Medium
- horizontalAlignment: Text.AlignHCenter
- }
- }
-}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationHeader.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationHeader.qml
deleted file mode 100644
index 201e716..0000000
--- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationHeader.qml
+++ /dev/null
@@ -1,158 +0,0 @@
-import QtQuick
-import qs.Common
-import qs.Services
-import qs.Widgets
-
-Item {
- id: root
-
- property var keyboardController: null
- property bool showSettings: false
- property int currentTab: 0
-
- onCurrentTabChanged: {
- if (currentTab === 1 && !SettingsData.notificationHistoryEnabled)
- currentTab = 0;
- }
-
- Connections {
- target: SettingsData
- function onNotificationHistoryEnabledChanged() {
- if (!SettingsData.notificationHistoryEnabled)
- root.currentTab = 0;
- }
- }
-
- width: parent.width
- height: headerColumn.implicitHeight
-
- DankTooltipV2 {
- id: sharedTooltip
- }
-
- Column {
- id: headerColumn
- width: parent.width
- spacing: Theme.spacingS
-
- Item {
- width: parent.width
- height: Math.max(titleRow.implicitHeight, actionsRow.implicitHeight)
-
- Row {
- id: titleRow
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- spacing: Theme.spacingXS
-
- StyledText {
- text: I18n.tr("Notifications")
- font.pixelSize: Theme.fontSizeLarge
- color: Theme.surfaceText
- font.weight: Font.Medium
- anchors.verticalCenter: parent.verticalCenter
- }
-
- DankActionButton {
- id: doNotDisturbButton
- iconName: SessionData.doNotDisturb ? "notifications_off" : "notifications"
- iconColor: SessionData.doNotDisturb ? Theme.error : Theme.surfaceText
- buttonSize: Theme.iconSize + Theme.spacingS
- anchors.verticalCenter: parent.verticalCenter
- onClicked: SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
- onEntered: sharedTooltip.show(I18n.tr("Do Not Disturb"), doNotDisturbButton, 0, 0, "bottom")
- onExited: sharedTooltip.hide()
- }
- }
-
- Row {
- id: actionsRow
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- spacing: Theme.spacingXS
-
- DankActionButton {
- id: helpButton
- iconName: "info"
- iconColor: (keyboardController && keyboardController.showKeyboardHints) ? Theme.primary : Theme.surfaceText
- buttonSize: Theme.iconSize + Theme.spacingS
- visible: keyboardController !== null
- anchors.verticalCenter: parent.verticalCenter
- onClicked: {
- if (keyboardController)
- keyboardController.showKeyboardHints = !keyboardController.showKeyboardHints;
- }
- }
-
- DankActionButton {
- id: settingsButton
- iconName: "settings"
- iconColor: root.showSettings ? Theme.primary : Theme.surfaceText
- buttonSize: Theme.iconSize + Theme.spacingS
- anchors.verticalCenter: parent.verticalCenter
- onClicked: root.showSettings = !root.showSettings
- }
-
- Rectangle {
- id: clearAllButton
- width: clearButtonContent.implicitWidth + Theme.spacingM * 2
- height: Theme.iconSize + Theme.spacingS
- radius: Theme.cornerRadius
- visible: root.currentTab === 0 ? NotificationService.notifications.length > 0 : NotificationService.historyList.length > 0
- color: clearArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
-
- Row {
- id: clearButtonContent
- anchors.centerIn: parent
- spacing: Theme.spacingXS
-
- DankIcon {
- name: "delete_sweep"
- size: Theme.iconSizeSmall
- color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
-
- StyledText {
- text: I18n.tr("Clear")
- font.pixelSize: Theme.fontSizeSmall
- color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
- font.weight: Font.Medium
- anchors.verticalCenter: parent.verticalCenter
- }
- }
-
- MouseArea {
- id: clearArea
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- if (root.currentTab === 0) {
- NotificationService.clearAllNotifications();
- } else {
- NotificationService.clearHistory();
- }
- }
- }
- }
- }
- }
-
- DankButtonGroup {
- id: tabGroup
- width: parent.width
- currentIndex: root.currentTab
- buttonHeight: 32
- buttonPadding: Theme.spacingM
- checkEnabled: false
- textSize: Theme.fontSizeSmall
- visible: SettingsData.notificationHistoryEnabled
- model: [I18n.tr("Current", "notification center tab") + " (" + NotificationService.notifications.length + ")", I18n.tr("History", "notification center tab") + " (" + NotificationService.historyList.length + ")"]
- onSelectionChanged: (index, selected) => {
- if (selected)
- root.currentTab = index;
- }
- }
- }
-}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationKeyboardController.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationKeyboardController.qml
deleted file mode 100644
index eda12c0..0000000
--- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationKeyboardController.qml
+++ /dev/null
@@ -1,556 +0,0 @@
-import QtQuick
-import qs.Services
-
-QtObject {
- id: controller
-
- property var listView: null
- property bool isOpen: false
- property var onClose: null
-
- property int selectionVersion: 0
-
- property bool keyboardNavigationActive: false
- property int selectedFlatIndex: 0
- property var flatNavigation: []
- property bool showKeyboardHints: false
-
- property string selectedNotificationId: ""
- property string selectedGroupKey: ""
- property string selectedItemType: ""
- property bool isTogglingGroup: false
- property bool isRebuilding: false
-
- function rebuildFlatNavigation() {
- isRebuilding = true;
-
- const nav = [];
- const groups = NotificationService.groupedNotifications;
-
- for (var i = 0; i < groups.length; i++) {
- const group = groups[i];
- const isExpanded = NotificationService.expandedGroups[group.key] || false;
-
- nav.push({
- "type": "group",
- "groupIndex": i,
- "notificationIndex": -1,
- "groupKey": group.key,
- "notificationId": ""
- });
-
- if (isExpanded) {
- const notifications = group.notifications || [];
- const maxNotifications = Math.min(notifications.length, 10);
- for (var j = 0; j < maxNotifications; j++) {
- const notifId = String(notifications[j] && notifications[j].notification && notifications[j].notification.id ? notifications[j].notification.id : "");
- nav.push({
- "type": "notification",
- "groupIndex": i,
- "notificationIndex": j,
- "groupKey": group.key,
- "notificationId": notifId
- });
- }
- }
- }
-
- flatNavigation = nav;
- updateSelectedIndexFromId();
- isRebuilding = false;
- }
-
- function updateSelectedIndexFromId() {
- if (!keyboardNavigationActive) {
- return;
- }
-
- for (var i = 0; i < flatNavigation.length; i++) {
- const item = flatNavigation[i];
-
- if (selectedItemType === "group" && item.type === "group" && item.groupKey === selectedGroupKey) {
- selectedFlatIndex = i;
- selectionVersion++; // Trigger UI update
- return;
- } else if (selectedItemType === "notification" && item.type === "notification" && String(item.notificationId) === String(selectedNotificationId)) {
- selectedFlatIndex = i;
- selectionVersion++; // Trigger UI update
- return;
- }
- }
-
- // If not found, try to find the same group but select the group header instead
- if (selectedItemType === "notification") {
- for (var j = 0; j < flatNavigation.length; j++) {
- const groupItem = flatNavigation[j];
- if (groupItem.type === "group" && groupItem.groupKey === selectedGroupKey) {
- selectedFlatIndex = j;
- selectedItemType = "group";
- selectedNotificationId = "";
- selectionVersion++; // Trigger UI update
- return;
- }
- }
- }
-
- // If still not found, clamp to valid range and update
- if (flatNavigation.length > 0) {
- selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1);
- selectedFlatIndex = Math.max(selectedFlatIndex, 0);
- updateSelectedIdFromIndex();
- selectionVersion++; // Trigger UI update
- }
- }
-
- function updateSelectedIdFromIndex() {
- if (selectedFlatIndex >= 0 && selectedFlatIndex < flatNavigation.length) {
- const item = flatNavigation[selectedFlatIndex];
- selectedItemType = item.type;
- selectedGroupKey = item.groupKey;
- selectedNotificationId = item.notificationId;
- }
- }
-
- function reset() {
- selectedFlatIndex = 0;
- keyboardNavigationActive = false;
- showKeyboardHints = false;
- // Reset keyboardActive when modal is reset
- if (listView) {
- listView.keyboardActive = false;
- }
- rebuildFlatNavigation();
- }
-
- function selectNext() {
- keyboardNavigationActive = true;
- if (flatNavigation.length === 0)
- return;
-
- // Re-enable auto-scrolling when arrow keys are used
- if (listView && listView.enableAutoScroll) {
- listView.enableAutoScroll();
- }
-
- selectedFlatIndex = Math.min(selectedFlatIndex + 1, flatNavigation.length - 1);
- updateSelectedIdFromIndex();
- selectionVersion++;
- ensureVisible();
- }
-
- function selectNextWrapping() {
- keyboardNavigationActive = true;
- if (flatNavigation.length === 0)
- return;
-
- // Re-enable auto-scrolling when arrow keys are used
- if (listView && listView.enableAutoScroll) {
- listView.enableAutoScroll();
- }
-
- selectedFlatIndex = (selectedFlatIndex + 1) % flatNavigation.length;
- updateSelectedIdFromIndex();
- selectionVersion++;
- ensureVisible();
- }
-
- function selectPrevious() {
- keyboardNavigationActive = true;
- if (flatNavigation.length === 0)
- return;
-
- // Re-enable auto-scrolling when arrow keys are used
- if (listView && listView.enableAutoScroll) {
- listView.enableAutoScroll();
- }
-
- selectedFlatIndex = Math.max(selectedFlatIndex - 1, 0);
- updateSelectedIdFromIndex();
- selectionVersion++;
- ensureVisible();
- }
-
- function toggleGroupExpanded() {
- if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
- return;
- const currentItem = flatNavigation[selectedFlatIndex];
- const groups = NotificationService.groupedNotifications;
- const group = groups[currentItem.groupIndex];
- if (!group)
- return;
-
- // Prevent expanding groups with < 2 notifications
- const notificationCount = group.notifications ? group.notifications.length : 0;
- if (notificationCount < 2)
- return;
- const wasExpanded = NotificationService.expandedGroups[group.key] || false;
- const groupIndex = currentItem.groupIndex;
-
- isTogglingGroup = true;
- NotificationService.toggleGroupExpansion(group.key);
- rebuildFlatNavigation();
-
- // Smart selection after toggle
- if (!wasExpanded) {
- // Just expanded - move to first notification in the group
- for (var i = 0; i < flatNavigation.length; i++) {
- if (flatNavigation[i].type === "notification" && flatNavigation[i].groupIndex === groupIndex) {
- selectedFlatIndex = i;
- break;
- }
- }
- } else {
- // Just collapsed - stay on the group header
- for (var i = 0; i < flatNavigation.length; i++) {
- if (flatNavigation[i].type === "group" && flatNavigation[i].groupIndex === groupIndex) {
- selectedFlatIndex = i;
- break;
- }
- }
- }
-
- isTogglingGroup = false;
- }
-
- function handleEnterKey() {
- if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
- return;
- const currentItem = flatNavigation[selectedFlatIndex];
- const groups = NotificationService.groupedNotifications;
- const group = groups[currentItem.groupIndex];
- if (!group)
- return;
- if (currentItem.type === "group") {
- const notificationCount = group.notifications ? group.notifications.length : 0;
- if (notificationCount >= 2) {
- toggleGroupExpanded();
- } else {
- executeAction(0);
- }
- } else if (currentItem.type === "notification") {
- executeAction(0);
- }
- }
-
- function toggleTextExpanded() {
- if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
- return;
- const currentItem = flatNavigation[selectedFlatIndex];
- const groups = NotificationService.groupedNotifications;
- const group = groups[currentItem.groupIndex];
- if (!group)
- return;
- let messageId = "";
-
- if (currentItem.type === "group") {
- messageId = group.latestNotification?.notification?.id + "_desc";
- } else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) {
- messageId = group.notifications[currentItem.notificationIndex]?.notification?.id + "_desc";
- }
-
- if (messageId) {
- NotificationService.toggleMessageExpansion(messageId);
- }
- }
-
- function executeAction(actionIndex) {
- if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
- return;
- const currentItem = flatNavigation[selectedFlatIndex];
- const groups = NotificationService.groupedNotifications;
- const group = groups[currentItem.groupIndex];
- if (!group)
- return;
- let actions = [];
-
- if (currentItem.type === "group") {
- actions = group.latestNotification?.actions || [];
- } else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) {
- actions = group.notifications[currentItem.notificationIndex]?.actions || [];
- }
-
- if (actionIndex >= 0 && actionIndex < actions.length) {
- const action = actions[actionIndex];
- if (action.invoke) {
- action.invoke();
- if (onClose)
- onClose();
- }
- }
- }
-
- function clearSelected() {
- if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length)
- return;
- const currentItem = flatNavigation[selectedFlatIndex];
- const groups = NotificationService.groupedNotifications;
- const group = groups[currentItem.groupIndex];
- if (!group)
- return;
- if (currentItem.type === "group") {
- NotificationService.dismissGroup(group.key);
- } else if (currentItem.type === "notification") {
- const notification = group.notifications[currentItem.notificationIndex];
- NotificationService.dismissNotification(notification);
- }
-
- rebuildFlatNavigation();
-
- if (flatNavigation.length === 0) {
- keyboardNavigationActive = false;
- if (listView) {
- listView.keyboardActive = false;
- }
- } else {
- selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1);
- updateSelectedIdFromIndex();
- ensureVisible();
- }
- }
-
- function findRepeater(parent) {
- if (!parent || !parent.children) {
- return null;
- }
- for (var i = 0; i < parent.children.length; i++) {
- const child = parent.children[i];
- if (child.objectName === "notificationRepeater") {
- return child;
- }
- const found = findRepeater(child);
- if (found) {
- return found;
- }
- }
- return null;
- }
-
- function ensureVisible() {
- if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length || !listView)
- return;
- const currentItem = flatNavigation[selectedFlatIndex];
-
- if (keyboardNavigationActive && currentItem && currentItem.groupIndex >= 0) {
- if (currentItem.type === "notification") {
- const groupDelegate = listView.itemAtIndex(currentItem.groupIndex);
- if (groupDelegate && groupDelegate.children && groupDelegate.children.length > 0) {
- const notificationCard = groupDelegate.children[0];
- const repeater = findRepeater(notificationCard);
-
- if (repeater && currentItem.notificationIndex < repeater.count) {
- const notificationItem = repeater.itemAt(currentItem.notificationIndex);
- if (notificationItem) {
- const itemPos = notificationItem.mapToItem(listView.contentItem, 0, 0);
- const itemY = itemPos.y;
- const itemHeight = notificationItem.height;
-
- const viewportTop = listView.contentY;
- const viewportBottom = listView.contentY + listView.height;
-
- if (itemY < viewportTop) {
- listView.contentY = itemY - 20;
- } else if (itemY + itemHeight > viewportBottom) {
- listView.contentY = itemY + itemHeight - listView.height + 20;
- }
- }
- }
- }
- } else {
- listView.positionViewAtIndex(currentItem.groupIndex, ListView.Contain);
- }
-
- listView.forceLayout();
- }
- }
-
- function handleKey(event) {
- if ((event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) && (event.modifiers & Qt.ShiftModifier)) {
- NotificationService.clearAllNotifications();
- rebuildFlatNavigation();
- if (flatNavigation.length === 0) {
- keyboardNavigationActive = false;
- if (listView) {
- listView.keyboardActive = false;
- }
- } else {
- selectedFlatIndex = 0;
- updateSelectedIdFromIndex();
- }
- selectionVersion++;
- event.accepted = true;
- return;
- }
-
- if (event.key === Qt.Key_Escape) {
- if (keyboardNavigationActive) {
- keyboardNavigationActive = false;
- event.accepted = true;
- } else {
- if (onClose)
- onClose();
- event.accepted = true;
- }
- } else if (event.key === Qt.Key_Down || event.key === 16777237) {
- if (!keyboardNavigationActive) {
- keyboardNavigationActive = true;
- rebuildFlatNavigation(); // Ensure we have fresh navigation data
- selectedFlatIndex = 0;
- updateSelectedIdFromIndex();
- // Set keyboardActive on listView to show highlight
- if (listView) {
- listView.keyboardActive = true;
- }
- selectionVersion++;
- ensureVisible();
- event.accepted = true;
- } else {
- selectNext();
- event.accepted = true;
- }
- } else if (event.key === Qt.Key_Up || event.key === 16777235) {
- if (!keyboardNavigationActive) {
- keyboardNavigationActive = true;
- rebuildFlatNavigation(); // Ensure we have fresh navigation data
- selectedFlatIndex = 0;
- updateSelectedIdFromIndex();
- // Set keyboardActive on listView to show highlight
- if (listView) {
- listView.keyboardActive = true;
- }
- selectionVersion++;
- ensureVisible();
- event.accepted = true;
- } else if (selectedFlatIndex === 0) {
- keyboardNavigationActive = false;
- // Reset keyboardActive when navigation is disabled
- if (listView) {
- listView.keyboardActive = false;
- }
- selectionVersion++;
- event.accepted = true;
- return;
- } else {
- selectPrevious();
- event.accepted = true;
- }
- } else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
- if (!keyboardNavigationActive) {
- keyboardNavigationActive = true;
- rebuildFlatNavigation();
- selectedFlatIndex = 0;
- updateSelectedIdFromIndex();
- if (listView) {
- listView.keyboardActive = true;
- }
- selectionVersion++;
- ensureVisible();
- } else {
- selectNext();
- }
- event.accepted = true;
- } else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
- if (!keyboardNavigationActive) {
- keyboardNavigationActive = true;
- rebuildFlatNavigation();
- selectedFlatIndex = 0;
- updateSelectedIdFromIndex();
- if (listView) {
- listView.keyboardActive = true;
- }
- selectionVersion++;
- ensureVisible();
- } else if (selectedFlatIndex === 0) {
- keyboardNavigationActive = false;
- if (listView) {
- listView.keyboardActive = false;
- }
- selectionVersion++;
- } else {
- selectPrevious();
- }
- event.accepted = true;
- } else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
- if (!keyboardNavigationActive) {
- keyboardNavigationActive = true;
- rebuildFlatNavigation();
- selectedFlatIndex = 0;
- updateSelectedIdFromIndex();
- if (listView) {
- listView.keyboardActive = true;
- }
- selectionVersion++;
- ensureVisible();
- } else {
- selectNext();
- }
- event.accepted = true;
- } else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
- if (!keyboardNavigationActive) {
- keyboardNavigationActive = true;
- rebuildFlatNavigation();
- selectedFlatIndex = 0;
- updateSelectedIdFromIndex();
- if (listView) {
- listView.keyboardActive = true;
- }
- selectionVersion++;
- ensureVisible();
- } else if (selectedFlatIndex === 0) {
- keyboardNavigationActive = false;
- if (listView) {
- listView.keyboardActive = false;
- }
- selectionVersion++;
- } else {
- selectPrevious();
- }
- event.accepted = true;
- } else if (keyboardNavigationActive) {
- if (event.key === Qt.Key_Space) {
- toggleGroupExpanded();
- event.accepted = true;
- } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
- handleEnterKey();
- event.accepted = true;
- } else if (event.key === Qt.Key_E) {
- toggleTextExpanded();
- event.accepted = true;
- } else if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) {
- clearSelected();
- event.accepted = true;
- } else if (event.key === Qt.Key_Tab) {
- selectNext();
- event.accepted = true;
- } else if (event.key === Qt.Key_Backtab) {
- selectPrevious();
- event.accepted = true;
- } else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) {
- const actionIndex = event.key - Qt.Key_1;
- executeAction(actionIndex);
- event.accepted = true;
- }
- }
-
- if (event.key === Qt.Key_F10) {
- showKeyboardHints = !showKeyboardHints;
- event.accepted = true;
- }
- }
-
- // Get current selection info for UI
- function getCurrentSelection() {
- if (!keyboardNavigationActive || selectedFlatIndex < 0 || selectedFlatIndex >= flatNavigation.length) {
- return {
- "type": "",
- "groupIndex": -1,
- "notificationIndex": -1
- };
- }
- const result = flatNavigation[selectedFlatIndex] || {
- "type": "",
- "groupIndex": -1,
- "notificationIndex": -1
- };
- return result;
- }
-}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationKeyboardHints.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationKeyboardHints.qml
deleted file mode 100644
index 78ba2d5..0000000
--- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationKeyboardHints.qml
+++ /dev/null
@@ -1,50 +0,0 @@
-import QtQuick
-import qs.Common
-import qs.Widgets
-
-Rectangle {
- id: root
-
- property bool showHints: false
-
- height: 80
- radius: Theme.cornerRadius
- color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
- border.color: Theme.primary
- border.width: 2
- opacity: showHints ? 1 : 0
- z: 100
-
- Column {
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.margins: Theme.spacingS
- spacing: 2
-
- StyledText {
- text: I18n.tr("↑/↓: Nav • Space: Expand • Enter: Action/Expand • E: Text")
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- width: parent.width
- wrapMode: Text.WordWrap
- horizontalAlignment: Text.AlignHCenter
- }
-
- StyledText {
- text: I18n.tr("Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close")
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- width: parent.width
- wrapMode: Text.WordWrap
- horizontalAlignment: Text.AlignHCenter
- }
- }
-
- Behavior on opacity {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationSettings.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationSettings.qml
deleted file mode 100644
index 97364c7..0000000
--- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationSettings.qml
+++ /dev/null
@@ -1,449 +0,0 @@
-import QtQuick
-import qs.Common
-import qs.Widgets
-
-Rectangle {
- id: root
-
- property bool expanded: false
- property real maxAllowedHeight: 0
- readonly property real naturalContentHeight: contentColumn.height + Theme.spacingL * 2
-
- width: parent.width
- height: expanded ? (maxAllowedHeight > 0 ? Math.min(naturalContentHeight, maxAllowedHeight) : naturalContentHeight) : 0
- visible: expanded
- clip: true
- radius: Theme.cornerRadius
- color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
- border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
- border.width: 1
-
- Behavior on height {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.emphasizedEasing
- }
- }
-
- opacity: expanded ? 1 : 0
- Behavior on opacity {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.emphasizedEasing
- }
- }
-
- readonly property var timeoutOptions: [
- {
- "text": I18n.tr("Never"),
- "value": 0
- },
- {
- "text": I18n.tr("1 second"),
- "value": 1000
- },
- {
- "text": I18n.tr("3 seconds"),
- "value": 3000
- },
- {
- "text": I18n.tr("5 seconds"),
- "value": 5000
- },
- {
- "text": I18n.tr("8 seconds"),
- "value": 8000
- },
- {
- "text": I18n.tr("10 seconds"),
- "value": 10000
- },
- {
- "text": I18n.tr("15 seconds"),
- "value": 15000
- },
- {
- "text": I18n.tr("30 seconds"),
- "value": 30000
- },
- {
- "text": I18n.tr("1 minute"),
- "value": 60000
- },
- {
- "text": I18n.tr("2 minutes"),
- "value": 120000
- },
- {
- "text": I18n.tr("5 minutes"),
- "value": 300000
- },
- {
- "text": I18n.tr("10 minutes"),
- "value": 600000
- }
- ]
-
- function getTimeoutText(value) {
- if (value === undefined || value === null || isNaN(value)) {
- return I18n.tr("5 seconds");
- }
-
- for (let i = 0; i < timeoutOptions.length; i++) {
- if (timeoutOptions[i].value === value) {
- return timeoutOptions[i].text;
- }
- }
- if (value === 0) {
- return I18n.tr("Never");
- }
- if (value < 1000) {
- return value + "ms";
- }
- if (value < 60000) {
- return Math.round(value / 1000) + " " + I18n.tr("seconds");
- }
- return Math.round(value / 60000) + " " + I18n.tr("minutes");
- }
-
- Flickable {
- id: settingsFlickable
- anchors.fill: parent
- contentHeight: contentColumn.height + Theme.spacingL * 2
- clip: true
- flickableDirection: Flickable.VerticalFlick
- boundsBehavior: Flickable.DragAndOvershootBounds
- interactive: root.naturalContentHeight > root.height
-
- Column {
- id: contentColumn
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.margins: Theme.spacingL
- spacing: Theme.spacingM
-
- StyledText {
- text: I18n.tr("Notification Settings")
- font.pixelSize: Theme.fontSizeMedium
- font.weight: Font.Bold
- color: Theme.surfaceText
- }
-
- Item {
- width: parent.width
- height: Math.max(dndRow.implicitHeight, dndToggle.implicitHeight) + Theme.spacingS
-
- Row {
- id: dndRow
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- spacing: Theme.spacingM
-
- DankIcon {
- name: SessionData.doNotDisturb ? "notifications_off" : "notifications"
- size: Theme.iconSizeSmall
- color: SessionData.doNotDisturb ? Theme.error : Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
-
- StyledText {
- text: I18n.tr("Do Not Disturb")
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
- }
-
- DankToggle {
- id: dndToggle
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- checked: SessionData.doNotDisturb
- onToggled: SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
- }
-
- StyledText {
- text: I18n.tr("Notification Timeouts")
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Medium
- color: Theme.surfaceVariantText
- }
-
- DankDropdown {
- text: I18n.tr("Low Priority")
- description: I18n.tr("Timeout for low priority notifications")
- currentValue: getTimeoutText(SettingsData.notificationTimeoutLow)
- options: timeoutOptions.map(opt => opt.text)
- onValueChanged: value => {
- for (let i = 0; i < timeoutOptions.length; i++) {
- if (timeoutOptions[i].text === value) {
- SettingsData.set("notificationTimeoutLow", timeoutOptions[i].value);
- break;
- }
- }
- }
- }
-
- DankDropdown {
- text: I18n.tr("Normal Priority")
- description: I18n.tr("Timeout for normal priority notifications")
- currentValue: getTimeoutText(SettingsData.notificationTimeoutNormal)
- options: timeoutOptions.map(opt => opt.text)
- onValueChanged: value => {
- for (let i = 0; i < timeoutOptions.length; i++) {
- if (timeoutOptions[i].text === value) {
- SettingsData.set("notificationTimeoutNormal", timeoutOptions[i].value);
- break;
- }
- }
- }
- }
-
- DankDropdown {
- text: I18n.tr("Critical Priority")
- description: I18n.tr("Timeout for critical priority notifications")
- currentValue: getTimeoutText(SettingsData.notificationTimeoutCritical)
- options: timeoutOptions.map(opt => opt.text)
- onValueChanged: value => {
- for (let i = 0; i < timeoutOptions.length; i++) {
- if (timeoutOptions[i].text === value) {
- SettingsData.set("notificationTimeoutCritical", timeoutOptions[i].value);
- break;
- }
- }
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
- }
-
- Item {
- width: parent.width
- height: Math.max(overlayRow.implicitHeight, overlayToggle.implicitHeight) + Theme.spacingS
-
- Row {
- id: overlayRow
- anchors.left: parent.left
- anchors.right: overlayToggle.left
- anchors.rightMargin: Theme.spacingM
- anchors.verticalCenter: parent.verticalCenter
- spacing: Theme.spacingM
-
- DankIcon {
- name: "notifications_active"
- size: Theme.iconSizeSmall
- color: SettingsData.notificationOverlayEnabled ? Theme.primary : Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
-
- Column {
- spacing: 2
- anchors.verticalCenter: parent.verticalCenter
- width: overlayRow.width - Theme.iconSizeSmall - Theme.spacingM
-
- StyledText {
- width: parent.width
- text: I18n.tr("Notification Overlay")
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- wrapMode: Text.Wrap
- }
-
- StyledText {
- width: parent.width
- text: I18n.tr("Display all priorities over fullscreen apps")
- font.pixelSize: Theme.fontSizeSmall - 1
- color: Theme.surfaceVariantText
- wrapMode: Text.Wrap
- }
- }
- }
-
- DankToggle {
- id: overlayToggle
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- checked: SettingsData.notificationOverlayEnabled
- onToggled: toggled => SettingsData.set("notificationOverlayEnabled", toggled)
- }
- }
-
- Item {
- width: parent.width
- height: Math.max(privacyRow.implicitHeight, privacyToggle.implicitHeight) + Theme.spacingS
-
- Row {
- id: privacyRow
- anchors.left: parent.left
- anchors.right: privacyToggle.left
- anchors.rightMargin: Theme.spacingM
- anchors.verticalCenter: parent.verticalCenter
- spacing: Theme.spacingM
-
- DankIcon {
- name: "privacy_tip"
- size: Theme.iconSizeSmall
- color: SettingsData.notificationPopupPrivacyMode ? Theme.primary : Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
-
- Column {
- spacing: 2
- anchors.verticalCenter: parent.verticalCenter
- width: privacyRow.width - Theme.iconSizeSmall - Theme.spacingM
-
- StyledText {
- width: parent.width
- text: I18n.tr("Privacy Mode")
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- wrapMode: Text.Wrap
- }
-
- StyledText {
- width: parent.width
- text: I18n.tr("Hide notification content until expanded")
- font.pixelSize: Theme.fontSizeSmall - 1
- color: Theme.surfaceVariantText
- wrapMode: Text.Wrap
- }
- }
- }
-
- DankToggle {
- id: privacyToggle
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- checked: SettingsData.notificationPopupPrivacyMode
- onToggled: toggled => SettingsData.set("notificationPopupPrivacyMode", toggled)
- }
- }
-
- Rectangle {
- width: parent.width
- height: 1
- color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
- }
-
- StyledText {
- text: I18n.tr("History Settings")
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Medium
- color: Theme.surfaceVariantText
- }
-
- Item {
- width: parent.width
- height: Math.max(lowRow.implicitHeight, lowToggle.implicitHeight) + Theme.spacingS
-
- Row {
- id: lowRow
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- spacing: Theme.spacingM
-
- DankIcon {
- name: "low_priority"
- size: Theme.iconSizeSmall
- color: SettingsData.notificationHistorySaveLow ? Theme.primary : Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
-
- StyledText {
- text: I18n.tr("Low Priority")
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
- }
-
- DankToggle {
- id: lowToggle
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- checked: SettingsData.notificationHistorySaveLow
- onToggled: toggled => SettingsData.set("notificationHistorySaveLow", toggled)
- }
- }
-
- Item {
- width: parent.width
- height: Math.max(normalRow.implicitHeight, normalToggle.implicitHeight) + Theme.spacingS
-
- Row {
- id: normalRow
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- spacing: Theme.spacingM
-
- DankIcon {
- name: "notifications"
- size: Theme.iconSizeSmall
- color: SettingsData.notificationHistorySaveNormal ? Theme.primary : Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
-
- StyledText {
- text: I18n.tr("Normal Priority")
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
- }
-
- DankToggle {
- id: normalToggle
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- checked: SettingsData.notificationHistorySaveNormal
- onToggled: toggled => SettingsData.set("notificationHistorySaveNormal", toggled)
- }
- }
-
- Item {
- width: parent.width
- height: Math.max(criticalRow.implicitHeight, criticalToggle.implicitHeight) + Theme.spacingS
-
- Row {
- id: criticalRow
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- spacing: Theme.spacingM
-
- DankIcon {
- name: "priority_high"
- size: Theme.iconSizeSmall
- color: SettingsData.notificationHistorySaveCritical ? Theme.primary : Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
-
- StyledText {
- text: I18n.tr("Critical Priority")
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
- }
-
- DankToggle {
- id: criticalToggle
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- checked: SettingsData.notificationHistorySaveCritical
- onToggled: toggled => SettingsData.set("notificationHistorySaveCritical", toggled)
- }
- }
- }
- }
-}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Popup/NotificationPopup.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Popup/NotificationPopup.qml
deleted file mode 100644
index 8c9f7f4..0000000
--- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Popup/NotificationPopup.qml
+++ /dev/null
@@ -1,1164 +0,0 @@
-import QtQuick
-import QtQuick.Controls
-import Quickshell
-import Quickshell.Wayland
-import Quickshell.Services.Notifications
-import qs.Common
-import qs.Services
-import qs.Widgets
-
-PanelWindow {
- id: win
-
- WindowBlur {
- targetWindow: win
- blurX: content.x + content.cardInset + swipeTx.x + tx.x
- blurY: content.y + content.cardInset + swipeTx.y + tx.y
- blurWidth: !win._finalized ? Math.max(0, content.width - content.cardInset * 2) : 0
- blurHeight: !win._finalized ? Math.max(0, content.height - content.cardInset * 2) : 0
- blurRadius: Theme.cornerRadius
- }
-
- WlrLayershell.namespace: "dms:notification-popup"
-
- required property var notificationData
- required property string notificationId
- readonly property bool hasValidData: notificationData && notificationData.notification
- readonly property alias hovered: cardHoverHandler.hovered
- property int screenY: 0
- property bool exiting: false
- property bool _isDestroying: false
- property bool _finalized: false
- property real _lastReportedAlignedHeight: -1
- property real _storedTopMargin: 0
- property real _storedBottomMargin: 0
- readonly property string clearText: I18n.tr("Dismiss")
- property bool descriptionExpanded: false
- readonly property bool hasExpandableBody: (notificationData?.htmlBody || "").replace(/<[^>]*>/g, "").trim().length > 0
- onDescriptionExpandedChanged: {
- popupHeightChanged();
- }
- onImplicitHeightChanged: {
- const aligned = Theme.px(implicitHeight, dpr);
- if (Math.abs(aligned - _lastReportedAlignedHeight) < 0.5)
- return;
- _lastReportedAlignedHeight = aligned;
- popupHeightChanged();
- }
-
- readonly property bool compactMode: SettingsData.notificationCompactMode
- readonly property real cardPadding: compactMode ? Theme.notificationCardPaddingCompact : Theme.notificationCardPadding
- readonly property real popupIconSize: compactMode ? Theme.notificationIconSizeCompact : Theme.notificationIconSizeNormal
- readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS
- readonly property real contentBottomClearance: 8
- readonly property real actionButtonHeight: compactMode ? 20 : 24
- readonly property real collapsedContentHeight: Math.max(popupIconSize, Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2)) + contentBottomClearance
- readonly property real privacyCollapsedContentHeight: Math.max(popupIconSize, Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2) + contentBottomClearance
- readonly property real basePopupHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + contentSpacing
- readonly property real basePopupHeightPrivacy: cardPadding * 2 + privacyCollapsedContentHeight + actionButtonHeight + contentSpacing
-
- signal entered
- signal exitStarted
- signal exitFinished
- signal popupHeightChanged
-
- function startExit() {
- if (exiting || _isDestroying) {
- return;
- }
- exiting = true;
- exitStarted();
- exitAnim.restart();
- exitWatchdog.restart();
- if (NotificationService.removeFromVisibleNotifications)
- NotificationService.removeFromVisibleNotifications(win.notificationData);
- }
-
- function forceExit() {
- if (_isDestroying) {
- return;
- }
- _isDestroying = true;
- exiting = true;
- visible = false;
- exitWatchdog.stop();
- finalizeExit("forced");
- }
-
- function finalizeExit(reason) {
- if (_finalized) {
- return;
- }
-
- _finalized = true;
- _isDestroying = true;
- exitWatchdog.stop();
- wrapperConn.enabled = false;
- wrapperConn.target = null;
- win.exitFinished();
- }
-
- visible: !_finalized
- WlrLayershell.layer: {
- const envLayer = Quickshell.env("DMS_NOTIFICATION_LAYER");
- if (envLayer) {
- switch (envLayer) {
- case "bottom":
- return WlrLayershell.Bottom;
- case "overlay":
- return WlrLayershell.Overlay;
- case "background":
- return WlrLayershell.Background;
- case "top":
- return WlrLayershell.Top;
- }
- }
-
- if (!notificationData)
- return WlrLayershell.Top;
-
- SettingsData.notificationOverlayEnabled;
-
- const shouldUseOverlay = (SettingsData.notificationOverlayEnabled) || (notificationData.urgency === NotificationUrgency.Critical);
-
- return shouldUseOverlay ? WlrLayershell.Overlay : WlrLayershell.Top;
- }
- WlrLayershell.exclusiveZone: -1
- WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
- color: "transparent"
- readonly property real contentImplicitWidth: screen ? Math.min(400, Math.max(320, screen.width * 0.23)) : 380
- readonly property real contentImplicitHeight: {
- if (SettingsData.notificationPopupPrivacyMode && !descriptionExpanded)
- return basePopupHeightPrivacy;
- if (!descriptionExpanded)
- return basePopupHeight;
- const bodyTextHeight = bodyText.contentHeight || 0;
- const collapsedBodyHeight = Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2);
- if (bodyTextHeight > collapsedBodyHeight + 2)
- return basePopupHeight + bodyTextHeight - collapsedBodyHeight;
- return basePopupHeight;
- }
- implicitWidth: contentImplicitWidth + (windowShadowPad * 2)
- implicitHeight: contentImplicitHeight + (windowShadowPad * 2)
-
- Behavior on implicitHeight {
- enabled: !exiting && !_isDestroying
- NumberAnimation {
- id: implicitHeightAnim
- duration: descriptionExpanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration
- easing.type: Easing.BezierSpline
- easing.bezierCurve: Theme.expressiveCurves.emphasized
- }
- }
-
- onHasValidDataChanged: {
- if (!hasValidData && !exiting && !_isDestroying) {
- forceExit();
- }
- }
- Component.onCompleted: {
- _lastReportedAlignedHeight = Theme.px(implicitHeight, dpr);
- _storedTopMargin = getTopMargin();
- _storedBottomMargin = getBottomMargin();
- if (SettingsData.notificationPopupPrivacyMode)
- descriptionExpanded = false;
- if (hasValidData) {
- Qt.callLater(() => enterX.restart());
- } else {
- forceExit();
- }
- }
- onNotificationDataChanged: {
- if (!_isDestroying) {
- if (SettingsData.notificationPopupPrivacyMode)
- descriptionExpanded = false;
- wrapperConn.target = win.notificationData || null;
- notificationConn.target = (win.notificationData && win.notificationData.notification && win.notificationData.notification.Retainable) || null;
- }
- }
- onEntered: {
- if (!_isDestroying) {
- enterDelay.start();
- }
- }
- Component.onDestruction: {
- _isDestroying = true;
- exitWatchdog.stop();
- if (notificationData && notificationData.timer) {
- notificationData.timer.stop();
- }
- }
-
- property bool isTopCenter: SettingsData.notificationPopupPosition === -1
- property bool isBottomCenter: SettingsData.notificationPopupPosition === SettingsData.Position.BottomCenter
- property bool isCenterPosition: isTopCenter || isBottomCenter
- readonly property real maxPopupShadowBlurPx: Math.max((Theme.elevationLevel3 && Theme.elevationLevel3.blurPx !== undefined) ? Theme.elevationLevel3.blurPx : 12, (Theme.elevationLevel4 && Theme.elevationLevel4.blurPx !== undefined) ? Theme.elevationLevel4.blurPx : 16)
- readonly property real maxPopupShadowOffsetXPx: Math.max(Math.abs(Theme.elevationOffsetX(Theme.elevationLevel3)), Math.abs(Theme.elevationOffsetX(Theme.elevationLevel4)))
- readonly property real maxPopupShadowOffsetYPx: Math.max(Math.abs(Theme.elevationOffsetY(Theme.elevationLevel3, 6)), Math.abs(Theme.elevationOffsetY(Theme.elevationLevel4, 8)))
- readonly property real windowShadowPad: Theme.elevationEnabled && SettingsData.notificationPopupShadowEnabled ? Theme.snap(Math.max(16, maxPopupShadowBlurPx + Math.max(maxPopupShadowOffsetXPx, maxPopupShadowOffsetYPx) + 8), dpr) : 0
-
- anchors.top: true
- anchors.left: true
- anchors.bottom: false
- anchors.right: false
-
- mask: contentInputMask
-
- Region {
- id: contentInputMask
- item: contentMaskRect
- }
-
- Item {
- id: contentMaskRect
- visible: false
- x: content.x
- y: content.y
- width: alignedWidth
- height: alignedHeight
- }
-
- margins {
- top: getWindowTopMargin()
- bottom: 0
- left: getWindowLeftMargin()
- right: 0
- }
-
- function getBarInfo() {
- if (!screen)
- return {
- topBar: 0,
- bottomBar: 0,
- leftBar: 0,
- rightBar: 0
- };
- return SettingsData.getAdjacentBarInfo(screen, SettingsData.notificationPopupPosition, {
- id: "notification-popup",
- screenPreferences: [screen.name],
- autoHide: false
- });
- }
-
- function getTopMargin() {
- const popupPos = SettingsData.notificationPopupPosition;
- const isTop = isTopCenter || popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Left;
- if (!isTop)
- return 0;
-
- const barInfo = getBarInfo();
- const base = barInfo.topBar > 0 ? barInfo.topBar : Theme.popupDistance;
- return base + screenY;
- }
-
- function getBottomMargin() {
- const popupPos = SettingsData.notificationPopupPosition;
- const isBottom = isBottomCenter || popupPos === SettingsData.Position.Bottom || popupPos === SettingsData.Position.Right;
- if (!isBottom)
- return 0;
-
- const barInfo = getBarInfo();
- const base = barInfo.bottomBar > 0 ? barInfo.bottomBar : Theme.popupDistance;
- return base + screenY;
- }
-
- function getLeftMargin() {
- if (isCenterPosition)
- return screen ? (screen.width - alignedWidth) / 2 : 0;
-
- const popupPos = SettingsData.notificationPopupPosition;
- const isLeft = popupPos === SettingsData.Position.Left || popupPos === SettingsData.Position.Bottom;
- if (!isLeft)
- return 0;
-
- const barInfo = getBarInfo();
- return barInfo.leftBar > 0 ? barInfo.leftBar : Theme.popupDistance;
- }
-
- function getRightMargin() {
- if (isCenterPosition)
- return 0;
-
- const popupPos = SettingsData.notificationPopupPosition;
- const isRight = popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Right;
- if (!isRight)
- return 0;
-
- const barInfo = getBarInfo();
- return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance;
- }
-
- function getContentX() {
- if (!screen)
- return 0;
-
- const popupPos = SettingsData.notificationPopupPosition;
- const barLeft = getLeftMargin();
- const barRight = getRightMargin();
-
- if (isCenterPosition)
- return Theme.snap((screen.width - alignedWidth) / 2, dpr);
- if (popupPos === SettingsData.Position.Left || popupPos === SettingsData.Position.Bottom)
- return Theme.snap(barLeft, dpr);
- return Theme.snap(screen.width - alignedWidth - barRight, dpr);
- }
-
- function getContentY() {
- if (!screen)
- return 0;
-
- const popupPos = SettingsData.notificationPopupPosition;
- const barTop = getTopMargin();
- const barBottom = getBottomMargin();
- const isTop = isTopCenter || popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Left;
- if (isTop)
- return Theme.snap(barTop, dpr);
- return Theme.snap(screen.height - alignedHeight - barBottom, dpr);
- }
-
- function getWindowLeftMargin() {
- if (!screen)
- return 0;
- return Theme.snap(getContentX() - windowShadowPad, dpr);
- }
-
- function getWindowTopMargin() {
- if (!screen)
- return 0;
- return Theme.snap(getContentY() - windowShadowPad, dpr);
- }
-
- readonly property bool screenValid: win.screen && !_isDestroying
- readonly property real dpr: screenValid ? CompositorService.getScreenScale(win.screen) : 1
- readonly property real alignedWidth: Theme.px(Math.max(0, implicitWidth - (windowShadowPad * 2)), dpr)
- readonly property real alignedHeight: Theme.px(Math.max(0, implicitHeight - (windowShadowPad * 2)), dpr)
-
- Item {
- id: content
-
- x: Theme.snap(windowShadowPad, dpr)
- y: Theme.snap(windowShadowPad, dpr)
- width: alignedWidth
- height: alignedHeight
- visible: !win._finalized
- scale: cardHoverHandler.hovered ? 1.01 : 1.0
- transformOrigin: Item.Center
-
- Behavior on scale {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- property real swipeOffset: 0
- readonly property real dismissThreshold: isCenterPosition ? height * 0.4 : width * 0.35
- readonly property real swipeFadeStartRatio: 0.75
- readonly property real swipeTravelDistance: isCenterPosition ? height : width
- readonly property real swipeFadeStartOffset: swipeTravelDistance * swipeFadeStartRatio
- readonly property real swipeFadeDistance: Math.max(1, swipeTravelDistance - swipeFadeStartOffset)
- readonly property bool swipeActive: swipeDragHandler.active
- property bool swipeDismissing: false
-
- readonly property bool shadowsAllowed: Theme.elevationEnabled && SettingsData.notificationPopupShadowEnabled
- readonly property var elevLevel: cardHoverHandler.hovered ? Theme.elevationLevel4 : Theme.elevationLevel3
- readonly property real cardInset: Theme.snap(4, win.dpr)
- readonly property real shadowRenderPadding: shadowsAllowed ? Theme.snap(Math.max(16, shadowBlurPx + Math.max(Math.abs(shadowOffsetX), Math.abs(shadowOffsetY)) + 8), win.dpr) : 0
- property real shadowBlurPx: shadowsAllowed ? (elevLevel && elevLevel.blurPx !== undefined ? elevLevel.blurPx : 12) : 0
- property real shadowOffsetX: shadowsAllowed ? Theme.elevationOffsetX(elevLevel) : 0
- property real shadowOffsetY: shadowsAllowed ? Theme.elevationOffsetY(elevLevel, 6) : 0
-
- Behavior on shadowBlurPx {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- Behavior on shadowOffsetX {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- Behavior on shadowOffsetY {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- ElevationShadow {
- id: bgShadowLayer
- anchors.fill: parent
- anchors.margins: -content.shadowRenderPadding
- level: content.elevLevel
- fallbackOffset: 6
- shadowBlurPx: content.shadowBlurPx
- shadowOffsetX: content.shadowOffsetX
- shadowOffsetY: content.shadowOffsetY
- shadowColor: content.shadowsAllowed && content.elevLevel ? Theme.elevationShadowColor(content.elevLevel) : "transparent"
- shadowEnabled: !win._isDestroying && win.screenValid && content.shadowsAllowed
- layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr))
- layer.textureMirroring: ShaderEffectSource.MirrorVertically
-
- sourceRect.anchors.fill: undefined
- sourceRect.x: content.shadowRenderPadding + content.cardInset
- sourceRect.y: content.shadowRenderPadding + content.cardInset
- sourceRect.width: Math.max(0, content.width - (content.cardInset * 2))
- sourceRect.height: Math.max(0, content.height - (content.cardInset * 2))
- sourceRect.radius: Theme.cornerRadius
- sourceRect.color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
- sourceRect.border.color: notificationData && notificationData.urgency === NotificationUrgency.Critical ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.outline, 0.08)
- sourceRect.border.width: notificationData && notificationData.urgency === NotificationUrgency.Critical ? 2 : 0
-
- Rectangle {
- x: bgShadowLayer.sourceRect.x
- y: bgShadowLayer.sourceRect.y
- width: bgShadowLayer.sourceRect.width
- height: bgShadowLayer.sourceRect.height
- radius: bgShadowLayer.sourceRect.radius
- visible: notificationData && notificationData.urgency === NotificationUrgency.Critical
- opacity: 1
- clip: true
-
- gradient: Gradient {
- orientation: Gradient.Horizontal
-
- GradientStop {
- position: 0
- color: Theme.primary
- }
-
- GradientStop {
- position: 0.02
- color: Theme.primary
- }
-
- GradientStop {
- position: 0.021
- color: "transparent"
- }
- }
- }
- }
-
- Rectangle {
- anchors.fill: parent
- anchors.margins: content.cardInset
- radius: Theme.cornerRadius
- color: "transparent"
- border.color: BlurService.borderColor
- border.width: BlurService.borderWidth
- z: 100
- }
-
- Item {
- id: backgroundContainer
- anchors.fill: parent
- anchors.margins: content.cardInset
- clip: true
-
- HoverHandler {
- id: cardHoverHandler
- }
-
- Connections {
- target: cardHoverHandler
- function onHoveredChanged() {
- if (!notificationData || win.exiting || win._isDestroying)
- return;
- if (cardHoverHandler.hovered) {
- if (notificationData.timer)
- notificationData.timer.stop();
- } else if (notificationData.popup && notificationData.timer) {
- notificationData.timer.restart();
- }
- }
- }
-
- LayoutMirroring.enabled: I18n.isRtl
- LayoutMirroring.childrenInherit: true
-
- Item {
- id: notificationContent
-
- readonly property real expandedTextHeight: bodyText.contentHeight || 0
- readonly property real collapsedBodyHeight: Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2)
- readonly property real effectiveCollapsedHeight: (SettingsData.notificationPopupPrivacyMode && !descriptionExpanded) ? win.privacyCollapsedContentHeight : win.collapsedContentHeight
- readonly property real extraHeight: (descriptionExpanded && expandedTextHeight > collapsedBodyHeight + 2) ? (expandedTextHeight - collapsedBodyHeight) : 0
-
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.topMargin: cardPadding
- anchors.leftMargin: Theme.spacingL
- anchors.rightMargin: Theme.spacingL + Theme.notificationHoverRevealMargin
- height: effectiveCollapsedHeight + extraHeight
- clip: SettingsData.notificationPopupPrivacyMode && !descriptionExpanded
-
- DankCircularImage {
- id: iconContainer
-
- readonly property string rawImage: notificationData?.image || ""
- readonly property string iconFromImage: {
- if (rawImage.startsWith("image://icon/"))
- return rawImage.substring(13);
- return "";
- }
- readonly property bool imageHasSpecialPrefix: {
- const icon = iconFromImage;
- return icon.startsWith("material:") || icon.startsWith("svg:") || icon.startsWith("unicode:") || icon.startsWith("image:");
- }
- readonly property bool hasNotificationImage: rawImage !== "" && !rawImage.startsWith("image://icon/")
- readonly property bool needsImagePersist: hasNotificationImage && rawImage.startsWith("image://qsimage/") && !notificationData.persistedImagePath
-
- width: popupIconSize
- height: popupIconSize
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.topMargin: {
- if (SettingsData.notificationPopupPrivacyMode && !descriptionExpanded) {
- const headerSummary = Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2;
- return Math.max(0, headerSummary / 2 - popupIconSize / 2);
- }
- if (descriptionExpanded)
- return Math.max(0, Theme.fontSizeSmall * 1.2 + (Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2)) / 2 - popupIconSize / 2);
- return Math.max(0, Theme.fontSizeSmall * 1.2 + (textContainer.height - Theme.fontSizeSmall * 1.2) / 2 - popupIconSize / 2);
- }
-
- imageSource: {
- if (!notificationData)
- return "";
- if (hasNotificationImage)
- return notificationData.cleanImage || "";
- if (imageHasSpecialPrefix)
- return "";
- const appIcon = notificationData.appIcon;
- if (!appIcon)
- return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
- if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
- return appIcon;
- if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
- return "";
- return Paths.resolveIconPath(appIcon);
- }
-
- hasImage: hasNotificationImage
- fallbackIcon: {
- if (imageHasSpecialPrefix)
- return iconFromImage;
- return notificationData?.appIcon || iconFromImage || "";
- }
- fallbackText: {
- const appName = notificationData?.appName || "?";
- return appName.charAt(0).toUpperCase();
- }
-
- onImageStatusChanged: {
- if (imageStatus === Image.Ready && needsImagePersist) {
- const cachePath = NotificationService.getImageCachePath(notificationData);
- saveImageToFile(cachePath);
- }
- }
-
- onImageSaved: filePath => {
- if (!notificationData)
- return;
- notificationData.persistedImagePath = filePath;
- const wrapperId = notificationData.notification?.id?.toString() || "";
- if (wrapperId)
- NotificationService.updateHistoryImage(wrapperId, filePath);
- }
- }
-
- Column {
- id: textContainer
-
- anchors.left: iconContainer.right
- anchors.leftMargin: Theme.spacingM
- anchors.right: parent.right
- anchors.top: parent.top
- spacing: Theme.notificationContentSpacing
-
- Row {
- id: headerRow
- width: parent.width
- spacing: Theme.spacingXS
- visible: headerAppNameText.text.length > 0 || headerTimeText.text.length > 0
-
- StyledText {
- id: headerAppNameText
- text: notificationData ? (notificationData.appName || "") : ""
- color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Normal
- elide: Text.ElideRight
- maximumLineCount: 1
- width: Math.min(implicitWidth, parent.width - headerSeparator.implicitWidth - headerTimeText.implicitWidth - parent.spacing * 2)
- }
-
- StyledText {
- id: headerSeparator
- text: (headerAppNameText.text.length > 0 && headerTimeText.text.length > 0) ? " • " : ""
- color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Normal
- }
-
- StyledText {
- id: headerTimeText
- text: notificationData ? (notificationData.timeStr || "") : ""
- color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Normal
- }
- }
-
- StyledText {
- text: notificationData ? (notificationData.summary || "") : ""
- color: Theme.surfaceText
- font.pixelSize: Theme.fontSizeMedium
- font.weight: Font.Medium
- width: parent.width
- elide: Text.ElideRight
- horizontalAlignment: Text.AlignLeft
- maximumLineCount: 1
- visible: text.length > 0
- }
-
- StyledText {
- id: bodyText
- property bool hasMoreText: truncated
-
- text: notificationData ? (notificationData.htmlBody || "") : ""
- color: Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- width: parent.width
- elide: descriptionExpanded ? Text.ElideNone : Text.ElideRight
- horizontalAlignment: Text.AlignLeft
- maximumLineCount: descriptionExpanded ? -1 : (compactMode ? 1 : 2)
- wrapMode: Text.WrapAtWordBoundaryOrAnywhere
- visible: text.length > 0
- opacity: (SettingsData.notificationPopupPrivacyMode && !descriptionExpanded) ? 0 : 1
- linkColor: Theme.primary
- onLinkActivated: link => Qt.openUrlExternally(link)
-
- MouseArea {
- anchors.fill: parent
- cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : (bodyText.hasMoreText || descriptionExpanded) ? Qt.PointingHandCursor : Qt.ArrowCursor
-
- onClicked: mouse => {
- if (!parent.hoveredLink && (bodyText.hasMoreText || descriptionExpanded))
- win.descriptionExpanded = !win.descriptionExpanded;
- }
-
- propagateComposedEvents: true
- onPressed: mouse => {
- if (parent.hoveredLink)
- mouse.accepted = false;
- }
- onReleased: mouse => {
- if (parent.hoveredLink)
- mouse.accepted = false;
- }
- }
- }
-
- StyledText {
- text: I18n.tr("Message Content", "notification privacy mode placeholder")
- color: Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- width: parent.width
- visible: SettingsData.notificationPopupPrivacyMode && !descriptionExpanded && win.hasExpandableBody
- }
- }
- }
-
- DankActionButton {
- id: closeButton
-
- anchors.right: parent.right
- anchors.top: parent.top
- anchors.topMargin: cardPadding
- anchors.rightMargin: Theme.spacingL
- iconName: "close"
- iconSize: compactMode ? 14 : 16
- buttonSize: compactMode ? 20 : 24
- z: 15
-
- onClicked: {
- if (notificationData && !win.exiting)
- notificationData.popup = false;
- }
- }
-
- DankActionButton {
- id: expandButton
-
- anchors.right: closeButton.left
- anchors.rightMargin: Theme.spacingXS
- anchors.top: parent.top
- anchors.topMargin: cardPadding
- iconName: descriptionExpanded ? "expand_less" : "expand_more"
- iconSize: compactMode ? 14 : 16
- buttonSize: compactMode ? 20 : 24
- z: 15
- visible: SettingsData.notificationPopupPrivacyMode && win.hasExpandableBody
-
- onClicked: {
- if (win.hasExpandableBody)
- win.descriptionExpanded = !win.descriptionExpanded;
- }
- }
-
- Row {
- visible: cardHoverHandler.hovered
- opacity: visible ? 1 : 0
- anchors.right: clearButton.visible ? clearButton.left : parent.right
- anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL
- anchors.top: notificationContent.bottom
- anchors.topMargin: contentSpacing
- spacing: contentSpacing
- z: 20
-
- Behavior on opacity {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- Repeater {
- model: notificationData ? (notificationData.actions || []) : []
-
- Rectangle {
- property bool isHovered: false
-
- width: Math.max(actionText.implicitWidth + Theme.spacingM, Theme.notificationActionMinWidth)
- height: actionButtonHeight
- radius: Theme.notificationButtonCornerRadius
- color: isHovered ? Theme.withAlpha(Theme.primary, Theme.stateLayerHover) : "transparent"
-
- StyledText {
- id: actionText
-
- text: modelData.text || "Open"
- color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Medium
- anchors.centerIn: parent
- elide: Text.ElideRight
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- acceptedButtons: Qt.LeftButton
- onEntered: parent.isHovered = true
- onExited: parent.isHovered = false
- onClicked: {
- if (modelData && modelData.invoke)
- modelData.invoke();
- if (notificationData && !win.exiting)
- notificationData.popup = false;
- }
- }
- }
- }
- }
-
- Rectangle {
- id: clearButton
-
- property bool isHovered: false
- readonly property int actionCount: notificationData ? (notificationData.actions || []).length : 0
-
- visible: actionCount < 3 && cardHoverHandler.hovered
- opacity: visible ? 1 : 0
- Behavior on opacity {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
- anchors.right: parent.right
- anchors.rightMargin: Theme.spacingL
- anchors.top: notificationContent.bottom
- anchors.topMargin: contentSpacing
- width: Math.max(clearTextLabel.implicitWidth + Theme.spacingM, Theme.notificationActionMinWidth)
- height: actionButtonHeight
- radius: Theme.notificationButtonCornerRadius
- color: isHovered ? Theme.withAlpha(Theme.primary, Theme.stateLayerHover) : "transparent"
- z: 20
-
- StyledText {
- id: clearTextLabel
-
- text: win.clearText
- color: clearButton.isHovered ? Theme.primary : Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Medium
- anchors.centerIn: parent
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- acceptedButtons: Qt.LeftButton
- onEntered: clearButton.isHovered = true
- onExited: clearButton.isHovered = false
- onClicked: {
- if (notificationData && !win.exiting)
- NotificationService.dismissNotification(notificationData);
- }
- }
- }
-
- MouseArea {
- id: cardHoverArea
-
- anchors.fill: parent
- hoverEnabled: true
- acceptedButtons: Qt.LeftButton | Qt.RightButton
- cursorShape: Qt.PointingHandCursor
- propagateComposedEvents: true
- z: -1
- onClicked: mouse => {
- if (!notificationData || win.exiting)
- return;
- if (mouse.button === Qt.RightButton) {
- popupContextMenu.popup();
- } else if (mouse.button === Qt.LeftButton) {
- const canExpand = bodyText.hasMoreText || win.descriptionExpanded || (SettingsData.notificationPopupPrivacyMode && win.hasExpandableBody);
- if (canExpand) {
- win.descriptionExpanded = !win.descriptionExpanded;
- } else if (notificationData.actions && notificationData.actions.length > 0) {
- notificationData.actions[0].invoke();
- NotificationService.dismissNotification(notificationData);
- } else {
- notificationData.popup = false;
- }
- }
- }
- }
- }
-
- DragHandler {
- id: swipeDragHandler
- target: null
- xAxis.enabled: !isCenterPosition
- yAxis.enabled: isCenterPosition
-
- onActiveChanged: {
- if (active || win.exiting || content.swipeDismissing)
- return;
-
- if (Math.abs(content.swipeOffset) > content.dismissThreshold) {
- content.swipeDismissing = true;
- swipeDismissAnim.start();
- } else {
- content.swipeOffset = 0;
- }
- }
-
- onTranslationChanged: {
- if (win.exiting)
- return;
-
- const raw = isCenterPosition ? translation.y : translation.x;
- if (isTopCenter) {
- content.swipeOffset = Math.min(0, raw);
- } else if (isBottomCenter) {
- content.swipeOffset = Math.max(0, raw);
- } else {
- const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
- content.swipeOffset = isLeft ? Math.min(0, raw) : Math.max(0, raw);
- }
- }
- }
-
- opacity: {
- const swipeAmount = Math.abs(content.swipeOffset);
- if (swipeAmount <= content.swipeFadeStartOffset)
- return 1;
- const fadeProgress = (swipeAmount - content.swipeFadeStartOffset) / content.swipeFadeDistance;
- return Math.max(0, 1 - fadeProgress);
- }
-
- Behavior on opacity {
- enabled: !content.swipeActive
- NumberAnimation {
- duration: Theme.shortDuration
- }
- }
-
- Behavior on swipeOffset {
- enabled: !content.swipeActive && !content.swipeDismissing
- NumberAnimation {
- duration: Theme.notificationExitDuration
- easing.type: Theme.standardEasing
- }
- }
-
- NumberAnimation {
- id: swipeDismissAnim
- target: content
- property: "swipeOffset"
- to: isTopCenter ? -content.height : isBottomCenter ? content.height : (SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom ? -content.width : content.width)
- duration: Theme.notificationExitDuration
- easing.type: Easing.OutCubic
- onStopped: {
- NotificationService.dismissNotification(notificationData);
- win.forceExit();
- }
- }
-
- transform: [
- Translate {
- id: swipeTx
- x: isCenterPosition ? 0 : content.swipeOffset
- y: isCenterPosition ? content.swipeOffset : 0
- },
- Translate {
- id: tx
- x: {
- if (isCenterPosition)
- return 0;
- const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
- return isLeft ? -Anims.slidePx : Anims.slidePx;
- }
- y: isTopCenter ? -Anims.slidePx : isBottomCenter ? Anims.slidePx : 0
- }
- ]
- }
-
- NumberAnimation {
- id: enterX
-
- target: tx
- property: isCenterPosition ? "y" : "x"
- from: {
- if (isTopCenter)
- return -Anims.slidePx;
- if (isBottomCenter)
- return Anims.slidePx;
- const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
- return isLeft ? -Anims.slidePx : Anims.slidePx;
- }
- to: 0
- duration: Theme.notificationEnterDuration
- easing.type: Easing.BezierSpline
- easing.bezierCurve: isCenterPosition ? Theme.expressiveCurves.standardDecel : Theme.expressiveCurves.emphasizedDecel
- onStopped: {
- if (!win.exiting && !win._isDestroying) {
- if (isCenterPosition) {
- if (Math.abs(tx.y) < 0.5)
- win.entered();
- } else {
- if (Math.abs(tx.x) < 0.5)
- win.entered();
- }
- }
- }
- }
-
- ParallelAnimation {
- id: exitAnim
-
- onStopped: finalizeExit("animStopped")
-
- PropertyAnimation {
- target: tx
- property: isCenterPosition ? "y" : "x"
- from: 0
- to: {
- if (isTopCenter)
- return -Anims.slidePx;
- if (isBottomCenter)
- return Anims.slidePx;
- const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
- return isLeft ? -Anims.slidePx : Anims.slidePx;
- }
- duration: Theme.notificationExitDuration
- easing.type: Easing.BezierSpline
- easing.bezierCurve: Theme.expressiveCurves.emphasizedAccel
- }
-
- NumberAnimation {
- target: content
- property: "opacity"
- from: 1
- to: 0
- duration: Theme.notificationExitDuration
- easing.type: Easing.BezierSpline
- easing.bezierCurve: Theme.expressiveCurves.standardAccel
- }
-
- NumberAnimation {
- target: content
- property: "scale"
- from: 1
- to: 0.98
- duration: Theme.notificationExitDuration
- easing.type: Easing.BezierSpline
- easing.bezierCurve: Theme.expressiveCurves.emphasizedAccel
- }
- }
-
- Connections {
- id: wrapperConn
-
- function onPopupChanged() {
- if (!win.notificationData || win._isDestroying)
- return;
- if (!win.notificationData.popup && !win.exiting)
- startExit();
- }
-
- target: win.notificationData || null
- ignoreUnknownSignals: true
- enabled: !win._isDestroying
- }
-
- Connections {
- id: notificationConn
-
- function onDropped() {
- if (!win._isDestroying && !win.exiting)
- forceExit();
- }
-
- target: (win.notificationData && win.notificationData.notification && win.notificationData.notification.Retainable) || null
- ignoreUnknownSignals: true
- enabled: !win._isDestroying
- }
-
- Timer {
- id: enterDelay
-
- interval: 160
- repeat: false
- onTriggered: {
- if (notificationData && notificationData.timer && !exiting && !_isDestroying)
- notificationData.timer.start();
- }
- }
-
- Timer {
- id: exitWatchdog
-
- interval: 600
- repeat: false
- onTriggered: finalizeExit("watchdog")
- }
-
- Behavior on screenY {
- id: screenYAnim
-
- enabled: !exiting && !_isDestroying
-
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Easing.BezierSpline
- easing.bezierCurve: Theme.expressiveCurves.standardDecel
- }
- }
-
- Menu {
- id: popupContextMenu
- width: 220
- contentHeight: 130
- margins: -1
- popupType: Popup.Window
- closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
-
- background: Rectangle {
- color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
- radius: Theme.cornerRadius
- border.width: 0
- border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
- }
-
- MenuItem {
- id: setNotificationRulesItem
- text: I18n.tr("Set notification rules")
-
- contentItem: StyledText {
- text: parent.text
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- leftPadding: Theme.spacingS
- verticalAlignment: Text.AlignVCenter
- }
-
- background: Rectangle {
- color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
- radius: Theme.cornerRadius / 2
- }
-
- onTriggered: {
- const appName = notificationData?.appName || "";
- const desktopEntry = notificationData?.desktopEntry || "";
- SettingsData.addNotificationRuleForNotification(appName, desktopEntry);
- PopoutService.openSettingsWithTab("notifications");
- }
- }
-
- MenuItem {
- id: muteUnmuteItem
- readonly property bool isMuted: SettingsData.isAppMuted(notificationData?.appName || "", notificationData?.desktopEntry || "")
- text: isMuted ? I18n.tr("Unmute popups for %1").arg(notificationData?.appName || I18n.tr("this app")) : I18n.tr("Mute popups for %1").arg(notificationData?.appName || I18n.tr("this app"))
-
- contentItem: StyledText {
- text: parent.text
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- leftPadding: Theme.spacingS
- verticalAlignment: Text.AlignVCenter
- }
-
- background: Rectangle {
- color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
- radius: Theme.cornerRadius / 2
- }
-
- onTriggered: {
- const appName = notificationData?.appName || "";
- const desktopEntry = notificationData?.desktopEntry || "";
- if (isMuted) {
- SettingsData.removeMuteRuleForApp(appName, desktopEntry);
- } else {
- SettingsData.addMuteRuleForApp(appName, desktopEntry);
- if (notificationData && !exiting)
- NotificationService.dismissNotification(notificationData);
- }
- }
- }
-
- MenuItem {
- text: I18n.tr("Dismiss")
-
- contentItem: StyledText {
- text: parent.text
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceText
- leftPadding: Theme.spacingS
- verticalAlignment: Text.AlignVCenter
- }
-
- background: Rectangle {
- color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
- radius: Theme.cornerRadius / 2
- }
-
- onTriggered: {
- if (notificationData && !exiting)
- NotificationService.dismissNotification(notificationData);
- }
- }
- }
-}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml
deleted file mode 100644
index 02bfc0f..0000000
--- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml
+++ /dev/null
@@ -1,239 +0,0 @@
-import QtQuick
-import qs.Common
-import qs.Services
-
-QtObject {
- id: manager
-
- property var modelData
- property int topMargin: 0
- readonly property bool compactMode: SettingsData.notificationCompactMode
- readonly property real cardPadding: compactMode ? Theme.notificationCardPaddingCompact : Theme.notificationCardPadding
- readonly property real popupIconSize: compactMode ? Theme.notificationIconSizeCompact : Theme.notificationIconSizeNormal
- readonly property real actionButtonHeight: compactMode ? 20 : 24
- readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS
- readonly property real popupSpacing: compactMode ? 0 : Theme.spacingXS
- readonly property real collapsedContentHeight: Math.max(popupIconSize, Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2))
- readonly property int baseNotificationHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + contentSpacing + popupSpacing
- property var popupWindows: []
- property var destroyingWindows: new Set()
- property var pendingDestroys: []
- property int destroyDelayMs: 100
- property Component popupComponent
-
- popupComponent: Component {
- NotificationPopup {
- onExitFinished: manager._onPopupExitFinished(this)
- onPopupHeightChanged: manager._onPopupHeightChanged(this)
- }
- }
-
- property Connections notificationConnections
-
- notificationConnections: Connections {
- function onVisibleNotificationsChanged() {
- manager._sync(NotificationService.visibleNotifications);
- }
-
- target: NotificationService
- }
-
- property Timer sweeper
-
- property Timer destroyTimer: Timer {
- interval: destroyDelayMs
- running: false
- repeat: false
- onTriggered: manager._processDestroyQueue()
- }
-
- function _processDestroyQueue() {
- if (pendingDestroys.length === 0)
- return;
- const p = pendingDestroys.shift();
- if (p && p.destroy) {
- try {
- p.destroy();
- } catch (e) {}
- }
- if (pendingDestroys.length > 0)
- destroyTimer.restart();
- }
-
- function _scheduleDestroy(p) {
- if (!p)
- return;
- pendingDestroys.push(p);
- if (!destroyTimer.running)
- destroyTimer.restart();
- }
-
- sweeper: Timer {
- interval: 500
- running: false
- repeat: true
- onTriggered: {
- const toRemove = [];
- for (const p of popupWindows) {
- if (!p) {
- toRemove.push(p);
- continue;
- }
- const isZombie = p.status === Component.Null || (!p.visible && !p.exiting) || (!p.notificationData && !p._isDestroying) || (!p.hasValidData && !p._isDestroying);
- if (isZombie) {
- toRemove.push(p);
- if (p.forceExit) {
- p.forceExit();
- } else if (p.destroy) {
- try {
- p.destroy();
- } catch (e) {}
- }
- }
- }
- if (toRemove.length) {
- popupWindows = popupWindows.filter(p => toRemove.indexOf(p) === -1);
- _repositionAll();
- }
- if (popupWindows.length === 0)
- sweeper.stop();
- }
- }
-
- function _hasWindowFor(w) {
- return popupWindows.some(p => p && p.notificationData === w && !p._isDestroying && p.status !== Component.Null);
- }
-
- function _isValidWindow(p) {
- return p && p.status !== Component.Null && !p._isDestroying && p.hasValidData;
- }
-
- function _isFocusedScreen() {
- if (!SettingsData.notificationFocusedMonitor)
- return true;
- const focused = CompositorService.getFocusedScreen();
- return focused && manager.modelData && focused.name === manager.modelData.name;
- }
-
- function _sync(newWrappers) {
- for (const p of popupWindows.slice()) {
- if (!_isValidWindow(p) || p.exiting)
- continue;
- if (p.notificationData && newWrappers.indexOf(p.notificationData) === -1) {
- p.notificationData.removedByLimit = true;
- p.notificationData.popup = false;
- }
- }
- for (const w of newWrappers) {
- if (w && !_hasWindowFor(w) && _isFocusedScreen())
- _insertAtTop(w);
- }
- }
-
- function _popupHeight(p) {
- return (p.alignedHeight || p.implicitHeight || (baseNotificationHeight - popupSpacing)) + popupSpacing;
- }
-
- function _insertAtTop(wrapper) {
- if (!wrapper)
- return;
- const notificationId = wrapper?.notification ? wrapper.notification.id : "";
- const win = popupComponent.createObject(null, {
- "notificationData": wrapper,
- "notificationId": notificationId,
- "screenY": topMargin,
- "screen": manager.modelData
- });
- if (!win)
- return;
- if (!win.hasValidData) {
- win.destroy();
- return;
- }
- popupWindows.unshift(win);
- _repositionAll();
- if (!sweeper.running)
- sweeper.start();
- }
-
- function _repositionAll() {
- const active = popupWindows.filter(p => _isValidWindow(p) && p.notificationData?.popup && !p.exiting);
-
- const pinnedSlots = [];
- for (const p of active) {
- if (!p.hovered)
- continue;
- pinnedSlots.push({
- y: p.screenY,
- end: p.screenY + _popupHeight(p)
- });
- }
- pinnedSlots.sort((a, b) => a.y - b.y);
-
- let currentY = topMargin;
- for (const win of active) {
- if (win.hovered)
- continue;
- for (const slot of pinnedSlots) {
- if (currentY >= slot.y - 1 && currentY < slot.end)
- currentY = slot.end;
- }
- win.screenY = currentY;
- currentY += _popupHeight(win);
- }
- }
-
- function _onPopupHeightChanged(p) {
- if (!p || p.exiting || p._isDestroying)
- return;
- if (popupWindows.indexOf(p) === -1)
- return;
- _repositionAll();
- }
-
- function _onPopupExitFinished(p) {
- if (!p)
- return;
- const windowId = p.toString();
- if (destroyingWindows.has(windowId))
- return;
- destroyingWindows.add(windowId);
- const i = popupWindows.indexOf(p);
- if (i !== -1) {
- popupWindows.splice(i, 1);
- popupWindows = popupWindows.slice();
- }
- if (NotificationService.releaseWrapper && p.notificationData)
- NotificationService.releaseWrapper(p.notificationData);
- _scheduleDestroy(p);
- Qt.callLater(() => destroyingWindows.delete(windowId));
- _repositionAll();
- }
-
- function cleanupAllWindows() {
- sweeper.stop();
- destroyTimer.stop();
- pendingDestroys = [];
- for (const p of popupWindows.slice()) {
- if (p) {
- try {
- if (p.forceExit) {
- p.forceExit();
- } else if (p.destroy) {
- p.destroy();
- }
- } catch (e) {}
- }
- }
- popupWindows = [];
- destroyingWindows.clear();
- }
-
- onPopupWindowsChanged: {
- if (popupWindows.length > 0 && !sweeper.running) {
- sweeper.start();
- } else if (popupWindows.length === 0 && sweeper.running) {
- sweeper.stop();
- }
- }
-}