From ed8644a6c25a33cea152a82cc6da737f4ab734e8 Mon Sep 17 00:00:00 2001 From: ArashZich Date: Fri, 18 Sep 2026 17:11:47 +0330 Subject: [PATCH] feat: add full-screen break screen overlay - Add fullscreen_break_shield setting in SQLite (migration 7) and Settings -> Timer - Add full-screen /break route displaying progress dial, timer, and mindfulness prompt - Support dismissing screen with Esc or Unlock button without interrupting the break timer - Support skipping break directly from the overlay - Automatically dismiss overlay when transitioning back to focus - Add localized strings for all supported locales --- CHANGELOG.md | 6 + src-tauri/capabilities/default.json | 4 +- src-tauri/src/db/migrations.rs | 15 +- src-tauri/src/lib.rs | 2 +- src-tauri/src/settings/defaults.rs | 1 + src-tauri/src/settings/mod.rs | 4 + src/lib/components/Timer.svelte | 10 + .../settings/sections/TimerSection.svelte | 6 + src/lib/stores/settings.ts | 1 + src/lib/types.ts | 1 + src/lib/utils/breakShield.ts | 48 ++ src/messages/de.json | 8 + src/messages/en.json | 8 + src/messages/es.json | 8 + src/messages/fr.json | 8 + src/messages/ja.json | 8 + src/messages/pt.json | 8 + src/messages/tr.json | 8 + src/messages/zh.json | 8 + src/routes/break/+page.svelte | 528 ++++++++++++++++++ 20 files changed, 687 insertions(+), 3 deletions(-) create mode 100644 src/lib/utils/breakShield.ts create mode 100644 src/routes/break/+page.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 7abcc8a1..e317df6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [Unreleased] + +### Features + +- **Full-screen break screen overlay** — adds an opt-in full-screen ambient break screen (screen shield) that covers the display during short and long breaks with a large countdown dial, relaxation prompt, and quick unlock/skip controls. Contributed by [@ArashZich](https://github.com/ArashZich). + ## [v1.7.1] - 2026-05-11 ### Bug Fixes diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index f7ccf026..c7432398 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -2,11 +2,12 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for the main window", - "windows": ["main", "settings", "stats"], + "windows": ["main", "settings", "stats", "break"], "permissions": [ "core:default", "log:default", "core:window:allow-close", + "core:window:allow-hide", "core:window:allow-minimize", "core:webview:allow-create-webview-window", "core:window:allow-show", @@ -17,6 +18,7 @@ "core:window:allow-is-maximized", "core:window:allow-set-fullscreen", "core:window:allow-is-fullscreen", + "core:window:allow-set-always-on-top", "opener:default", "dialog:default", "dialog:allow-open", diff --git a/src-tauri/src/db/migrations.rs b/src-tauri/src/db/migrations.rs index 113c3ac0..ccedf2c6 100644 --- a/src-tauri/src/db/migrations.rs +++ b/src-tauri/src/db/migrations.rs @@ -90,6 +90,13 @@ const MIGRATION_6: &str = " INSERT INTO schema_version VALUES (6); "; +/// Seeds the `fullscreen_break_shield` setting for users upgrading from an older version. +/// Defaults to 'false' (opt-in break screen overlay). +const MIGRATION_7: &str = " + INSERT OR IGNORE INTO settings (key, value) VALUES ('fullscreen_break_shield', 'false'); + INSERT INTO schema_version VALUES (7); +"; + /// Apply any pending migrations. Each migration is wrapped in a transaction /// so a partial failure leaves the database unchanged. pub fn run(conn: &Connection) -> Result<()> { @@ -131,6 +138,12 @@ pub fn run(conn: &Connection) -> Result<()> { log::info!("[db/migrations] MIGRATION_6 complete"); } + if version < 7 { + log::info!("[db/migrations] applying MIGRATION_7: seed fullscreen_break_shield"); + conn.execute_batch(&format!("BEGIN; {MIGRATION_7} COMMIT;"))?; + log::info!("[db/migrations] MIGRATION_7 complete"); + } + Ok(()) } @@ -166,7 +179,7 @@ mod tests { let v: i64 = conn .query_row("SELECT MAX(version) FROM schema_version", [], |r| r.get(0)) .unwrap(); - assert_eq!(v, 6); + assert_eq!(v, 7); } #[test] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3f4596d7..8883426b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -335,7 +335,7 @@ pub fn run() { let _ = win_for_close.hide(); } else { // Main window is truly closing — close child windows if open. - for label in ["settings", "stats"] { + for label in ["settings", "stats", "break"] { if let Some(win) = app_for_close.get_webview_window(label) { let _ = win.close(); } diff --git a/src-tauri/src/settings/defaults.rs b/src-tauri/src/settings/defaults.rs index 2edea8ff..880f5d3b 100644 --- a/src-tauri/src/settings/defaults.rs +++ b/src-tauri/src/settings/defaults.rs @@ -39,4 +39,5 @@ pub const DEFAULTS: &[(&str, &str)] = &[ ("local_shortcut_volume_up", "ArrowUp"), ("local_shortcut_mute", "m"), ("local_shortcut_fullscreen", "F11"), + ("fullscreen_break_shield", "false"), ]; diff --git a/src-tauri/src/settings/mod.rs b/src-tauri/src/settings/mod.rs index 268c76c1..c771db1f 100644 --- a/src-tauri/src/settings/mod.rs +++ b/src-tauri/src/settings/mod.rs @@ -57,6 +57,8 @@ pub struct Settings { pub local_shortcut_volume_up: String, pub local_shortcut_mute: String, pub local_shortcut_fullscreen: String, + /// When true, a full-screen ambient break overlay covers the screen during breaks. + pub fullscreen_break_shield: bool, /// Last known window X coordinate (physical pixels). `None` = use OS default. pub window_x: Option, /// Last known window Y coordinate (physical pixels). `None` = use OS default. @@ -120,6 +122,7 @@ impl Default for Settings { local_shortcut_volume_up: "ArrowUp".to_string(), local_shortcut_mute: "m".to_string(), local_shortcut_fullscreen: "F11".to_string(), + fullscreen_break_shield: false, window_x: None, window_y: None, window_width: None, @@ -245,6 +248,7 @@ pub fn load(conn: &Connection) -> Result { local_shortcut_volume_up: map.get("local_shortcut_volume_up").cloned().unwrap_or(d.local_shortcut_volume_up), local_shortcut_mute: map.get("local_shortcut_mute").cloned().unwrap_or(d.local_shortcut_mute), local_shortcut_fullscreen: map.get("local_shortcut_fullscreen").cloned().unwrap_or(d.local_shortcut_fullscreen), + fullscreen_break_shield: parse_bool(&map, "fullscreen_break_shield", d.fullscreen_break_shield), window_x: parse_opt_i32(&map, "window_x"), window_y: parse_opt_i32(&map, "window_y"), window_width: parse_opt_u32(&map, "window_width"), diff --git a/src/lib/components/Timer.svelte b/src/lib/components/Timer.svelte index c82dd9b9..7e85bfdd 100644 --- a/src/lib/components/Timer.svelte +++ b/src/lib/components/Timer.svelte @@ -24,6 +24,7 @@ import type { UnlistenFn } from '@tauri-apps/api/event'; import * as m from '$paraglide/messages.js'; import { notificationShow } from '$lib/ipc'; + import { openBreakShield, closeBreakShield } from '$lib/utils/breakShield'; interface Props { isCompact?: boolean; @@ -82,6 +83,14 @@ }), await onRoundChange((snap) => { timerState.set(snap); + if (snap.round_type === 'short-break' || snap.round_type === 'long-break') { + if ($settings.fullscreen_break_shield) { + openBreakShield(); + } + } else { + closeBreakShield(); + } + if ($settings.notifications_enabled) { let title: string; let body: string; @@ -103,6 +112,7 @@ }), await onTimerReset((snap) => { timerState.set(snap); + closeBreakShield(); }) ); })(); diff --git a/src/lib/components/settings/sections/TimerSection.svelte b/src/lib/components/settings/sections/TimerSection.svelte index cd1e1891..4174a20b 100644 --- a/src/lib/components/settings/sections/TimerSection.svelte +++ b/src/lib/components/settings/sections/TimerSection.svelte @@ -309,6 +309,12 @@ checked={$settings.dial_countdown} onclick={() => toggle('dial_countdown', $settings.dial_countdown)} /> + toggle('fullscreen_break_shield', $settings.fullscreen_break_shield)} + />