add blocker presets

This commit is contained in:
timka123 committed 2026-10-19 11:54:15 +03:00
1 parent 2be074f191
commit 772fdbfded
12 files changed
+426 -83

No files matched your search

+35 -77
View File
@@ -29,27 +29,21 @@ ScrollView {
controller.patchSettings({ notifications: { telegram: patch } })
}
// Presets are a saved list on the server (`pomodoro.presets`), each one a
// name plus its own work/short/long minutes — editable here, not baked
// into the UI. `_merge` on the backend replaces list fields wholesale, so
// editing one preset's number means resending the whole list.
function patchPreset(index, key, value) {
if (!page.pomodoro || !page.pomodoro.presets) return
var presets = page.pomodoro.presets.map(function (p) { return Object.assign({}, p) })
presets[index][key] = value
controller.patchSettings({ pomodoro: { presets: presets } })
}
function applyPreset(index) {
if (!page.pomodoro || !page.pomodoro.presets) return
var preset = page.pomodoro.presets[index]
controller.patchSettings({
pomodoro: {
work_minutes: preset.work_minutes,
short_break_minutes: preset.short_break_minutes,
long_break_minutes: preset.long_break_minutes,
cycles_before_long_break: preset.cycles_before_long_break
}
})
// The Focus/Short break/Long break fields always show and edit the live
// settings. When they still match a saved preset, the edit also rewrites
// that preset's own numbers, so picking it again later gets you back to
// where you left off rather than to some older saved value.
function patchDuration(key, value) {
if (!page.pomodoro) return
var patch = {}; patch[key] = value
var idx = controller.activePresetIndex
if (idx >= 0 && page.pomodoro.presets) {
var presets = page.pomodoro.presets.map(function (p) { return Object.assign({}, p) })
presets[idx] = Object.assign(presets[idx], patch)
controller.patchSettings({ pomodoro: Object.assign({ presets: presets }, patch) })
} else {
controller.patchSettings({ pomodoro: patch })
}
}
component Field: ColumnLayout {
@@ -127,68 +121,32 @@ ScrollView {
spacing: 12
SectionTitle { text: "Timer" }
// Presets: a saved list of work/short/long combos. "Apply" copies
// one onto the live settings below; the numbers on each row are
// the preset's own and edit it in place.
ColumnLayout {
// The preset switcher: pick one and it applies at once. The fields
// below always belong to whichever one is lit — there is nothing
// to show for the others until you switch to them.
RowLayout {
Layout.fillWidth: true
spacing: 8
Repeater {
model: page.pomodoro && page.pomodoro.presets ? page.pomodoro.presets : []
RowLayout {
id: presetRow
delegate: PresetChip {
required property var modelData
required property int index
Layout.fillWidth: true
spacing: 16
readonly property bool isActive: page.pomodoro
&& page.pomodoro.work_minutes === modelData.work_minutes
&& page.pomodoro.short_break_minutes === modelData.short_break_minutes
&& page.pomodoro.long_break_minutes === modelData.long_break_minutes
Label {
text: presetRow.modelData.name
color: presetRow.isActive ? Theme.ember : Theme.ink
font.pixelSize: 13
font.weight: presetRow.isActive ? Font.DemiBold : Font.Medium
font.family: Theme.sansFamily
Layout.preferredWidth: 70
}
Field {
label: "Focus"
SpinBox {
from: 1; to: 240
value: presetRow.modelData.work_minutes
onValueModified: page.patchPreset(presetRow.index, "work_minutes", value)
}
}
Field {
label: "Short break"
SpinBox {
from: 1; to: 120
value: presetRow.modelData.short_break_minutes
onValueModified: page.patchPreset(presetRow.index, "short_break_minutes", value)
}
}
Field {
label: "Long break"
SpinBox {
from: 1; to: 240
value: presetRow.modelData.long_break_minutes
onValueModified: page.patchPreset(presetRow.index, "long_break_minutes", value)
}
}
GhostButton {
text: presetRow.isActive ? "Active" : "Apply"
enabled: !presetRow.isActive
onClicked: page.applyPreset(presetRow.index)
}
Item { Layout.fillWidth: true }
text: modelData.name
active: controller.activePresetIndex === index
onClicked: controller.applyPreset(index)
}
}
Label {
visible: controller.activePresetIndex === -1
text: "Custom"
color: Theme.muted
font.pixelSize: 12
font.family: Theme.sansFamily
leftPadding: 6
}
Item { Layout.fillWidth: true }
}
Flow {
@@ -199,7 +157,7 @@ ScrollView {
SpinBox {
from: 1; to: 240
value: page.pomodoro ? page.pomodoro.work_minutes : 25
onValueModified: page.patchPomodoro("work_minutes", value)
onValueModified: page.patchDuration("work_minutes", value)
}
}
Field {
@@ -207,7 +165,7 @@ ScrollView {
SpinBox {
from: 1; to: 120
value: page.pomodoro ? page.pomodoro.short_break_minutes : 5
onValueModified: page.patchPomodoro("short_break_minutes", value)
onValueModified: page.patchDuration("short_break_minutes", value)
}
}
Field {
@@ -215,7 +173,7 @@ ScrollView {
SpinBox {
from: 1; to: 240
value: page.pomodoro ? page.pomodoro.long_break_minutes : 15
onValueModified: page.patchPomodoro("long_break_minutes", value)
onValueModified: page.patchDuration("long_break_minutes", value)
}
}
Field {
+23
View File
@@ -102,6 +102,29 @@ ScrollView {
idle: !controller.timerEnabled || !controller.running
}
// Switching here takes effect at once — it resizes whatever
// is already running or paused too, not just the next one.
RowLayout {
visible: controller.timerEnabled
&& controller.settings.pomodoro
&& controller.settings.pomodoro.presets
&& controller.settings.pomodoro.presets.length > 0
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 14
spacing: 8
Repeater {
model: controller.settings.pomodoro ? controller.settings.pomodoro.presets : []
delegate: PresetChip {
required property var modelData
required property int index
text: modelData.name
active: controller.activePresetIndex === index
onClicked: controller.applyPreset(index)
}
}
}
RowLayout {
visible: controller.timerEnabled && controller.running
Layout.alignment: Qt.AlignHCenter
@@ -0,0 +1,39 @@
// A pill in a preset switcher: filled ember when it is the one the live
// settings currently match, outlined otherwise. Clicking any pill applies it
// at once — there is no separate "Apply" step. Shared by SettingsPage (the
// full switcher, with the numbers editable below it) and TimerPage (a quick
// way to change the length of the *next* interval without leaving the dial).
import QtQuick
import QtQuick.Controls
import ".."
Rectangle {
id: chip
property string text: ""
property bool active: false
signal clicked()
implicitWidth: label.implicitWidth + 28
implicitHeight: 32
radius: 16
color: chip.active ? Theme.ember : (chipHover.hovered ? Qt.alpha(Theme.ember, 0.10) : "transparent")
border.width: chip.active ? 0 : 1
border.color: chipHover.hovered ? Theme.ember : Theme.rule
Behavior on color { ColorAnimation { duration: 110 } }
Label {
id: label
anchors.centerIn: parent
text: chip.text
color: chip.active ? "white" : Theme.ink
font.pixelSize: 12
font.weight: chip.active ? Font.DemiBold : Font.Medium
font.family: Theme.sansFamily
}
HoverHandler { id: chipHover; cursorShape: Qt.PointingHandCursor }
TapHandler {
grabPermissions: PointerHandler.TakeOverForbidden
onTapped: chip.clicked()
}
}