a lightweight, interval-based utility to combat digital strain through "Ma" (intentional pauses) for the eyes and body.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add profile level editor to settings

+137 -17
+4 -1
src/main.rs
··· 75 75 let win = settings_mgr.window().clone_strong(); 76 76 let cfg_arc = state.cfg.clone(); 77 77 let settings_ref = Rc::clone(&settings_mgr); 78 + let cmd_tx_save = state.cmd_tx.clone(); 78 79 settings_mgr.window().on_save_clicked(move || { 79 80 let mut cfg = cfg_arc.lock().unwrap().clone(); 80 81 settings_ref.read_into(&mut cfg); ··· 84 85 } 85 86 win.hide().unwrap_or_default(); 86 87 config::save(&cfg).unwrap_or_else(|e| log::warn!("save failed: {e}")); 88 + // Live apply: switch the running timer to the new profile immediately. 89 + let profile = active_profile(&cfg); 90 + let _ = cmd_tx_save.send(TimerCommand::SetProfile(profile)); 87 91 }); 88 92 } 89 93 settings_mgr.window().on_open_config_dir(|| { ··· 94 98 settings_mgr.window().on_set_password_clicked(|| { 95 99 log::info!("Password setup — Phase 3"); 96 100 }); 97 - settings_mgr.window().on_profile_changed(|_name| {}); 98 101 99 102 // Poll timer — owns overlay, break state, and event_rx directly. 100 103 // No Rc<RefCell> needed: this closure is the sole accessor of these values.
+133 -16
ui/settings.slint
··· 1 - import { Button, ComboBox, CheckBox, Slider, TabWidget, LineEdit, SpinBox } from "std-widgets.slint"; 1 + import { Button, ComboBox, CheckBox, Slider, TabWidget, LineEdit, SpinBox, ScrollView } from "std-widgets.slint"; 2 + 3 + struct LevelEntry { 4 + work-mins: int, 5 + break-mins: int, 6 + break-extra-secs: int, 7 + label: string, 8 + } 2 9 3 10 export component SettingsWindow inherits Window { 4 11 title: "ioma — Settings"; 5 - width: 560px; 6 - min-height: 420px; 12 + width: 600px; 13 + min-height: 460px; 7 14 8 15 // Populated from Rust. 9 16 in-out property <bool> enforced-mode: false; ··· 15 22 in-out property <string> active-profile: "Pomodoro"; 16 23 in-out property <[string]> profile-names: ["Pomodoro", "52/17", "20-20-20", "Focus+Micro"]; 17 24 25 + // Profile level editor 26 + in-out property <[LevelEntry]> levels: []; 27 + in-out property <bool> long-break-enabled: false; 28 + in-out property <int> long-break-after-cycles: 4; 29 + in-out property <int> long-break-duration-mins: 15; 30 + in-out property <int> long-break-gap-mins: 30; 31 + in-out property <string> long-break-label: "Long break"; 32 + 18 33 callback save-clicked(); 19 34 callback cancel-clicked(); 20 35 callback set-password-clicked(); 21 36 callback open-config-dir(); 22 37 callback profile-changed(string); 38 + callback level-changed(int, LevelEntry); 39 + callback level-removed(int); 40 + callback level-added(); 23 41 24 42 VerticalLayout { 25 43 padding: 16px; ··· 53 71 54 72 Tab { 55 73 title: "Profile"; 56 - VerticalLayout { 57 - padding: 16px; 58 - spacing: 12px; 74 + ScrollView { 75 + VerticalLayout { 76 + padding: 16px; 77 + spacing: 10px; 78 + 79 + HorizontalLayout { 80 + spacing: 8px; 81 + Text { text: "Active profile:"; vertical-alignment: center; } 82 + ComboBox { 83 + model: profile-names; 84 + current-value <=> active-profile; 85 + selected(name) => { root.profile-changed(name); } 86 + } 87 + } 88 + 89 + Rectangle { height: 1px; background: #cccccc; } 90 + Text { text: "Break levels:"; font-weight: 600; } 91 + 92 + for level[i] in levels : HorizontalLayout { 93 + spacing: 6px; 94 + alignment: start; 95 + SpinBox { 96 + width: 68px; 97 + value: level.work-mins; 98 + minimum: 1; 99 + maximum: 240; 100 + edited(v) => { root.level-changed(i, { work-mins: v, break-mins: level.break-mins, break-extra-secs: level.break-extra-secs, label: level.label }); } 101 + } 102 + Text { text: "min →"; vertical-alignment: center; } 103 + SpinBox { 104 + width: 58px; 105 + value: level.break-mins; 106 + minimum: 0; 107 + maximum: 120; 108 + edited(v) => { root.level-changed(i, { work-mins: level.work-mins, break-mins: v, break-extra-secs: level.break-extra-secs, label: level.label }); } 109 + } 110 + Text { text: "m"; vertical-alignment: center; } 111 + SpinBox { 112 + width: 58px; 113 + value: level.break-extra-secs; 114 + minimum: 0; 115 + maximum: 59; 116 + edited(v) => { root.level-changed(i, { work-mins: level.work-mins, break-mins: level.break-mins, break-extra-secs: v, label: level.label }); } 117 + } 118 + Text { text: "s break"; vertical-alignment: center; } 119 + LineEdit { 120 + width: 130px; 121 + text: level.label; 122 + edited(v) => { root.level-changed(i, { work-mins: level.work-mins, break-mins: level.break-mins, break-extra-secs: level.break-extra-secs, label: v }); } 123 + } 124 + Button { 125 + width: 32px; 126 + text: "✕"; 127 + clicked => { root.level-removed(i); } 128 + } 129 + } 130 + 131 + Button { 132 + text: "+ Add level"; 133 + clicked => { root.level-added(); } 134 + } 135 + 136 + Rectangle { height: 1px; background: #cccccc; } 137 + 138 + CheckBox { 139 + text: "Long break"; 140 + checked <=> long-break-enabled; 141 + } 142 + 143 + if long-break-enabled : VerticalLayout { 144 + spacing: 8px; 145 + padding-left: 16px; 59 146 60 - Text { text: "Active profile:"; } 61 - ComboBox { 62 - model: profile-names; 63 - current-value <=> active-profile; 64 - } 65 - Text { 66 - text: "Full profile editor (add/remove levels, set long break) coming in next release."; 67 - color: #888888; 68 - font-size: 13px; 69 - wrap: word-wrap; 147 + HorizontalLayout { 148 + spacing: 6px; 149 + Text { text: "After"; vertical-alignment: center; } 150 + SpinBox { 151 + width: 58px; 152 + value <=> long-break-after-cycles; 153 + minimum: 1; 154 + maximum: 20; 155 + } 156 + Text { text: "cycles, take a"; vertical-alignment: center; } 157 + SpinBox { 158 + width: 58px; 159 + value <=> long-break-duration-mins; 160 + minimum: 1; 161 + maximum: 120; 162 + } 163 + Text { text: "min break"; vertical-alignment: center; } 164 + } 165 + 166 + HorizontalLayout { 167 + spacing: 6px; 168 + Text { text: "Reset cycle count if gap >"; vertical-alignment: center; } 169 + SpinBox { 170 + width: 58px; 171 + value <=> long-break-gap-mins; 172 + minimum: 5; 173 + maximum: 240; 174 + } 175 + Text { text: "min"; vertical-alignment: center; } 176 + } 177 + 178 + HorizontalLayout { 179 + spacing: 6px; 180 + Text { text: "Label:"; vertical-alignment: center; } 181 + LineEdit { 182 + width: 200px; 183 + text <=> long-break-label; 184 + } 185 + } 186 + } 70 187 } 71 188 } 72 189 }