1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-25 14:02:53 -05:00
Files
DankMaterialShell/quickshell/Modules/Notifications/Center/HistoryNotificationList.qml
2026-01-12 11:08:22 -05:00

373 lines
12 KiB
QML

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
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
width: ListView.view.width
height: historyCard.height
clip: true
HistoryNotificationCard {
id: historyCard
width: parent.width
x: 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
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
}
}
}
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;
}
}
}