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(ui): move profile selection to profile tab, group setings on rhythm tab

+237 -182
+2
.gitignore
··· 1 1 /target 2 2 doc/ 3 3 .*/ 4 + .im* 5 + CLAUDE.md
+39
ui/components/atoms.slint
··· 25 25 color: Theme.ink-mid; 26 26 } 27 27 } 28 + 29 + export component SectionHeading inherits VerticalLayout { 30 + in property <string> title; 31 + in property <string> description: ""; 32 + 33 + spacing: 4px; 34 + 35 + HorizontalLayout { 36 + spacing: 12px; 37 + 38 + Text { 39 + text: root.title; 40 + font-family: "Shippori Mincho"; 41 + font-size: Theme.font_label; 42 + font-weight: 700; 43 + color: Theme.ink-hi; 44 + } 45 + 46 + VerticalLayout { 47 + horizontal-stretch: 1; 48 + spacing: 0px; 49 + 50 + Rectangle { vertical-stretch: 1; } 51 + 52 + Rectangle { 53 + height: 1px; 54 + background: Theme.line; 55 + } 56 + 57 + Rectangle { vertical-stretch: 1; } 58 + } 59 + } 60 + 61 + if root.description != "": Text { 62 + text: root.description; 63 + font-size: Theme.font_xxsmall; 64 + color: Theme.ink-lo; 65 + } 66 + }
+4 -4
ui/settings.slint
··· 101 101 autostart <=> root.autostart; 102 102 idle-detection-enabled <=> root.idle-detection-enabled; 103 103 idle-threshold-mins <=> root.idle-threshold-mins; 104 - active-profile <=> root.active-profile; 105 - profile-names: root.profile-names; 106 - profile-name-widths: root.profile-name-widths; 107 104 theme-mode <=> root.theme-mode; 108 105 text-size-mode <=> root.text-size-mode; 109 106 set-password-clicked => { root.set-password-clicked(); } 110 107 open-config-dir => { root.open-config-dir(); } 111 - profile-changed(name) => { root.profile-changed(name); } 112 108 theme-mode-changed(i) => { root.theme-mode-changed(i); } 113 109 text-size-mode-changed(i) => { root.text-size-mode-changed(i); } 114 110 } 115 111 116 112 if root.active-tab == "profile": ProfileTab { 113 + active-profile <=> root.active-profile; 114 + profile-names: root.profile-names; 115 + profile-name-widths: root.profile-name-widths; 117 116 levels <=> root.levels; 118 117 long-break-enabled <=> root.long-break-enabled; 119 118 long-break-after-cycles <=> root.long-break-after-cycles; 120 119 long-break-duration-mins <=> root.long-break-duration-mins; 121 120 long-break-gap-mins <=> root.long-break-gap-mins; 122 121 long-break-label <=> root.long-break-label; 122 + profile-changed(name) => { root.profile-changed(name); } 123 123 level-changed(i, entry) => { root.level-changed(i, entry); } 124 124 level-removed(i) => { root.level-removed(i); } 125 125 level-added => { root.level-added(); }
+29
ui/views/profile_tab.slint
··· 1 1 import { ScrollView } from "std-widgets.slint"; 2 2 import { Theme } from "../theme.slint"; 3 3 import { PaperDivider, SettingLabel } from "../components/atoms.slint"; 4 + import { ProfileChips } from "../components/chips.slint"; 4 5 import { PaperToggle } from "../components/toggles.slint"; 5 6 import { NumberField, PaperInput } from "../components/inputs.slint"; 6 7 import { IntervalCard, LevelEntry } from "../components/interval_card.slint"; ··· 8 9 export { LevelEntry } 9 10 10 11 export component ProfileTab inherits ScrollView { 12 + in-out property <string> active-profile: ""; 13 + in property <[string]> profile-names; 14 + in property <[int]> profile-name-widths; 11 15 in-out property <[LevelEntry]> levels; 12 16 in-out property <bool> long-break-enabled: false; 13 17 in-out property <int> long-break-after-cycles: 3; ··· 18 22 callback level-changed(int, LevelEntry); 19 23 callback level-removed(int); 20 24 callback level-added; 25 + callback profile-changed(string); 21 26 22 27 vertical-stretch: 1; 23 28 ··· 27 32 padding-top: 24px; 28 33 padding-bottom: 16px; 29 34 spacing: 0px; 35 + 36 + HorizontalLayout { 37 + padding-bottom: 14px; 38 + spacing: 24px; 39 + 40 + SettingLabel { 41 + title: "Active profile"; 42 + description: "The cadence ioma follows today."; 43 + } 44 + 45 + Rectangle { horizontal-stretch: 1; } 46 + 47 + ProfileChips { 48 + profile-names: root.profile-names; 49 + name-widths: root.profile-name-widths; 50 + active-profile <=> root.active-profile; 51 + selection-changed(name) => { 52 + root.profile-changed(name); 53 + } 54 + } 55 + } 56 + 57 + PaperDivider { } 58 + Rectangle { height: 20px; } 30 59 31 60 Text { 32 61 text: "Break cadence";
+163 -178
ui/views/rhythm_tab.slint
··· 1 1 import { ScrollView } from "std-widgets.slint"; 2 2 import { Theme } from "../theme.slint"; 3 - import { SettingLabel, PaperDivider } from "../components/atoms.slint"; 3 + import { SettingLabel, PaperDivider, SectionHeading } from "../components/atoms.slint"; 4 4 import { PaperButton } from "../components/buttons.slint"; 5 - import { ChipGroup, ProfileChips } from "../components/chips.slint"; 5 + import { ChipGroup } from "../components/chips.slint"; 6 6 import { NumberField, VolumeSlider } from "../components/inputs.slint"; 7 7 import { PaperToggle } from "../components/toggles.slint"; 8 8 ··· 13 13 in-out property <bool> autostart: false; 14 14 in-out property <bool> idle-detection-enabled: true; 15 15 in-out property <int> idle-threshold-mins: 5; 16 - in-out property <string> active-profile: ""; 17 - in property <[string]> profile-names; 18 - in property <[int]> profile-name-widths; 19 16 in-out property <int> theme-mode: 0; 20 17 in-out property <int> text-size-mode: 0; 21 18 22 19 callback set-password-clicked; 23 20 callback open-config-dir; 24 - callback profile-changed(string); 25 21 callback theme-mode-changed(int); 26 22 callback text-size-mode-changed(int); 27 23 ··· 30 26 VerticalLayout { 31 27 padding-left: 28px; 32 28 padding-right: 28px; 33 - padding-top: 20px; 29 + padding-top: 24px; 34 30 padding-bottom: 16px; 35 31 spacing: 0px; 36 32 37 - HorizontalLayout { 38 - padding-top: 10px; 39 - padding-bottom: 14px; 40 - spacing: 24px; 33 + SectionHeading { 34 + title: "During breaks"; 35 + description: "How firmly ioma holds the pause when it is time to rest."; 36 + } 41 37 42 - SettingLabel { 43 - title: "Active profile"; 44 - description: "The cadence ioma follows today."; 45 - } 38 + Rectangle { height: 12px; } 46 39 47 - Rectangle { 48 - horizontal-stretch: 1; 49 - } 40 + VerticalLayout { 41 + spacing: 0px; 50 42 51 - ProfileChips { 52 - profile-names: root.profile-names; 53 - name-widths: root.profile-name-widths; 54 - active-profile <=> root.active-profile; 55 - selection-changed(name) => { 56 - root.profile-changed(name); 57 - } 58 - } 59 - } 43 + HorizontalLayout { 44 + padding-top: 12px; 45 + padding-bottom: 4px; 46 + spacing: 24px; 60 47 61 - PaperDivider { } 48 + SettingLabel { 49 + title: "Enforced mode"; 50 + description: "Full-screen break. Emergency unlock requires a password."; 51 + } 62 52 63 - HorizontalLayout { 64 - padding-top: 12px; 65 - padding-bottom: 4px; 66 - spacing: 24px; 53 + Rectangle { horizontal-stretch: 1; } 67 54 68 - SettingLabel { 69 - title: "Enforced mode"; 70 - description: "Full-screen break. Emergency unlock requires a password."; 55 + PaperToggle { 56 + checked <=> root.enforced-mode; 57 + label: "Enforced mode"; 58 + } 71 59 } 72 60 73 - Rectangle { 74 - horizontal-stretch: 1; 61 + HorizontalLayout { 62 + padding-bottom: 12px; 63 + 64 + PaperButton { 65 + text: "Set emergency unlock password…"; 66 + preferred-width: 220px; 67 + clicked => { 68 + root.set-password-clicked(); 69 + } 70 + } 75 71 } 76 72 77 - PaperToggle { 78 - checked <=> root.enforced-mode; 79 - label: "Enforced mode"; 80 - } 81 - } 73 + HorizontalLayout { 74 + padding-top: 12px; 75 + padding-bottom: 4px; 76 + spacing: 24px; 77 + 78 + SettingLabel { 79 + title: "Pause on idle"; 80 + description: "If you've stepped away, ioma waits."; 81 + } 82 82 83 - HorizontalLayout { 84 - padding-bottom: 12px; 83 + Rectangle { horizontal-stretch: 1; } 85 84 86 - PaperButton { 87 - text: "Set emergency unlock password…"; 88 - preferred-width: 220px; 89 - clicked => { 90 - root.set-password-clicked(); 85 + PaperToggle { 86 + checked <=> root.idle-detection-enabled; 87 + label: "Pause on idle"; 91 88 } 92 89 } 93 - } 94 90 95 - PaperDivider { } 91 + HorizontalLayout { 92 + padding-bottom: 12px; 93 + spacing: 10px; 94 + alignment: start; 96 95 97 - HorizontalLayout { 98 - padding-top: 12px; 99 - padding-bottom: 4px; 100 - spacing: 24px; 96 + Text { 97 + text: "for"; 98 + font-size: Theme.font_label; 99 + color: Theme.ink-mid; 100 + vertical-alignment: center; 101 + } 101 102 102 - SettingLabel { 103 - title: "Pause on idle"; 104 - description: "If you've stepped away, ioma waits."; 105 - } 103 + NumberField { 104 + width: 80px; 105 + value <=> root.idle-threshold-mins; 106 + minimum: 1; 107 + maximum: 60; 108 + field-label: "Idle threshold minutes"; 109 + } 106 110 107 - Rectangle { 108 - horizontal-stretch: 1; 109 - } 110 - 111 - PaperToggle { 112 - checked <=> root.idle-detection-enabled; 113 - label: "Pause on idle"; 111 + Text { 112 + text: "minutes before resetting"; 113 + font-size: Theme.font_label; 114 + color: Theme.ink-mid; 115 + vertical-alignment: center; 116 + } 114 117 } 115 118 } 116 119 117 - HorizontalLayout { 118 - padding-bottom: 12px; 119 - spacing: 10px; 120 - alignment: start; 120 + Rectangle { height: 28px; } 121 121 122 - Text { 123 - text: "for"; 124 - font-size: Theme.font_label; 125 - color: Theme.ink-mid; 126 - vertical-alignment: center; 127 - } 122 + SectionHeading { 123 + title: "Sensory experience"; 124 + description: "Sound, appearance, and readability across the overlay and settings."; 125 + } 128 126 129 - NumberField { 130 - width: 80px; 131 - value <=> root.idle-threshold-mins; 132 - minimum: 1; 133 - maximum: 60; 134 - field-label: "Idle threshold minutes"; 135 - } 127 + Rectangle { height: 12px; } 136 128 137 - Text { 138 - text: "minutes before resetting"; 139 - font-size: Theme.font_label; 140 - color: Theme.ink-mid; 141 - vertical-alignment: center; 142 - } 143 - } 129 + VerticalLayout { 130 + spacing: 0px; 144 131 145 - PaperDivider { } 132 + HorizontalLayout { 133 + padding-top: 12px; 134 + padding-bottom: 4px; 135 + spacing: 24px; 146 136 147 - HorizontalLayout { 148 - padding-top: 12px; 149 - padding-bottom: 4px; 150 - spacing: 24px; 137 + SettingLabel { 138 + title: "Chime on break start"; 139 + description: "A single, gentle tone."; 140 + } 151 141 152 - SettingLabel { 153 - title: "Chime on break start"; 154 - description: "A single, gentle tone."; 155 - } 142 + Rectangle { horizontal-stretch: 1; } 156 143 157 - Rectangle { 158 - horizontal-stretch: 1; 144 + PaperToggle { 145 + checked <=> root.sound-enabled; 146 + label: "Chime on break start"; 147 + } 159 148 } 160 149 161 - PaperToggle { 162 - checked <=> root.sound-enabled; 163 - label: "Chime on break start"; 164 - } 165 - } 150 + HorizontalLayout { 151 + padding-bottom: 12px; 152 + spacing: 12px; 166 153 167 - HorizontalLayout { 168 - padding-bottom: 12px; 169 - spacing: 12px; 154 + Text { 155 + text: "volume"; 156 + font-size: Theme.font_label; 157 + color: Theme.ink-mid; 158 + vertical-alignment: center; 159 + } 170 160 171 - Text { 172 - text: "volume"; 173 - font-size: Theme.font_label; 174 - color: Theme.ink-mid; 175 - vertical-alignment: center; 161 + VolumeSlider { 162 + value <=> root.sound-volume; 163 + horizontal-stretch: 1; 164 + } 176 165 } 177 166 178 - VolumeSlider { 179 - value <=> root.sound-volume; 180 - horizontal-stretch: 1; 181 - } 182 - } 167 + HorizontalLayout { 168 + padding-top: 12px; 169 + padding-bottom: 10px; 170 + spacing: 24px; 183 171 184 - PaperDivider { } 172 + SettingLabel { 173 + title: "Appearance"; 174 + description: "Changes the look of the overlay and settings."; 175 + } 185 176 186 - HorizontalLayout { 187 - padding-top: 12px; 188 - padding-bottom: 10px; 189 - spacing: 24px; 177 + Rectangle { horizontal-stretch: 1; } 190 178 191 - SettingLabel { 192 - title: "Launch with system"; 193 - description: "ioma starts quietly at login."; 179 + ChipGroup { 180 + labels: ["System", "Light", "Dark"]; 181 + selected-index <=> root.theme-mode; 182 + selection-changed(i) => { 183 + root.theme-mode-changed(i); 184 + } 185 + } 194 186 } 195 187 196 - Rectangle { 197 - horizontal-stretch: 1; 198 - } 188 + HorizontalLayout { 189 + padding-top: 12px; 190 + padding-bottom: 10px; 191 + spacing: 24px; 199 192 200 - PaperToggle { 201 - checked <=> root.autostart; 202 - label: "Launch with system"; 203 - } 204 - } 193 + SettingLabel { 194 + title: "Text size"; 195 + description: "Adjusts readability throughout the app."; 196 + } 205 197 206 - HorizontalLayout { 207 - padding-bottom: 4px; 198 + Rectangle { horizontal-stretch: 1; } 208 199 209 - PaperButton { 210 - text: "Open config file location"; 211 - preferred-width: 180px; 212 - clicked => { 213 - root.open-config-dir(); 200 + ChipGroup { 201 + labels: ["Smaller", "Small", "Default", "Large", "Larger"]; 202 + selected-index <=> root.text-size-mode; 203 + selection-changed(i) => { 204 + root.text-size-mode-changed(i); 205 + } 214 206 } 215 207 } 216 208 } 217 209 218 - PaperDivider { } 210 + Rectangle { height: 28px; } 211 + 212 + SectionHeading { 213 + title: "On this computer"; 214 + description: "Startup behavior and quick access to local configuration."; 215 + } 219 216 220 - HorizontalLayout { 221 - padding-top: 12px; 222 - padding-bottom: 10px; 223 - spacing: 24px; 217 + Rectangle { height: 12px; } 224 218 225 - SettingLabel { 226 - title: "Appearance"; 227 - description: "Follows system by default."; 228 - } 219 + VerticalLayout { 220 + spacing: 0px; 229 221 230 - Rectangle { 231 - horizontal-stretch: 1; 232 - } 222 + HorizontalLayout { 223 + padding-top: 12px; 224 + padding-bottom: 10px; 225 + spacing: 24px; 233 226 234 - ChipGroup { 235 - labels: ["System", "Light", "Dark"]; 236 - selected-index <=> root.theme-mode; 237 - selection-changed(i) => { 238 - root.theme-mode-changed(i); 227 + SettingLabel { 228 + title: "Launch with system"; 229 + description: "ioma starts quietly at login."; 239 230 } 240 - } 241 - } 242 231 243 - PaperDivider { } 244 - 245 - HorizontalLayout { 246 - padding-top: 12px; 247 - padding-bottom: 10px; 248 - spacing: 24px; 232 + Rectangle { horizontal-stretch: 1; } 249 233 250 - SettingLabel { 251 - title: "Text size"; 252 - description: "Adjusts all UI text for readability."; 234 + PaperToggle { 235 + checked <=> root.autostart; 236 + label: "Launch with system"; 237 + } 253 238 } 254 239 255 - Rectangle { 256 - horizontal-stretch: 1; 257 - } 240 + HorizontalLayout { 241 + padding-bottom: 12px; 258 242 259 - ChipGroup { 260 - labels: ["Smaller", "Small", "Default", "Large", "Larger"]; 261 - selected-index <=> root.text-size-mode; 262 - selection-changed(i) => { 263 - root.text-size-mode-changed(i); 243 + PaperButton { 244 + text: "Open config file location"; 245 + preferred-width: 180px; 246 + clicked => { 247 + root.open-config-dir(); 248 + } 264 249 } 265 250 } 266 251 }