mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-22 03:04:01 -04:00
366 lines
11 KiB
QML
366 lines
11 KiB
QML
import QtQuick
|
|
import qs.Modules.Notifications
|
|
import Quickshell
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.DCommon.Widgets
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property string selectedFilterKey: "all"
|
|
property var keyboardController: null
|
|
property bool keyboardActive: false
|
|
property bool focusAllowed: true
|
|
property int selectedIndex: -1
|
|
property bool showKeyboardHints: false
|
|
property bool nested: false
|
|
property real swipeBleed: 0
|
|
property alias showScrollBar: historyListView.showScrollBar
|
|
|
|
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.duration(7 * 86400),
|
|
key: "7d",
|
|
maxDays: 7
|
|
},
|
|
{
|
|
label: I18n.duration(30 * 86400),
|
|
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);
|
|
layoutTimer.restart();
|
|
}
|
|
|
|
Timer {
|
|
id: layoutTimer
|
|
interval: 0
|
|
onTriggered: historyListView.forceLayout()
|
|
}
|
|
|
|
Column {
|
|
anchors.fill: parent
|
|
spacing: Theme.spacingS
|
|
|
|
DFilterChips {
|
|
id: filterChips
|
|
width: parent.width
|
|
visible: root.visibleFilters.length > 1
|
|
currentIndex: root.getChipIndex()
|
|
showCounts: true
|
|
model: root.visibleFilters
|
|
onSelectionChanged: index => {
|
|
if (index >= 0 && index < root.visibleFilters.length) {
|
|
root.selectedFilterKey = root.visibleFilters[index].key;
|
|
}
|
|
}
|
|
}
|
|
|
|
DListView {
|
|
id: historyListView
|
|
x: -root.swipeBleed
|
|
width: parent.width + root.swipeBleed * 2
|
|
height: parent.height - (filterChips.visible ? filterChips.height + Theme.spacingS : 0)
|
|
clip: true
|
|
leftMargin: root.swipeBleed
|
|
rightMargin: root.swipeBleed
|
|
spacing: Theme.groupedListGap
|
|
rowFadeEnabled: NotificationMetrics.animationsEnabled
|
|
|
|
model: ScriptModel {
|
|
id: historyModel
|
|
values: root.getFilteredHistory()
|
|
objectProp: "id"
|
|
}
|
|
|
|
NotificationEmptyState {
|
|
parent: historyListView
|
|
visible: historyListView.count === 0
|
|
}
|
|
|
|
NotificationSwipeGroup {
|
|
id: historySwipe
|
|
}
|
|
|
|
delegate: NotificationSwipeRow {
|
|
id: delegateRoot
|
|
required property var modelData
|
|
|
|
group: historySwipe
|
|
bleed: root.swipeBleed
|
|
width: ListView.view.width - root.swipeBleed * 2
|
|
height: historyCard.height
|
|
onDismissed: root.removeWithScrollPreserve(modelData?.id || "")
|
|
|
|
Component.onCompleted: historyListView.fadeIn(delegateRoot, index)
|
|
|
|
HistoryNotificationCard {
|
|
id: historyCard
|
|
width: parent.width
|
|
x: delegateRoot.offset
|
|
historyItem: modelData
|
|
nested: root.nested
|
|
firstInGroup: index === 0
|
|
lastInGroup: index === historyListView.count - 1
|
|
topRoundness: delegateRoot.topRoundness
|
|
bottomRoundness: delegateRoot.bottomRoundness
|
|
contentOpacity: delegateRoot.contentOpacity
|
|
isSelected: root.keyboardActive && root.focusAllowed && root.selectedIndex === index
|
|
keyboardNavigationActive: root.keyboardActive && root.focusAllowed
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function selectNext() {
|
|
if (historyModel.values.length === 0)
|
|
return;
|
|
keyboardActive = true;
|
|
selectedIndex = Math.min(selectedIndex + 1, historyModel.values.length - 1);
|
|
historyListView.revealIndex(selectedIndex);
|
|
}
|
|
|
|
function selectPrevious() {
|
|
if (historyModel.values.length === 0)
|
|
return;
|
|
if (selectedIndex <= 0) {
|
|
keyboardActive = false;
|
|
selectedIndex = -1;
|
|
return;
|
|
}
|
|
selectedIndex = Math.max(selectedIndex - 1, 0);
|
|
historyListView.revealIndex(selectedIndex);
|
|
}
|
|
|
|
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) {
|
|
const dismissKey = event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace;
|
|
if (dismissKey && (event.modifiers & Qt.ShiftModifier)) {
|
|
NotificationService.clearHistory();
|
|
keyboardActive = false;
|
|
selectedIndex = -1;
|
|
event.accepted = true;
|
|
return;
|
|
}
|
|
switch (event.key) {
|
|
case Qt.Key_Down:
|
|
if (!keyboardActive) {
|
|
keyboardActive = true;
|
|
selectedIndex = 0;
|
|
event.accepted = true;
|
|
return;
|
|
}
|
|
selectNext();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Up:
|
|
if (keyboardActive)
|
|
selectPrevious();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Delete:
|
|
case Qt.Key_Backspace:
|
|
if (!keyboardActive)
|
|
return;
|
|
clearSelected();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_F10:
|
|
showKeyboardHints = !showKeyboardHints;
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
}
|