From 4e5046eb9199c7fd27684fa10ac93a2c3077ff54 Mon Sep 17 00:00:00 2001 From: Olaf Luijks Date: Fri, 28 Nov 2025 09:48:22 +0100 Subject: [PATCH 1/3] feat(notifications): add date-range tabs to history panel --- Assets/Translations/de.json | 8 +- Assets/Translations/en.json | 8 +- Assets/Translations/es.json | 8 +- Assets/Translations/fr.json | 8 +- Assets/Translations/ja.json | 8 +- Assets/Translations/nl.json | 8 +- Assets/Translations/pt.json | 8 +- Assets/Translations/ru.json | 8 +- Assets/Translations/tr.json | 8 +- Assets/Translations/uk-UA.json | 8 +- Assets/Translations/zh-CN.json | 8 +- .../NotificationHistoryPanel.qml | 122 +++++++++++++++++- 12 files changed, 198 insertions(+), 12 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index e3d687e8..d29ab94b 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -504,6 +504,12 @@ "no-notifications": "Keine Benachrichtigungen", "title": "Benachrichtigungen" }, + "range": { + "all": "Alle", + "earlier": "Älter", + "today": "Heute", + "yesterday": "Gestern" + }, "time": { "diffD": "vor 1 Tag", "diffDD": "vor {diff} Tagen", @@ -2417,4 +2423,4 @@ "title": "WLAN" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 0c42d1e2..a21a953a 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -504,6 +504,12 @@ "no-notifications": "No notifications", "title": "Notifications" }, + "range": { + "all": "All", + "earlier": "Earlier", + "today": "Today", + "yesterday": "Yesterday" + }, "time": { "diffD": "1 day ago", "diffDD": "{diff} days ago", @@ -2417,4 +2423,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index fd7d1e40..c703d2b5 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -504,6 +504,12 @@ "no-notifications": "No hay notificaciones", "title": "Notificaciones" }, + "range": { + "all": "Todas", + "earlier": "Anteriores", + "today": "Hoy", + "yesterday": "Ayer" + }, "time": { "diffD": "hace 1 día", "diffDD": "hace {diff} días", @@ -2417,4 +2423,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 61e1ce48..35e0074d 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -504,6 +504,12 @@ "no-notifications": "Aucune notification", "title": "Notifications" }, + "range": { + "all": "Tout", + "earlier": "Plus anciennes", + "today": "Aujourd'hui", + "yesterday": "Hier" + }, "time": { "diffD": "il y a 1 jour", "diffDD": "il y a {diff} jours", @@ -2417,4 +2423,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/ja.json b/Assets/Translations/ja.json index dea9eec1..a06fc8c8 100644 --- a/Assets/Translations/ja.json +++ b/Assets/Translations/ja.json @@ -504,6 +504,12 @@ "no-notifications": "通知なし", "title": "通知" }, + "range": { + "all": "すべて", + "earlier": "以前", + "today": "今日", + "yesterday": "昨日" + }, "time": { "diffD": "1日前", "diffDD": "{diff}日前", @@ -2417,4 +2423,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/nl.json b/Assets/Translations/nl.json index a5057133..4ef639bc 100644 --- a/Assets/Translations/nl.json +++ b/Assets/Translations/nl.json @@ -504,6 +504,12 @@ "no-notifications": "Geen meldingen", "title": "Meldingen" }, + "range": { + "all": "Alle", + "earlier": "Eerder", + "today": "Vandaag", + "yesterday": "Gisteren" + }, "time": { "diffD": "1 dag geleden", "diffDD": "{diff} dagen geleden", @@ -2417,4 +2423,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index b74cdcbe..b6ae0e10 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -504,6 +504,12 @@ "no-notifications": "Nenhuma notificação", "title": "Notificações" }, + "range": { + "all": "Todas", + "earlier": "Mais antigas", + "today": "Hoje", + "yesterday": "Ontem" + }, "time": { "diffD": "há 1 dia", "diffDD": "há {diff} dias", @@ -2417,4 +2423,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/ru.json b/Assets/Translations/ru.json index 966a6646..26950f66 100644 --- a/Assets/Translations/ru.json +++ b/Assets/Translations/ru.json @@ -504,6 +504,12 @@ "no-notifications": "Нет уведомлений", "title": "Уведомления" }, + "range": { + "all": "Все", + "earlier": "Ранее", + "today": "Сегодня", + "yesterday": "Вчера" + }, "time": { "diffD": "1 день назад", "diffDD": "{diff} дней назад", @@ -2417,4 +2423,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 1975ea79..de448523 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -504,6 +504,12 @@ "no-notifications": "Bildirim yok", "title": "Bildirimler" }, + "range": { + "all": "Tümü", + "earlier": "Öncekiler", + "today": "Bugün", + "yesterday": "Dün" + }, "time": { "diffD": "1 gün önce", "diffDD": "{diff} gün önce", @@ -2417,4 +2423,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index 9c035c9a..72855b04 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -504,6 +504,12 @@ "no-notifications": "Немає сповіщень", "title": "Сповіщення" }, + "range": { + "all": "Усі", + "earlier": "Раніше", + "today": "Сьогодні", + "yesterday": "Учора" + }, "time": { "diffD": "1 день тому", "diffDD": "{diff} днів тому", @@ -2417,4 +2423,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 44f18867..d8aada4d 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -504,6 +504,12 @@ "no-notifications": "无通知", "title": "通知" }, + "range": { + "all": "全部", + "earlier": "更早", + "today": "今天", + "yesterday": "昨天" + }, "time": { "diffD": "1 天前", "diffDD": "{diff} 天前", @@ -2417,4 +2423,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml index 4e25642c..7feec0d9 100644 --- a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml +++ b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml @@ -13,6 +13,61 @@ import qs.Widgets SmartPanel { id: root + // 0 = All, 1 = Today, 2 = Yesterday, 3 = Earlier + property int currentRange: 1 // start on Today by default + property var rangeCounts: [0, 0, 0, 0] + + function dateOnly(d) { + return new Date(d.getFullYear(), d.getMonth(), d.getDate()); + } + + function rangeForTimestamp(ts) { + var dt = new Date(ts); + var today = dateOnly(new Date()); + var thatDay = dateOnly(dt); + + var diffMs = today - thatDay; + var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) + return 0; + if (diffDays === 1) + return 1; + return 2; + } + + function isInCurrentRange(ts) { + if (currentRange === 0) + return true; + return rangeForTimestamp(ts) === (currentRange - 1); + } + + function recalcRangeCounts() { + var m = NotificationService.historyList; + var counts = [0, 0, 0, 0]; + + counts[0] = m.count; + + for (var i = 0; i < m.count; ++i) { + var item = m.get(i); + var r = rangeForTimestamp(item.timestamp); + counts[r + 1] = counts[r + 1] + 1; + } + + rangeCounts = counts; + } + + function countForRange(range) { + return rangeCounts[range] || 0; + } + + Connections { + target: NotificationService.historyList + function onCountChanged() { recalcRangeCounts(); } + } + + Component.onCompleted: recalcRangeCounts() + preferredWidth: Math.round(420 * Style.uiScaleRatio) preferredHeight: Math.round(540 * Style.uiScaleRatio) @@ -82,6 +137,66 @@ SmartPanel { } } + // Time range tabs ([All] / [Today] / [Yesterday] / [Earlier]) + NBox { + Layout.fillWidth: true + Layout.topMargin: Style.marginS + implicitHeight: timeTabs.implicitHeight + (Style.marginS * 2) + visible: NotificationService.historyList.count > 0 && root.groupByDate + + RowLayout { + id: timeTabs + spacing: Style.marginXS + anchors.fill: parent + anchors.margins: Style.marginS + visible: NotificationService.historyList.count > 0 + + Repeater { + model: 4 + + delegate: NButton { + readonly property int rangeId: index + readonly property bool isActive: root.currentRange === rangeId + + text: { + if (rangeId === 0) + return I18n.tr("notifications.range.all") + + " (" + root.countForRange(rangeId) + ")"; + else if (rangeId === 1) + return I18n.tr("notifications.range.today") + + " (" + root.countForRange(rangeId) + ")"; + else if (rangeId === 2) + return I18n.tr("notifications.range.yesterday") + + " (" + root.countForRange(rangeId) + ")"; + return I18n.tr("notifications.range.earlier") + + " (" + root.countForRange(rangeId) + ")"; + } + + Layout.fillWidth: true + Layout.preferredWidth: 1 + implicitHeight: Style.baseWidgetSize * 0.7 + fontSize: Style.fontSizeXS + outlined: false + + backgroundColor: isActive + ? Color.mPrimary + : (hovered ? Color.mHover : Color.transparent) + textColor: isActive + ? Color.mOnPrimary + : (hovered ? Color.mOnHover : Color.mOnSurface) + hoverColor: backgroundColor + + Behavior on backgroundColor { + enabled: !Settings.data.general.animationDisabled + ColorAnimation { duration: Style.animationFast } + } + + onClicked: root.currentRange = rangeId + } + } + } + } + // Empty state when no notifications ColumnLayout { Layout.fillWidth: true @@ -148,7 +263,12 @@ SmartPanel { delegate: Item { id: notificationDelegate width: parent.width - height: contentColumn.height + (Style.marginM * 2) + // height: contentColumn.height + (Style.marginM * 2) + + // --- NEW: show only notifications in the active range --- + visible: root.isInCurrentRange(model.timestamp) + height: visible ? contentColumn.height + (Style.marginM * 2) : 0 + // --- end NEW --- property string notificationId: model.id property bool isExpanded: scrollView.expandedId === notificationId From aa892fceabeeef4fd56e1727310875f8d734c11e Mon Sep 17 00:00:00 2001 From: Olaf Luijks Date: Fri, 28 Nov 2025 09:55:07 +0100 Subject: [PATCH 2/3] fix(notifications): harden history date tabs for empty lists --- .../NotificationHistoryPanel.qml | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml index 7feec0d9..1ab4497f 100644 --- a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml +++ b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml @@ -16,6 +16,7 @@ SmartPanel { // 0 = All, 1 = Today, 2 = Yesterday, 3 = Earlier property int currentRange: 1 // start on Today by default property var rangeCounts: [0, 0, 0, 0] + property bool groupByDate: true function dateOnly(d) { return new Date(d.getFullYear(), d.getMonth(), d.getDate()); @@ -44,14 +45,21 @@ SmartPanel { function recalcRangeCounts() { var m = NotificationService.historyList; + if (!m || typeof m.count === "undefined" || m.count <= 0) { + rangeCounts = [0, 0, 0, 0]; + return; + } + var counts = [0, 0, 0, 0]; counts[0] = m.count; for (var i = 0; i < m.count; ++i) { var item = m.get(i); - var r = rangeForTimestamp(item.timestamp); - counts[r + 1] = counts[r + 1] + 1; + if (!item || typeof item.timestamp === "undefined") + continue; + var r = rangeForTimestamp(item.timestamp); // 0..2 + counts[r + 1] = counts[r + 1] + 1; // shift by +1 } rangeCounts = counts; @@ -161,15 +169,15 @@ SmartPanel { text: { if (rangeId === 0) return I18n.tr("notifications.range.all") + - " (" + root.countForRange(rangeId) + ")"; + " (" + root.countForRange(rangeId) + ")"; else if (rangeId === 1) return I18n.tr("notifications.range.today") + - " (" + root.countForRange(rangeId) + ")"; + " (" + root.countForRange(rangeId) + ")"; else if (rangeId === 2) return I18n.tr("notifications.range.yesterday") + - " (" + root.countForRange(rangeId) + ")"; + " (" + root.countForRange(rangeId) + ")"; return I18n.tr("notifications.range.earlier") + - " (" + root.countForRange(rangeId) + ")"; + " (" + root.countForRange(rangeId) + ")"; } Layout.fillWidth: true @@ -179,11 +187,11 @@ SmartPanel { outlined: false backgroundColor: isActive - ? Color.mPrimary - : (hovered ? Color.mHover : Color.transparent) + ? Color.mPrimary + : (hovered ? Color.mHover : Color.transparent) textColor: isActive - ? Color.mOnPrimary - : (hovered ? Color.mOnHover : Color.mOnSurface) + ? Color.mOnPrimary + : (hovered ? Color.mOnHover : Color.mOnSurface) hoverColor: backgroundColor Behavior on backgroundColor { @@ -197,6 +205,7 @@ SmartPanel { } } + // Empty state when no notifications ColumnLayout { Layout.fillWidth: true From f6080b9aa77b64b242e3a106a002d994c9778e80 Mon Sep 17 00:00:00 2001 From: Olaf Luijks Date: Fri, 28 Nov 2025 10:07:07 +0100 Subject: [PATCH 3/3] chore: remove silly comments --- .../NotificationHistory/NotificationHistoryPanel.qml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml index 1ab4497f..5899ac6f 100644 --- a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml +++ b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml @@ -58,8 +58,8 @@ SmartPanel { var item = m.get(i); if (!item || typeof item.timestamp === "undefined") continue; - var r = rangeForTimestamp(item.timestamp); // 0..2 - counts[r + 1] = counts[r + 1] + 1; // shift by +1 + var r = rangeForTimestamp(item.timestamp); + counts[r + 1] = counts[r + 1] + 1; } rangeCounts = counts; @@ -205,7 +205,6 @@ SmartPanel { } } - // Empty state when no notifications ColumnLayout { Layout.fillWidth: true @@ -272,12 +271,8 @@ SmartPanel { delegate: Item { id: notificationDelegate width: parent.width - // height: contentColumn.height + (Style.marginM * 2) - - // --- NEW: show only notifications in the active range --- visible: root.isInCurrentRange(model.timestamp) height: visible ? contentColumn.height + (Style.marginM * 2) : 0 - // --- end NEW --- property string notificationId: model.id property bool isExpanded: scrollView.expandedId === notificationId