mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
motifications: add support for configurable persistent history
fixes #929
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
import QtQuick
|
||||
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
|
||||
|
||||
width: parent ? parent.width : 400
|
||||
height: 116
|
||||
radius: Theme.cornerRadius
|
||||
clip: true
|
||||
|
||||
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 1;
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
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 {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 12
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 56
|
||||
height: 92
|
||||
|
||||
DankCircularImage {
|
||||
id: iconContainer
|
||||
readonly property bool hasNotificationImage: historyItem.image && historyItem.image !== ""
|
||||
|
||||
width: 63
|
||||
height: 63
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 14
|
||||
|
||||
imageSource: {
|
||||
if (hasNotificationImage)
|
||||
return historyItem.image;
|
||||
if (historyItem.appIcon) {
|
||||
const appIcon = historyItem.appIcon;
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
|
||||
return appIcon;
|
||||
return Quickshell.iconPath(appIcon, true);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
hasImage: hasNotificationImage
|
||||
fallbackIcon: ""
|
||||
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: 12
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 8
|
||||
color: "transparent"
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -2
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: {
|
||||
const timeStr = NotificationService.formatHistoryTime(historyItem.timestamp);
|
||||
const appName = historyItem.appName || "";
|
||||
return timeStr.length > 0 ? `${appName} • ${timeStr}` : appName;
|
||||
}
|
||||
color: Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: historyItem.summary || ""
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: descriptionText
|
||||
text: historyItem.htmlBody || historyItem.body || ""
|
||||
color: Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.WordWrap
|
||||
visible: text.length > 0
|
||||
linkColor: Theme.primary
|
||||
onLinkActivated: link => Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 12
|
||||
anchors.rightMargin: 16
|
||||
iconName: "close"
|
||||
iconSize: 18
|
||||
buttonSize: 28
|
||||
onClicked: NotificationService.removeFromHistory(historyItem.id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
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() {
|
||||
}
|
||||
|
||||
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: HistoryNotificationCard {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: ListView.view.width
|
||||
historyItem: modelData
|
||||
isSelected: root.keyboardActive && root.selectedIndex === index
|
||||
keyboardNavigationActive: root.keyboardActive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Notifications.Center
|
||||
|
||||
DankPopout {
|
||||
id: root
|
||||
@@ -112,8 +111,11 @@ DankPopout {
|
||||
baseHeight += Theme.spacingM * 2;
|
||||
|
||||
const settingsHeight = notificationSettings.expanded ? notificationSettings.contentHeight : 0;
|
||||
let listHeight = notificationList.listContentHeight;
|
||||
if (NotificationService.groupedNotifications.length === 0) {
|
||||
let listHeight = notificationHeader.currentTab === 0 ? notificationList.listContentHeight : 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;
|
||||
}
|
||||
|
||||
@@ -143,7 +145,13 @@ DankPopout {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
notificationHistoryVisible = false;
|
||||
event.accepted = true;
|
||||
} else if (externalKeyboardController) {
|
||||
return;
|
||||
}
|
||||
if (notificationHeader.currentTab === 1) {
|
||||
historyList.handleKey(event);
|
||||
return;
|
||||
}
|
||||
if (externalKeyboardController) {
|
||||
externalKeyboardController.handleKey(event);
|
||||
}
|
||||
}
|
||||
@@ -187,7 +195,14 @@ DankPopout {
|
||||
KeyboardNavigatedNotificationList {
|
||||
id: notificationList
|
||||
objectName: "notificationList"
|
||||
visible: notificationHeader.currentTab === 0
|
||||
width: parent.width
|
||||
height: parent.height - notificationContent.cachedHeaderHeight - notificationSettings.height - contentColumnInner.spacing * 2
|
||||
}
|
||||
|
||||
HistoryNotificationList {
|
||||
id: historyList
|
||||
visible: notificationHeader.currentTab === 1
|
||||
width: parent.width
|
||||
height: parent.height - notificationContent.cachedHeaderHeight - notificationSettings.height - contentColumnInner.spacing * 2
|
||||
}
|
||||
@@ -200,7 +215,7 @@ DankPopout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingL
|
||||
showHints: (externalKeyboardController && externalKeyboardController.showKeyboardHints) || false
|
||||
showHints: notificationHeader.currentTab === 0 ? (externalKeyboardController && externalKeyboardController.showKeyboardHints) || false : historyList.showKeyboardHints
|
||||
z: 200
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,108 +8,150 @@ Item {
|
||||
|
||||
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: 32
|
||||
height: headerColumn.implicitHeight
|
||||
|
||||
DankTooltipV2 {
|
||||
id: sharedTooltip
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
Column {
|
||||
id: headerColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
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: 28
|
||||
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 {
|
||||
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: 28
|
||||
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: 28
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: root.showSettings = !root.showSettings
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: clearAllButton
|
||||
|
||||
width: 120
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
visible: NotificationService.notifications.length > 0
|
||||
color: clearArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Math.max(titleRow.implicitHeight, actionsRow.implicitHeight)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
id: titleRow
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
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
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clearArea
|
||||
Row {
|
||||
id: actionsRow
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationService.clearAllNotifications()
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
@@ -36,64 +34,77 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var timeoutOptions: [{
|
||||
readonly property var timeoutOptions: [
|
||||
{
|
||||
"text": "Never",
|
||||
"value": 0
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "1 second",
|
||||
"value": 1000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "3 seconds",
|
||||
"value": 3000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "5 seconds",
|
||||
"value": 5000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "8 seconds",
|
||||
"value": 8000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "10 seconds",
|
||||
"value": 10000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "15 seconds",
|
||||
"value": 15000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "30 seconds",
|
||||
"value": 30000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "1 minute",
|
||||
"value": 60000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "2 minutes",
|
||||
"value": 120000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "5 minutes",
|
||||
"value": 300000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "10 minutes",
|
||||
"value": 600000
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
||||
function getTimeoutText(value) {
|
||||
if (value === undefined || value === null || isNaN(value)) {
|
||||
return "5 seconds"
|
||||
return "5 seconds";
|
||||
}
|
||||
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].value === value) {
|
||||
return timeoutOptions[i].text
|
||||
return timeoutOptions[i].text;
|
||||
}
|
||||
}
|
||||
if (value === 0) {
|
||||
return "Never"
|
||||
return "Never";
|
||||
}
|
||||
if (value < 1000) {
|
||||
return value + "ms"
|
||||
return value + "ms";
|
||||
}
|
||||
if (value < 60000) {
|
||||
return Math.round(value / 1000) + " seconds"
|
||||
return Math.round(value / 1000) + " seconds";
|
||||
}
|
||||
return Math.round(value / 60000) + " minutes"
|
||||
return Math.round(value / 60000) + " minutes";
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -113,9 +124,10 @@ Rectangle {
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 36
|
||||
height: Math.max(dndRow.implicitHeight, dndToggle.implicitHeight) + Theme.spacingS
|
||||
|
||||
Row {
|
||||
id: dndRow
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
@@ -136,6 +148,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: dndToggle
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SessionData.doNotDisturb
|
||||
@@ -162,13 +175,13 @@ Rectangle {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.set("notificationTimeoutLow", timeoutOptions[i].value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
@@ -177,13 +190,13 @@ Rectangle {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.set("notificationTimeoutNormal", timeoutOptions[i].value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
@@ -192,13 +205,13 @@ Rectangle {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.set("notificationTimeoutCritical", timeoutOptions[i].value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -209,9 +222,10 @@ Rectangle {
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 36
|
||||
height: Math.max(overlayRow.implicitHeight, overlayToggle.implicitHeight) + Theme.spacingS
|
||||
|
||||
Row {
|
||||
id: overlayRow
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
@@ -242,11 +256,127 @@ Rectangle {
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: overlayToggle
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.notificationOverlayEnabled
|
||||
onToggled: toggled => SettingsData.set("notificationOverlayEnabled", 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user