|
| 1 | +# SettingActivity |
| 2 | + |
| 3 | +`SettingActivity` is used to edit a single setting with various UI options. It provides a flexible framework for collecting user input with support for text input, radio buttons, dropdowns, and custom Activity implementations. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +`SettingActivity` displays a single setting that the user can modify. The setting is passed via Intent extras and can be configured with different UI types. When the user saves the setting, it's automatically persisted to SharedPreferences (unless `dont_persist` is set to true). |
| 8 | + |
| 9 | +## Basic Usage |
| 10 | + |
| 11 | +```python |
| 12 | +from mpos import Activity, Intent, SettingActivity, SharedPreferences |
| 13 | + |
| 14 | +class MyApp(Activity): |
| 15 | + def onCreate(self): |
| 16 | + self.prefs = SharedPreferences("com.example.myapp") |
| 17 | + # ... create UI ... |
| 18 | + |
| 19 | + def open_setting(self): |
| 20 | + intent = Intent(activity_class=SettingActivity) |
| 21 | + intent.putExtra("prefs", self.prefs) |
| 22 | + intent.putExtra("setting", { |
| 23 | + "title": "Your Name", |
| 24 | + "key": "user_name", |
| 25 | + "placeholder": "Enter your name" |
| 26 | + }) |
| 27 | + self.startActivity(intent) |
| 28 | +``` |
| 29 | + |
| 30 | +## Setting Dictionary Structure |
| 31 | + |
| 32 | +Each setting is defined as a dictionary with the following properties: |
| 33 | + |
| 34 | +### Required Properties |
| 35 | +- **`title`** (string): Display name of the setting shown at the top |
| 36 | +- **`key`** (string): Unique identifier used as the SharedPreferences key |
| 37 | + |
| 38 | +### Optional Properties |
| 39 | +- **`ui`** (string): UI type to use for editing. Options: `"textarea"` (default), `"radiobuttons"`, `"dropdown"`, `"activity"` |
| 40 | +- **`ui_options`** (list): Options for `radiobuttons` and `dropdown` UI types |
| 41 | +- **`placeholder`** (string): Placeholder text for textarea input |
| 42 | +- **`changed_callback`** (function): Callback function called when the setting value changes |
| 43 | +- **`should_show`** (function): Function to determine if this setting should be displayed |
| 44 | +- **`dont_persist`** (bool): If `True`, the setting won't be saved to SharedPreferences |
| 45 | +- **`activity_class`** (class): Custom Activity class for `"activity"` UI type |
| 46 | +- **`value_label`** (widget): Internal reference to the value label (set by SettingsActivity) |
| 47 | +- **`cont`** (widget): Internal reference to the container (set by SettingsActivity) |
| 48 | + |
| 49 | +## UI Types |
| 50 | + |
| 51 | +### 1. Textarea (Default) |
| 52 | + |
| 53 | +Text input with an on-screen keyboard and optional QR code scanner. |
| 54 | + |
| 55 | +**When to use:** For text input like URLs, API keys, names, etc. |
| 56 | + |
| 57 | +**Example:** |
| 58 | +```python |
| 59 | +{ |
| 60 | + "title": "API Key", |
| 61 | + "key": "api_key", |
| 62 | + "placeholder": "Enter your API key", |
| 63 | + "ui": "textarea" # or omit this, textarea is default |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +**Features:** |
| 68 | +- On-screen keyboard for text input |
| 69 | +- QR code scanner button (for scanning data from QR codes) |
| 70 | +- Placeholder text support |
| 71 | +- Single-line input |
| 72 | + |
| 73 | +### 2. Radio Buttons |
| 74 | + |
| 75 | +Mutually exclusive selection from a list of options. |
| 76 | + |
| 77 | +**When to use:** For choosing one option from a small set of choices. |
| 78 | + |
| 79 | +**Format for `ui_options`:** |
| 80 | +```python |
| 81 | +ui_options = [ |
| 82 | + ("Display Label 1", "value1"), |
| 83 | + ("Display Label 2", "value2"), |
| 84 | + ("Display Label 3", "value3") |
| 85 | +] |
| 86 | +``` |
| 87 | + |
| 88 | +**Example:** |
| 89 | +```python |
| 90 | +{ |
| 91 | + "title": "Theme", |
| 92 | + "key": "theme", |
| 93 | + "ui": "radiobuttons", |
| 94 | + "ui_options": [ |
| 95 | + ("Light", "light"), |
| 96 | + ("Dark", "dark"), |
| 97 | + ("Auto", "auto") |
| 98 | + ] |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +**Features:** |
| 103 | +- Circular radio button indicators |
| 104 | +- Only one option can be selected at a time |
| 105 | +- Displays both label and value (if different) |
| 106 | + |
| 107 | +### 3. Dropdown |
| 108 | + |
| 109 | +Dropdown selection from a list of options. |
| 110 | + |
| 111 | +**When to use:** For choosing one option from a larger set of choices. |
| 112 | + |
| 113 | +**Format for `ui_options`:** |
| 114 | +```python |
| 115 | +ui_options = [ |
| 116 | + ("Display Label 1", "value1"), |
| 117 | + ("Display Label 2", "value2"), |
| 118 | + ("Display Label 3", "value3") |
| 119 | +] |
| 120 | +``` |
| 121 | + |
| 122 | +**Example:** |
| 123 | +```python |
| 124 | +{ |
| 125 | + "title": "Language", |
| 126 | + "key": "language", |
| 127 | + "ui": "dropdown", |
| 128 | + "ui_options": [ |
| 129 | + ("English", "en"), |
| 130 | + ("German", "de"), |
| 131 | + ("French", "fr"), |
| 132 | + ("Spanish", "es") |
| 133 | + ] |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +**Features:** |
| 138 | +- Compact dropdown menu |
| 139 | +- Shows label and value if different |
| 140 | +- Automatically selects the current value |
| 141 | + |
| 142 | +### 4. Custom Activity |
| 143 | + |
| 144 | +Use a custom Activity class for advanced UI implementations. |
| 145 | + |
| 146 | +**When to use:** For complex settings that need custom UI beyond the built-in options. |
| 147 | + |
| 148 | +**Example:** |
| 149 | +```python |
| 150 | +class ColorPickerActivity(Activity): |
| 151 | + def onCreate(self): |
| 152 | + # Custom color picker UI |
| 153 | + pass |
| 154 | + |
| 155 | +setting = { |
| 156 | + "title": "Pick a Color", |
| 157 | + "key": "color", |
| 158 | + "ui": "activity", |
| 159 | + "activity_class": ColorPickerActivity |
| 160 | +} |
| 161 | +``` |
| 162 | + |
| 163 | +**Requirements:** |
| 164 | +- Must provide `activity_class` parameter |
| 165 | +- The custom Activity receives the setting and prefs via Intent extras |
| 166 | +- Must call `self.finish()` to return to the previous screen |
| 167 | + |
| 168 | +## Callbacks and Advanced Features |
| 169 | + |
| 170 | +### changed_callback |
| 171 | + |
| 172 | +Called when the setting value changes (after saving). Useful for triggering UI updates or reloading data. |
| 173 | + |
| 174 | +**Example:** |
| 175 | +```python |
| 176 | +def on_theme_changed(new_value): |
| 177 | + print(f"Theme changed to: {new_value}") |
| 178 | + # Reload UI with new theme |
| 179 | + self.apply_theme(new_value) |
| 180 | + |
| 181 | +setting = { |
| 182 | + "title": "Theme", |
| 183 | + "key": "theme", |
| 184 | + "ui": "radiobuttons", |
| 185 | + "ui_options": [("Light", "light"), ("Dark", "dark")], |
| 186 | + "changed_callback": on_theme_changed |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +**Important:** The callback is only called if the value actually changed (old value != new value). |
| 191 | + |
| 192 | +### should_show |
| 193 | + |
| 194 | +Function to conditionally show/hide a setting. Used primarily in SettingsActivity. |
| 195 | + |
| 196 | +**Example:** |
| 197 | +```python |
| 198 | +def should_show_advanced_options(setting): |
| 199 | + prefs = SharedPreferences("com.example.app") |
| 200 | + return prefs.get_string("mode") == "advanced" |
| 201 | + |
| 202 | +setting = { |
| 203 | + "title": "Advanced Option", |
| 204 | + "key": "advanced_setting", |
| 205 | + "should_show": should_show_advanced_options |
| 206 | +} |
| 207 | +``` |
| 208 | + |
| 209 | +### dont_persist |
| 210 | + |
| 211 | +Prevent the setting from being saved to SharedPreferences. |
| 212 | + |
| 213 | +**Example:** |
| 214 | +```python |
| 215 | +setting = { |
| 216 | + "title": "Temporary Value", |
| 217 | + "key": "temp_value", |
| 218 | + "dont_persist": True |
| 219 | +} |
| 220 | +``` |
| 221 | + |
| 222 | +## Complete Example: Simple Settings App |
| 223 | + |
| 224 | +Here's a minimal app that uses SettingActivity to edit a single setting: |
| 225 | + |
| 226 | +```python |
| 227 | +import lvgl as lv |
| 228 | +from mpos import Activity, Intent, SettingActivity, SharedPreferences |
| 229 | + |
| 230 | +class SimpleSettingsApp(Activity): |
| 231 | + def onCreate(self): |
| 232 | + self.prefs = SharedPreferences("com.example.simplesettings") |
| 233 | + |
| 234 | + # Create main screen |
| 235 | + self.main_screen = lv.obj() |
| 236 | + self.main_screen.set_flex_flow(lv.FLEX_FLOW.COLUMN) |
| 237 | + |
| 238 | + # Display current value |
| 239 | + self.value_label = lv.label(self.main_screen) |
| 240 | + self.update_value_label() |
| 241 | + |
| 242 | + # Settings button |
| 243 | + settings_btn = lv.button(self.main_screen) |
| 244 | + settings_btn.set_size(lv.pct(80), lv.SIZE_CONTENT) |
| 245 | + settings_label = lv.label(settings_btn) |
| 246 | + settings_label.set_text("Edit Setting") |
| 247 | + settings_label.center() |
| 248 | + settings_btn.add_event_cb(self.open_settings, lv.EVENT.CLICKED, None) |
| 249 | + |
| 250 | + self.setContentView(self.main_screen) |
| 251 | + |
| 252 | + def onResume(self, screen): |
| 253 | + super().onResume(screen) |
| 254 | + self.update_value_label() |
| 255 | + |
| 256 | + def update_value_label(self): |
| 257 | + value = self.prefs.get_string("my_setting", "(not set)") |
| 258 | + self.value_label.set_text(f"Current value: {value}") |
| 259 | + |
| 260 | + def open_settings(self, event): |
| 261 | + intent = Intent(activity_class=SettingActivity) |
| 262 | + intent.putExtra("prefs", self.prefs) |
| 263 | + intent.putExtra("setting", { |
| 264 | + "title": "My Setting", |
| 265 | + "key": "my_setting", |
| 266 | + "placeholder": "Enter a value", |
| 267 | + "changed_callback": self.on_setting_changed |
| 268 | + }) |
| 269 | + self.startActivity(intent) |
| 270 | + |
| 271 | + def on_setting_changed(self, new_value): |
| 272 | + print(f"Setting changed to: {new_value}") |
| 273 | + self.update_value_label() |
| 274 | +``` |
| 275 | + |
| 276 | +## Real-World Example: AppStore Backend Selection |
| 277 | + |
| 278 | +This example is inspired by the AppStore app, showing how to use SettingActivity with radio buttons and a callback: |
| 279 | + |
| 280 | +```python |
| 281 | +import lvgl as lv |
| 282 | +from mpos import Activity, Intent, SettingActivity, SharedPreferences |
| 283 | + |
| 284 | +class AppStore(Activity): |
| 285 | + BACKENDS = [ |
| 286 | + ("MPOS GitHub", "github,https://apps.micropythonos.com/app_index.json"), |
| 287 | + ("BadgeHub Test", "badgehub,https://badgehub.p1m.nl/api/v3"), |
| 288 | + ("BadgeHub Prod", "badgehub,https://badge.why2025.org/api/v3") |
| 289 | + ] |
| 290 | + |
| 291 | + def onCreate(self): |
| 292 | + self.prefs = SharedPreferences("com.micropythonos.appstore") |
| 293 | + |
| 294 | + self.main_screen = lv.obj() |
| 295 | + self.main_screen.set_flex_flow(lv.FLEX_FLOW.COLUMN) |
| 296 | + |
| 297 | + # Settings button |
| 298 | + settings_btn = lv.button(self.main_screen) |
| 299 | + settings_btn.set_size(lv.pct(20), lv.pct(25)) |
| 300 | + settings_btn.align(lv.ALIGN.TOP_RIGHT, 0, 0) |
| 301 | + settings_label = lv.label(settings_btn) |
| 302 | + settings_label.set_text(lv.SYMBOL.SETTINGS) |
| 303 | + settings_label.center() |
| 304 | + settings_btn.add_event_cb(self.open_backend_settings, lv.EVENT.CLICKED, None) |
| 305 | + |
| 306 | + self.setContentView(self.main_screen) |
| 307 | + |
| 308 | + def open_backend_settings(self, event): |
| 309 | + intent = Intent(activity_class=SettingActivity) |
| 310 | + intent.putExtra("prefs", self.prefs) |
| 311 | + intent.putExtra("setting", { |
| 312 | + "title": "AppStore Backend", |
| 313 | + "key": "backend", |
| 314 | + "ui": "radiobuttons", |
| 315 | + "ui_options": self.BACKENDS, |
| 316 | + "changed_callback": self.backend_changed |
| 317 | + }) |
| 318 | + self.startActivity(intent) |
| 319 | + |
| 320 | + def backend_changed(self, new_value): |
| 321 | + print(f"Backend changed to {new_value}, refreshing app list...") |
| 322 | + # Trigger app list refresh with new backend |
| 323 | + self.refresh_app_list() |
| 324 | + |
| 325 | + def refresh_app_list(self): |
| 326 | + # Implementation to refresh the app list |
| 327 | + pass |
| 328 | +``` |
| 329 | + |
| 330 | +## Tips and Best Practices |
| 331 | + |
| 332 | +1. **Load prefs once in onCreate()**: Load SharedPreferences in your main Activity's `onCreate()` and pass it to SettingActivity. This is faster than loading it multiple times. |
| 333 | + |
| 334 | +2. **Use changed_callback for side effects**: If changing a setting requires reloading data or updating the UI, use `changed_callback` instead of checking the value on resume. |
| 335 | + |
| 336 | +3. **Validate input**: For textarea inputs, consider validating the input in your `changed_callback` before using it. |
| 337 | + |
| 338 | +4. **Use radio buttons for small sets**: Use radio buttons for 2-5 options, dropdown for larger lists. |
| 339 | + |
| 340 | +5. **Provide meaningful placeholders**: Help users understand what format is expected with clear placeholder text. |
| 341 | + |
| 342 | +6. **Consider conditional visibility**: Use `should_show` in SettingsActivity to hide settings that don't apply based on other settings. |
0 commit comments