mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-17 19:22:04 -04:00
notifications: add configurable durations for do not disturb
fixes #1481
This commit is contained in:
250
quickshell/Modules/Notifications/Center/DndDurationMenu.qml
Normal file
250
quickshell/Modules/Notifications/Center/DndDurationMenu.qml
Normal file
@@ -0,0 +1,250 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
signal dismissed
|
||||
|
||||
readonly property bool currentlyActive: SessionData.doNotDisturb
|
||||
readonly property real currentRemainingMs: SessionData.doNotDisturbUntil > 0 ? Math.max(0, SessionData.doNotDisturbUntil - nowMs) : 0
|
||||
property real nowMs: Date.now()
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: root.visible && root.currentlyActive && SessionData.doNotDisturbUntil > 0
|
||||
onTriggered: root.nowMs = Date.now()
|
||||
}
|
||||
|
||||
function _pad2(n) {
|
||||
return n < 10 ? "0" + n : "" + n;
|
||||
}
|
||||
|
||||
function formatRemaining(ms) {
|
||||
if (ms <= 0)
|
||||
return I18n.tr("Off");
|
||||
const totalMinutes = Math.ceil(ms / 60000);
|
||||
if (totalMinutes < 60)
|
||||
return I18n.tr("%1 min left").arg(totalMinutes);
|
||||
const hours = Math.floor(totalMinutes / 60);
|
||||
const mins = totalMinutes - hours * 60;
|
||||
if (mins === 0)
|
||||
return I18n.tr("%1 h left").arg(hours);
|
||||
return I18n.tr("%1 h %2 m left").arg(hours).arg(mins);
|
||||
}
|
||||
|
||||
function formatUntilTimestamp(ts) {
|
||||
if (!ts)
|
||||
return "";
|
||||
const d = new Date(ts);
|
||||
const hours = d.getHours();
|
||||
const minutes = d.getMinutes();
|
||||
const use24h = (typeof SettingsData !== "undefined") ? SettingsData.use24HourClock : true;
|
||||
if (use24h) {
|
||||
return _pad2(hours) + ":" + _pad2(minutes);
|
||||
}
|
||||
const suffix = hours >= 12 ? "PM" : "AM";
|
||||
const h12 = ((hours + 11) % 12) + 1;
|
||||
return h12 + ":" + _pad2(minutes) + " " + suffix;
|
||||
}
|
||||
|
||||
function minutesUntilTomorrowMorning() {
|
||||
const now = new Date();
|
||||
const target = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 8, 0, 0, 0);
|
||||
return Math.max(1, Math.round((target.getTime() - now.getTime()) / 60000));
|
||||
}
|
||||
|
||||
readonly property var presetOptions: [
|
||||
{
|
||||
"label": I18n.tr("For 15 minutes"),
|
||||
"minutes": 15
|
||||
},
|
||||
{
|
||||
"label": I18n.tr("For 30 minutes"),
|
||||
"minutes": 30
|
||||
},
|
||||
{
|
||||
"label": I18n.tr("For 1 hour"),
|
||||
"minutes": 60
|
||||
},
|
||||
{
|
||||
"label": I18n.tr("For 3 hours"),
|
||||
"minutes": 180
|
||||
},
|
||||
{
|
||||
"label": I18n.tr("For 8 hours"),
|
||||
"minutes": 480
|
||||
},
|
||||
{
|
||||
"label": I18n.tr("Until tomorrow, 8:00 AM"),
|
||||
"minutesFn": true
|
||||
},
|
||||
{
|
||||
"label": I18n.tr("Until I turn it off"),
|
||||
"minutes": 0
|
||||
}
|
||||
]
|
||||
|
||||
function selectPreset(option) {
|
||||
let minutes = option.minutes;
|
||||
if (option.minutesFn) {
|
||||
minutes = minutesUntilTomorrowMorning();
|
||||
}
|
||||
SessionData.setDoNotDisturb(true, minutes);
|
||||
root.dismissed();
|
||||
}
|
||||
|
||||
function turnOff() {
|
||||
SessionData.setDoNotDisturb(false);
|
||||
root.dismissed();
|
||||
}
|
||||
|
||||
implicitWidth: Math.max(220, menuColumn.implicitWidth + Theme.spacingM * 2)
|
||||
implicitHeight: menuColumn.implicitHeight + Theme.spacingM * 2
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: BlurService.enabled ? BlurService.borderWidth : 1
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: SessionData.doNotDisturb ? "notifications_off" : "notifications_paused"
|
||||
size: Theme.iconSize - 2
|
||||
color: SessionData.doNotDisturb ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - parent.spacing
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Do Not Disturb")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: root.currentlyActive
|
||||
text: {
|
||||
if (SessionData.doNotDisturbUntil > 0) {
|
||||
return root.formatRemaining(root.currentRemainingMs) + " · " + I18n.tr("until %1").arg(root.formatUntilTimestamp(SessionData.doNotDisturbUntil));
|
||||
}
|
||||
return I18n.tr("On indefinitely");
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.15)
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.presetOptions
|
||||
|
||||
Rectangle {
|
||||
id: optionRect
|
||||
required property var modelData
|
||||
width: menuColumn.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: optionArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: optionRect.modelData.label
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: optionArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.selectPreset(optionRect.modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: root.currentlyActive
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.15)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: root.currentlyActive
|
||||
width: menuColumn.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: offArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.14) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: "notifications_active"
|
||||
size: Theme.iconSizeSmall
|
||||
color: offArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("Turn off now")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: offArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: offArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.turnOff()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
88
quickshell/Modules/Notifications/Center/DndDurationPopup.qml
Normal file
88
quickshell/Modules/Notifications/Center/DndDurationPopup.qml
Normal file
@@ -0,0 +1,88 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
WindowBlur {
|
||||
targetWindow: root
|
||||
blurX: menu.x
|
||||
blurY: menu.y
|
||||
blurWidth: root.visible ? menu.width : 0
|
||||
blurHeight: root.visible ? menu.height : 0
|
||||
blurRadius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
WlrLayershell.namespace: "dms:dnd-duration-menu"
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
property point anchorPos: Qt.point(0, 0)
|
||||
property string anchorEdge: "top"
|
||||
visible: false
|
||||
|
||||
function showAt(x, y, targetScreen, edge) {
|
||||
if (targetScreen)
|
||||
root.screen = targetScreen;
|
||||
anchorPos = Qt.point(x, y);
|
||||
anchorEdge = edge || "top";
|
||||
visible = true;
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PopoutManager
|
||||
function onPopoutOpening() {
|
||||
root.closeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: 0
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: root.closeMenu()
|
||||
}
|
||||
|
||||
DndDurationMenu {
|
||||
id: menu
|
||||
z: 1
|
||||
visible: root.visible
|
||||
|
||||
x: {
|
||||
const left = 10;
|
||||
const right = root.width - width - 10;
|
||||
const want = root.anchorPos.x - width / 2;
|
||||
return Math.max(left, Math.min(right, want));
|
||||
}
|
||||
y: {
|
||||
switch (root.anchorEdge) {
|
||||
case "bottom":
|
||||
return Math.max(10, root.anchorPos.y - height);
|
||||
case "left":
|
||||
case "right":
|
||||
return Math.max(10, Math.min(root.height - height - 10, root.anchorPos.y - height / 2));
|
||||
default:
|
||||
return Math.min(root.height - height - 10, root.anchorPos.y);
|
||||
}
|
||||
}
|
||||
|
||||
onDismissed: root.closeMenu()
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,18 @@ Item {
|
||||
property var keyboardController: null
|
||||
property bool showSettings: false
|
||||
property int currentTab: 0
|
||||
property bool showDndMenu: false
|
||||
|
||||
onCurrentTabChanged: {
|
||||
if (currentTab === 1 && !SettingsData.notificationHistoryEnabled)
|
||||
currentTab = 0;
|
||||
}
|
||||
|
||||
onShowSettingsChanged: {
|
||||
if (showSettings)
|
||||
showDndMenu = false;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onNotificationHistoryEnabledChanged() {
|
||||
@@ -59,8 +65,31 @@ Item {
|
||||
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")
|
||||
onClicked: {
|
||||
if (SessionData.doNotDisturb) {
|
||||
SessionData.setDoNotDisturb(false);
|
||||
return;
|
||||
}
|
||||
root.showDndMenu = !root.showDndMenu;
|
||||
if (root.showDndMenu)
|
||||
root.showSettings = false;
|
||||
}
|
||||
onEntered: sharedTooltip.show(SessionData.doNotDisturb ? I18n.tr("Turn off Do Not Disturb") : I18n.tr("Do Not Disturb"), doNotDisturbButton, 0, 0, "bottom")
|
||||
onExited: sharedTooltip.hide()
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: dndScheduleButton
|
||||
iconName: root.showDndMenu ? "expand_less" : "schedule"
|
||||
iconColor: root.showDndMenu ? Theme.primary : Theme.surfaceText
|
||||
buttonSize: Theme.iconSize + Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
root.showDndMenu = !root.showDndMenu;
|
||||
if (root.showDndMenu)
|
||||
root.showSettings = false;
|
||||
}
|
||||
onEntered: sharedTooltip.show(I18n.tr("Silence for a while"), dndScheduleButton, 0, 0, "bottom")
|
||||
onExited: sharedTooltip.hide()
|
||||
}
|
||||
}
|
||||
@@ -139,6 +168,13 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
DndDurationMenu {
|
||||
id: dndMenu
|
||||
width: parent.width
|
||||
visible: root.showDndMenu
|
||||
onDismissed: root.showDndMenu = false
|
||||
}
|
||||
|
||||
DankButtonGroup {
|
||||
id: tabGroup
|
||||
width: parent.width
|
||||
|
||||
Reference in New Issue
Block a user