// Settings, split by where they live. // // Anything under "This computer" is local to this machine (and saved in // ~/.config/kadence/desktop.json). Everything else is server-side, shared with // the web app and the Telegram bot. import QtQuick import QtQuick.Controls.Material import QtQuick.Layouts import "components" ScrollView { id: page contentWidth: availableWidth clip: true readonly property var pomodoro: controller.settings.pomodoro ? controller.settings.pomodoro : null readonly property var telegram: controller.settings.notifications ? controller.settings.notifications.telegram : null readonly property var report: controller.settings.notifications ? controller.settings.notifications.report : null function patchPomodoro(key, value) { var patch = {}; patch[key] = value controller.patchSettings({ pomodoro: patch }) } function patchTelegram(key, value) { var patch = {}; patch[key] = value controller.patchSettings({ notifications: { telegram: patch } }) } ColumnLayout { width: page.availableWidth spacing: 16 ColumnLayout { Layout.fillWidth: true Layout.margins: 20 spacing: 16 // ---- connection -------------------------------------------------- Card { title: "Backend" subtitle: controller.connected ? "Connected to " + controller.config.server_url : controller.connectionError RowLayout { Layout.fillWidth: true TextField { id: serverField Layout.fillWidth: true text: controller.config.server_url placeholderText: "http://localhost:8000" } TextField { id: tokenField Layout.preferredWidth: 180 text: controller.config.api_token placeholderText: "API token (optional)" echoMode: TextInput.Password } PrimaryButton { text: "Connect" onClicked: { controller.setConfig("server_url", serverField.text.trim()) controller.setConfig("api_token", tokenField.text) } } } RowLayout { Label { text: "Poll every"; font.pixelSize: 12 } SpinBox { from: 1; to: 60 value: controller.config.poll_seconds onValueModified: controller.setConfig("poll_seconds", value) } Label { text: "seconds"; font.pixelSize: 12 } Item { Layout.fillWidth: true } Button { text: "Open the web app"; flat: true; onClicked: controller.openWebUi() } } } // ---- timer ------------------------------------------------------- Card { title: "Timer" subtitle: "Shared with the web app and the Telegram bot." GridLayout { Layout.fillWidth: true columns: page.width > 700 ? 4 : 2 columnSpacing: 16 rowSpacing: 8 ColumnLayout { Label { text: "Focus (min)"; font.pixelSize: 11; opacity: 0.7 } SpinBox { from: 1; to: 240 value: page.pomodoro ? page.pomodoro.work_minutes : 25 onValueModified: page.patchPomodoro("work_minutes", value) } } ColumnLayout { Label { text: "Short break"; font.pixelSize: 11; opacity: 0.7 } SpinBox { from: 1; to: 120 value: page.pomodoro ? page.pomodoro.short_break_minutes : 5 onValueModified: page.patchPomodoro("short_break_minutes", value) } } ColumnLayout { Label { text: "Long break"; font.pixelSize: 11; opacity: 0.7 } SpinBox { from: 1; to: 240 value: page.pomodoro ? page.pomodoro.long_break_minutes : 15 onValueModified: page.patchPomodoro("long_break_minutes", value) } } ColumnLayout { Label { text: "Long break every"; font.pixelSize: 11; opacity: 0.7 } SpinBox { from: 1; to: 12 value: page.pomodoro ? page.pomodoro.cycles_before_long_break : 4 onValueModified: page.patchPomodoro("cycles_before_long_break", value) } } ColumnLayout { Label { text: "Daily goal"; font.pixelSize: 11; opacity: 0.7 } SpinBox { from: 1; to: 40 value: page.pomodoro ? page.pomodoro.daily_goal_pomodoros : 8 onValueModified: page.patchPomodoro("daily_goal_pomodoros", value) } } } Flow { Layout.fillWidth: true spacing: 16 Switch { text: "Auto-start breaks" checked: page.pomodoro ? page.pomodoro.auto_start_breaks : true onToggled: page.patchPomodoro("auto_start_breaks", checked) } Switch { text: "Auto-start next focus" checked: page.pomodoro ? page.pomodoro.auto_start_work : false onToggled: page.patchPomodoro("auto_start_work", checked) } Switch { text: "Allow pausing" checked: page.pomodoro ? page.pomodoro.allow_pause : true onToggled: page.patchPomodoro("allow_pause", checked) } Switch { text: "Strict mode" checked: page.pomodoro ? page.pomodoro.strict_mode : false onToggled: page.patchPomodoro("strict_mode", checked) ToolTip.visible: hovered ToolTip.text: "Abandoning a focus interval requires a reason." } } } // ---- desktop integration ---------------------------------------- Card { title: "This computer" subtitle: "Native desktop features. Stored locally, not on the server." Flow { Layout.fillWidth: true spacing: 16 Switch { text: "Tray icon" checked: controller.config.tray_icon onToggled: controller.setConfig("tray_icon", checked) } Switch { text: "Close to tray" checked: controller.config.close_to_tray onToggled: controller.setConfig("close_to_tray", checked) } Switch { text: "Notifications" checked: controller.config.notifications onToggled: controller.setConfig("notifications", checked) } Switch { text: "Notification buttons" checked: controller.config.notification_actions onToggled: controller.setConfig("notification_actions", checked) ToolTip.visible: hovered ToolTip.text: "“Start break” / “Skip” directly on the notification." } Switch { text: "Keep the screen awake" checked: controller.config.inhibit_screensaver onToggled: controller.setConfig("inhibit_screensaver", checked) } Switch { text: "Do Not Disturb while focusing" checked: controller.config.do_not_disturb onToggled: controller.setConfig("do_not_disturb", checked) } Switch { text: "Pause music when focus starts" checked: controller.config.pause_media onToggled: controller.setConfig("pause_media", checked) ToolTip.visible: hovered ToolTip.text: "Any MPRIS player: Spotify, VLC, mpv, browsers." } Switch { text: "Taskbar progress" checked: controller.config.taskbar_progress onToggled: controller.setConfig("taskbar_progress", checked) } Switch { text: "Pause when the screen locks" checked: controller.config.pause_on_lock onToggled: controller.setConfig("pause_on_lock", checked) } Switch { text: "D-Bus control" checked: controller.config.dbus_service onToggled: controller.setConfig("dbus_service", checked) ToolTip.visible: hovered ToolTip.text: "org.kadence.Desktop — bind it to a global shortcut." } } RowLayout { Layout.fillWidth: true Label { text: "Pause after"; font.pixelSize: 12 } SpinBox { from: 0; to: 120 value: controller.config.pause_on_idle_minutes onValueModified: controller.setConfig("pause_on_idle_minutes", value) } Label { text: "idle minutes (0 = never)"; font.pixelSize: 12 } Item { Layout.fillWidth: true } } RowLayout { Layout.fillWidth: true Switch { text: "Start with the session" checked: controller.config.autostart onToggled: controller.setConfig("autostart", checked) } Switch { text: "systemd user service" checked: controller.config.service_enabled onToggled: controller.setConfig("service_enabled", checked) ToolTip.visible: hovered ToolTip.text: "Restarts the app if it dies; logs to the journal." } Switch { text: "Start minimised" checked: controller.config.start_minimized onToggled: controller.setConfig("start_minimized", checked) } } } // ---- telegram ---------------------------------------------------- Card { title: "Telegram" subtitle: "Daily report, morning briefing and the nudges that get you back to work." RowLayout { Layout.fillWidth: true Switch { text: "Enabled" checked: page.telegram ? page.telegram.enabled : false onToggled: page.patchTelegram("enabled", checked) } TextField { Layout.fillWidth: true placeholderText: "Bot token from @BotFather" text: page.telegram ? page.telegram.bot_token : "" onEditingFinished: if (text !== "••••••••") page.patchTelegram("bot_token", text) } TextField { Layout.preferredWidth: 160 placeholderText: "Chat id" text: page.telegram ? page.telegram.chat_id : "" onEditingFinished: page.patchTelegram("chat_id", text) } } Flow { Layout.fillWidth: true spacing: 16 Switch { text: "Daily report" checked: page.telegram ? page.telegram.daily_report : true onToggled: page.patchTelegram("daily_report", checked) } Switch { text: "Morning briefing" checked: page.telegram ? page.telegram.morning_briefing : true onToggled: page.patchTelegram("morning_briefing", checked) } Switch { text: "Weekly review" checked: page.telegram ? page.telegram.weekly_report : true onToggled: page.patchTelegram("weekly_report", checked) } Switch { text: "Nudge me when idle" checked: page.telegram ? page.telegram.nudge_enabled : true onToggled: page.patchTelegram("nudge_enabled", checked) } Switch { text: "Chat commands" checked: page.telegram ? page.telegram.commands_enabled : true onToggled: page.patchTelegram("commands_enabled", checked) } } RowLayout { Layout.fillWidth: true ColumnLayout { Label { text: "Report at (HH:MM)"; font.pixelSize: 11; opacity: 0.7 } TextField { text: page.telegram ? page.telegram.daily_report_time : "21:00" onEditingFinished: page.patchTelegram("daily_report_time", text) } } ColumnLayout { Label { text: "Briefing at (HH:MM)"; font.pixelSize: 11; opacity: 0.7 } TextField { text: page.telegram ? page.telegram.morning_briefing_time : "09:00" onEditingFinished: page.patchTelegram("morning_briefing_time", text) } } ColumnLayout { Label { text: "Nudge after (min idle)"; font.pixelSize: 11; opacity: 0.7 } SpinBox { from: 10; to: 600 value: page.telegram ? page.telegram.nudge_after_idle_minutes : 90 onValueModified: page.patchTelegram("nudge_after_idle_minutes", value) } } ColumnLayout { Label { text: "Tone"; font.pixelSize: 11; opacity: 0.7 } ComboBox { model: ["friendly", "coach", "drill_sergeant", "dry"] currentIndex: page.report ? model.indexOf(page.report.tone) : 0 onActivated: function (index) { controller.patchSettings({ notifications: { report: { tone: model[index] } } }) } } } ColumnLayout { Label { text: "Language"; font.pixelSize: 11; opacity: 0.7 } ComboBox { model: ["en", "ru"] currentIndex: page.report && page.report.language === "ru" ? 1 : 0 onActivated: function (index) { controller.patchSettings({ notifications: { report: { language: model[index] } } }) } } } } } Card { title: "Shortcuts and scripting" subtitle: "The app exposes org.kadence.Desktop on the session bus." Label { Layout.fillWidth: true wrapMode: Text.WordWrap font.family: "Roboto Mono, monospace" font.pixelSize: 11 text: "qdbus org.kadence.Desktop /Timer org.kadence.Timer.Toggle\n" + "qdbus org.kadence.Desktop /Timer org.kadence.Timer.Status\n" + "kadence-desktop --toggle # same thing from a terminal\n\n" + "System Settings → Shortcuts → Custom → D-Bus command binds these to a key." } Label { text: "Kadence " + appVersion + " · sources, blocking rules and reports live on the backend." font.pixelSize: 11 opacity: 0.6 } } } } Component.onCompleted: controller.loadSettings() }