Notifications repeated both lock-screen switch labels for every application, so twenty apps meant sixty rows of the same two sentences and the page could not be scanned at all. Each app is one row now, carrying what its switches add up to -- "On, lock screen shows the sender only", "On, hidden on the lock screen", "Notifications off" -- with the switches behind it, one app open at a time. The identifier only appears while an app is open, which is the only time it disambiguates anything, and the content switch dims when the app cannot reach the lock screen at all, because there it means nothing. Shortcuts were already grouped; the problem was that "Windows" caught focus, movement, splitting, resizing and window state alike and held 43 of the 93 binds. A section that long is a list, not a grouping. They are separated by intent now -- Focus, Move & split, Size, Window state -- and the split was checked against the binds this machine actually has rather than trusted from the keywords. Order matters in two places worth naming: "Next window splits down" is about splitting rather than focus, and "Focus session" is quiet mode bound to a workspace rather than window focus, so both are settled before the general checks. Refresh rate gets its own row. That need was created by collapsing the resolution list: the rates for a resolution were only ever reachable by opening it, so changing nothing but the rate meant going through the mode you already had. It appears only when the current resolution offers more than one. Default-application rows carry a chevron, having previously opened a chooser while looking completely inert. The notification contract asserted the literal Notifs.appRule(app.id).enabled, which moved when the rows collapsed. The rule is still read through a binding on Notifs.appRule, so a rule changed elsewhere still reaches the row -- the assertion now requires that, rather than requiring one particular spelling of it. The power profile rows were left alone. A three-way choice in three rows looks wasteful until you notice each row explains what the profile does, and that page has empty space to spare; a segmented control would trade information for space that is not scarce. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
228 lines
8.4 KiB
QML
228 lines
8.4 KiB
QML
import QtQuick
|
|
import qs.config
|
|
import qs.services
|
|
|
|
SettingsPage {
|
|
id: root
|
|
|
|
// Only one application is expanded at a time; the point is a card you
|
|
// can read, not twenty open at once.
|
|
property string expandedApp: ""
|
|
|
|
title: "Notifications & Focus"
|
|
lede: "Control interruptions without losing useful history."
|
|
|
|
SettingsCard {
|
|
title: "Notifications"
|
|
|
|
SettingRow {
|
|
label: "Do Not Disturb"
|
|
detail: "Keep notifications in the center but suppress banners"
|
|
controlWidth: 48
|
|
|
|
SettingsToggle {
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
checked: Notifs.doNotDisturb
|
|
onToggled: value => Notifs.doNotDisturb = value
|
|
}
|
|
}
|
|
|
|
TextRow {
|
|
label: "Notification history"
|
|
detail: "Live notifications retained by the shell"
|
|
value: Notifs.history.length === 1 ? "1 item" : `${Notifs.history.length} items`
|
|
}
|
|
|
|
ActionRow {
|
|
label: "Clear notification history"
|
|
detail: "Dismiss every item currently in the notification center"
|
|
divider: false
|
|
action: "Clear all"
|
|
enabled: Notifs.history.length > 0
|
|
onTriggered: Notifs.dismissAll()
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Banner behavior"
|
|
|
|
SliderRow { setting: "notificationTimeoutMs" }
|
|
SliderRow {
|
|
setting: "notificationTimeoutCriticalMs"
|
|
zeroLabel: "Never"
|
|
}
|
|
SliderRow { setting: "notificationHistoryLimit" }
|
|
SliderRow { setting: "maxVisibleToasts"; divider: false }
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Application rules"
|
|
subtitle: "Apps appear here after they send a notification."
|
|
|
|
TextRow {
|
|
visible: Notifs.applications.length === 0
|
|
label: "No applications remembered yet"
|
|
detail: "Application controls will appear after the first notification arrives."
|
|
divider: false
|
|
}
|
|
|
|
Repeater {
|
|
model: Notifs.applications
|
|
|
|
Column {
|
|
id: appEntry
|
|
|
|
required property var modelData
|
|
|
|
readonly property var app: appEntry.modelData
|
|
readonly property var rule: Notifs.appRule(appEntry.app.id)
|
|
readonly property bool open: root.expandedApp === String(appEntry.app.id)
|
|
|
|
// What the two lock-screen switches add up to, so the common
|
|
// case -- reading rather than changing -- needs no interaction.
|
|
// Every app repeated both switch labels verbatim before this,
|
|
// which made twenty apps sixty rows of identical sentences.
|
|
readonly property string summary: {
|
|
if (!appEntry.rule.enabled)
|
|
return "Notifications off";
|
|
if (!appEntry.rule.showOnLockScreen)
|
|
return "On · hidden on the lock screen";
|
|
return appEntry.rule.showContentOnLockScreen
|
|
? "On · lock screen shows content"
|
|
: "On · lock screen shows the sender only";
|
|
}
|
|
|
|
width: parent.width
|
|
|
|
SettingRow {
|
|
width: parent.width
|
|
label: appEntry.app.name
|
|
// The identifier is only worth the space while the app is
|
|
// open, which is the only time it disambiguates anything.
|
|
detail: appEntry.open ? String(appEntry.app.id) : appEntry.summary
|
|
activatable: true
|
|
divider: !appEntry.open
|
|
controlWidth: 92
|
|
onActivated: root.expandedApp = appEntry.open ? "" : String(appEntry.app.id)
|
|
|
|
Row {
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 11
|
|
|
|
SettingsToggle {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
checked: appEntry.rule.enabled
|
|
onToggled: value => Notifs.setAppRule(appEntry.app.id, { enabled: value })
|
|
}
|
|
|
|
Text {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: appEntry.open ? "\u25B4" : "\u25BE"
|
|
color: Theme.fgMuted
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingRow {
|
|
width: parent.width
|
|
visible: appEntry.open
|
|
label: "Show on lock screen"
|
|
detail: "Allow this app's notifications on the lock screen"
|
|
controlWidth: 48
|
|
|
|
SettingsToggle {
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
checked: appEntry.rule.showOnLockScreen
|
|
onToggled: value => Notifs.setAppRule(appEntry.app.id, { showOnLockScreen: value })
|
|
}
|
|
}
|
|
|
|
SettingRow {
|
|
width: parent.width
|
|
visible: appEntry.open
|
|
label: "Show content on lock screen"
|
|
detail: "Show message details when this app is visible there"
|
|
divider: false
|
|
controlWidth: 48
|
|
// Meaningless unless the app reaches the lock screen at all.
|
|
opacity: appEntry.rule.showOnLockScreen ? 1 : 0.45
|
|
|
|
SettingsToggle {
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
enabled: appEntry.rule.showOnLockScreen
|
|
checked: appEntry.rule.showContentOnLockScreen
|
|
onToggled: value => Notifs.setAppRule(appEntry.app.id, { showContentOnLockScreen: value })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Focus sessions"
|
|
subtitle: "A focus session binds quiet mode and Caffeine to the current workspace."
|
|
|
|
SettingRow {
|
|
label: "Keep the screen awake"
|
|
detail: Caffeine.enabled
|
|
? "The display will not blank or lock while this is on"
|
|
: "Idle timings on Power & Lock apply normally"
|
|
controlWidth: 48
|
|
|
|
SettingsToggle {
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
checked: Caffeine.enabled
|
|
onToggled: value => Caffeine.enabled = value
|
|
}
|
|
}
|
|
|
|
SettingRow {
|
|
id: durationRow
|
|
|
|
label: "Default duration"
|
|
detail: "Used by Super+Shift+F and Quick Settings"
|
|
controlWidth: 264
|
|
|
|
Row {
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 6
|
|
|
|
Repeater {
|
|
model: [25, 45, 60, 90]
|
|
|
|
SettingsButton {
|
|
required property int modelData
|
|
|
|
text: `${modelData}m`
|
|
tone: DesktopPreferences.get("focusDurationMinutes") === modelData ? "accent" : "normal"
|
|
onClicked: SystemSettings.commitPreference("focusDurationMinutes", modelData)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingRow {
|
|
label: FocusSession.active ? `Active on ${FocusSession.workspaceLabel}` : "No active focus session"
|
|
detail: FocusSession.active ? `${FocusSession.remainingText} remaining` : "Start one without leaving Settings"
|
|
divider: false
|
|
controlWidth: 120
|
|
|
|
SettingsButton {
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: FocusSession.active ? "Show controls" : "Start focus"
|
|
tone: FocusSession.active ? "normal" : "accent"
|
|
onClicked: FocusSession.reveal()
|
|
}
|
|
}
|
|
}
|
|
}
|