|
| 1 | +from mpos.apps import Activity, Intent |
| 2 | +import mpos.config |
| 3 | +import mpos.ui |
| 4 | + |
| 5 | +class Hello(Activity): |
| 6 | + |
| 7 | + def onCreate(self): |
| 8 | + screen = lv.obj() |
| 9 | + label = lv.label(screen) |
| 10 | + label.set_text('Hello World!') |
| 11 | + label.center() |
| 12 | + self.setContentView(screen) |
| 13 | + |
| 14 | + |
| 15 | +# Used to list and edit all settings: |
| 16 | +class SettingsActivity(Activity): |
| 17 | + def __init__(self): |
| 18 | + super().__init__() |
| 19 | + self.prefs = None |
| 20 | + self.settings = [ |
| 21 | + {"title": "Light/Dark Theme", "key": "light_dark_theme", "value_label": None, "cont": None}, |
| 22 | + {"title": "Theme Color", "key": "theme_color", "value_label": None, "cont": None, "placeholder": "HTML hex color, like: EC048C"}, |
| 23 | + {"title": "Reboot into Bootloader", "key": "boot_mode", "value_label": None, "cont": None}, |
| 24 | + {"title": "Display Brightness", "key": "display_brightness", "value_label": None, "cont": None, "placeholder": "A value from 0 to 100."}, |
| 25 | + {"title": "Timezone", "key": "timezone", "value_label": None, "cont": None, "placeholder": "Example: Europe/Prague"}, |
| 26 | + ] |
| 27 | + |
| 28 | + def onCreate(self): |
| 29 | + screen = lv.obj() |
| 30 | + print("creating SettingsActivity ui...") |
| 31 | + screen.set_style_pad_all(mpos.ui.pct_of_display_width(2), 0) |
| 32 | + screen.set_flex_flow(lv.FLEX_FLOW.COLUMN) |
| 33 | + screen.set_style_border_width(0, 0) |
| 34 | + self.setContentView(screen) |
| 35 | + |
| 36 | + def onResume(self, screen): |
| 37 | + # reload settings because the SettingsActivity might have changed them - could be optimized to only load if it did: |
| 38 | + self.prefs = mpos.config.SharedPreferences("com.micropythonos.settings") |
| 39 | + #wallet_type = self.prefs.get_string("wallet_type") # unused |
| 40 | + |
| 41 | + # Create settings entries |
| 42 | + screen.clean() |
| 43 | + for setting in self.settings: |
| 44 | + # Container for each setting |
| 45 | + setting_cont = lv.obj(screen) |
| 46 | + setting_cont.set_width(lv.pct(100)) |
| 47 | + setting_cont.set_height(lv.SIZE_CONTENT) |
| 48 | + setting_cont.set_style_border_width(1, 0) |
| 49 | + setting_cont.set_style_border_side(lv.BORDER_SIDE.BOTTOM, 0) |
| 50 | + setting_cont.set_style_pad_all(mpos.ui.pct_of_display_width(2), 0) |
| 51 | + setting_cont.add_flag(lv.obj.FLAG.CLICKABLE) |
| 52 | + setting["cont"] = setting_cont # Store container reference for visibility control |
| 53 | + |
| 54 | + # Title label (bold, larger) |
| 55 | + title = lv.label(setting_cont) |
| 56 | + title.set_text(setting["title"]) |
| 57 | + title.set_style_text_font(lv.font_montserrat_16, 0) |
| 58 | + title.set_pos(0, 0) |
| 59 | + |
| 60 | + # Value label (smaller, below title) |
| 61 | + value = lv.label(setting_cont) |
| 62 | + value.set_text(self.prefs.get_string(setting["key"], "(not set)")) |
| 63 | + value.set_style_text_font(lv.font_montserrat_12, 0) |
| 64 | + value.set_style_text_color(lv.color_hex(0x666666), 0) |
| 65 | + value.set_pos(0, 20) |
| 66 | + setting["value_label"] = value # Store reference for updating |
| 67 | + setting_cont.add_event_cb( |
| 68 | + lambda e, s=setting: self.startSettingActivity(s), lv.EVENT.CLICKED, None |
| 69 | + ) |
| 70 | + |
| 71 | + def startSettingActivity(self, setting): |
| 72 | + intent = Intent(activity_class=SettingActivity) |
| 73 | + intent.putExtra("setting", setting) |
| 74 | + self.startActivity(intent) |
| 75 | + |
| 76 | +# Used to edit one setting: |
| 77 | +class SettingActivity(Activity): |
| 78 | + |
| 79 | + keyboard = None |
| 80 | + textarea = None |
| 81 | + radio_container = None |
| 82 | + active_radio_index = 0 # Track active radio button index |
| 83 | + |
| 84 | + def __init__(self): |
| 85 | + super().__init__() |
| 86 | + self.prefs = mpos.config.SharedPreferences("com.micropythonos.settings") |
| 87 | + self.setting = None |
| 88 | + |
| 89 | + def onCreate(self): |
| 90 | + setting = self.getIntent().extras.get("setting") |
| 91 | + settings_screen_detail = lv.obj() |
| 92 | + settings_screen_detail.set_style_pad_all(mpos.ui.pct_of_display_width(2), 0) |
| 93 | + settings_screen_detail.set_flex_flow(lv.FLEX_FLOW.COLUMN) |
| 94 | + |
| 95 | + top_cont = lv.obj(settings_screen_detail) |
| 96 | + top_cont.set_width(lv.pct(100)) |
| 97 | + top_cont.set_style_border_width(0, 0) |
| 98 | + top_cont.set_height(lv.SIZE_CONTENT) |
| 99 | + top_cont.set_style_pad_all(mpos.ui.pct_of_display_width(1), 0) |
| 100 | + top_cont.set_flex_flow(lv.FLEX_FLOW.ROW) |
| 101 | + top_cont.set_style_flex_main_place(lv.FLEX_ALIGN.SPACE_BETWEEN, 0) |
| 102 | + |
| 103 | + setting_label = lv.label(top_cont) |
| 104 | + setting_label.set_text(setting["title"]) |
| 105 | + setting_label.align(lv.ALIGN.TOP_LEFT,0,0) |
| 106 | + setting_label.set_style_text_font(lv.font_montserrat_26, 0) |
| 107 | + |
| 108 | + if setting["key"] == "light_dark_theme" or setting["key"] == "boot_mode": |
| 109 | + # Create container for radio buttons |
| 110 | + self.radio_container = lv.obj(settings_screen_detail) |
| 111 | + self.radio_container.set_width(lv.pct(100)) |
| 112 | + self.radio_container.set_height(lv.SIZE_CONTENT) |
| 113 | + self.radio_container.set_flex_flow(lv.FLEX_FLOW.COLUMN) |
| 114 | + self.radio_container.add_event_cb(self.radio_event_handler, lv.EVENT.CLICKED, None) |
| 115 | + |
| 116 | + # Create radio buttons |
| 117 | + if setting["key"] == "boot_mode": |
| 118 | + options = [("Normal", "normal"), ("Bootloader", "bootloader")] |
| 119 | + else: |
| 120 | + options = [("Light", "light"), ("Dark", "dark")] |
| 121 | + current_setting = self.prefs.get_string(setting["key"]) |
| 122 | + self.active_radio_index = -1 # none |
| 123 | + if current_setting == options[0][1]: |
| 124 | + self.active_radio_index = 0 |
| 125 | + elif current_setting == options[1][1]: |
| 126 | + self.active_radio_index = 1 |
| 127 | + |
| 128 | + for i, (text, _) in enumerate(options): |
| 129 | + cb = self.create_radio_button(self.radio_container, text, i) |
| 130 | + if i == self.active_radio_index: |
| 131 | + cb.add_state(lv.STATE.CHECKED) |
| 132 | + else: |
| 133 | + # Textarea for other settings |
| 134 | + self.textarea = lv.textarea(settings_screen_detail) |
| 135 | + self.textarea.set_width(lv.pct(100)) |
| 136 | + self.textarea.set_height(lv.SIZE_CONTENT) |
| 137 | + self.textarea.align_to(top_cont, lv.ALIGN.OUT_BOTTOM_MID, 0, 0) |
| 138 | + current = self.prefs.get_string(setting["key"]) |
| 139 | + if current: |
| 140 | + self.textarea.set_text(current) |
| 141 | + placeholder = setting.get("placeholder") |
| 142 | + if placeholder: |
| 143 | + self.textarea.set_placeholder_text(placeholder) |
| 144 | + self.textarea.add_event_cb(lambda *args: mpos.ui.anim.smooth_show(self.keyboard), lv.EVENT.CLICKED, None) # it might be focused, but keyboard hidden (because ready/cancel clicked) |
| 145 | + self.textarea.add_event_cb(lambda *args: mpos.ui.anim.smooth_hide(self.keyboard), lv.EVENT.DEFOCUSED, None) |
| 146 | + # Initialize keyboard (hidden initially) |
| 147 | + self.keyboard = lv.keyboard(lv.layer_sys()) |
| 148 | + self.keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0) |
| 149 | + self.keyboard.set_style_min_height(150, 0) |
| 150 | + self.keyboard.add_flag(lv.obj.FLAG.HIDDEN) |
| 151 | + self.keyboard.add_event_cb(lambda *args: mpos.ui.anim.smooth_hide(self.keyboard), lv.EVENT.READY, None) |
| 152 | + self.keyboard.add_event_cb(lambda *args: mpos.ui.anim.smooth_hide(self.keyboard), lv.EVENT.CANCEL, None) |
| 153 | + self.keyboard.set_textarea(self.textarea) |
| 154 | + |
| 155 | + # Button container |
| 156 | + btn_cont = lv.obj(settings_screen_detail) |
| 157 | + btn_cont.set_width(lv.pct(100)) |
| 158 | + btn_cont.set_style_border_width(0, 0) |
| 159 | + btn_cont.set_height(lv.SIZE_CONTENT) |
| 160 | + btn_cont.set_flex_flow(lv.FLEX_FLOW.ROW) |
| 161 | + btn_cont.set_style_flex_main_place(lv.FLEX_ALIGN.SPACE_BETWEEN, 0) |
| 162 | + # Save button |
| 163 | + save_btn = lv.button(btn_cont) |
| 164 | + save_btn.set_size(lv.pct(45), lv.SIZE_CONTENT) |
| 165 | + save_label = lv.label(save_btn) |
| 166 | + save_label.set_text("Save") |
| 167 | + save_label.center() |
| 168 | + save_btn.add_event_cb(lambda e, s=setting: self.save_setting(s), lv.EVENT.CLICKED, None) |
| 169 | + # Cancel button |
| 170 | + cancel_btn = lv.button(btn_cont) |
| 171 | + cancel_btn.set_size(lv.pct(45), lv.SIZE_CONTENT) |
| 172 | + cancel_label = lv.label(cancel_btn) |
| 173 | + cancel_label.set_text("Cancel") |
| 174 | + cancel_label.center() |
| 175 | + cancel_btn.add_event_cb(lambda e: self.finish(), lv.EVENT.CLICKED, None) |
| 176 | + |
| 177 | + if False: # No scan QR button for text settings because they're all short right now |
| 178 | + cambutton = lv.button(settings_screen_detail) |
| 179 | + cambutton.align(lv.ALIGN.BOTTOM_MID,0,0) |
| 180 | + cambutton.set_size(lv.pct(100), lv.pct(30)) |
| 181 | + cambuttonlabel = lv.label(cambutton) |
| 182 | + cambuttonlabel.set_text("Scan data from QR code") |
| 183 | + cambuttonlabel.set_style_text_font(lv.font_montserrat_18, 0) |
| 184 | + cambuttonlabel.align(lv.ALIGN.TOP_MID, 0, 0) |
| 185 | + cambuttonlabel2 = lv.label(cambutton) |
| 186 | + cambuttonlabel2.set_text("Tip: Create your own QR code,\nusing https://genqrcode.com or another tool.") |
| 187 | + cambuttonlabel2.set_style_text_font(lv.font_montserrat_10, 0) |
| 188 | + cambuttonlabel2.align(lv.ALIGN.BOTTOM_MID, 0, 0) |
| 189 | + cambutton.add_event_cb(self.cambutton_cb, lv.EVENT.CLICKED, None) |
| 190 | + |
| 191 | + self.setContentView(settings_screen_detail) |
| 192 | + |
| 193 | + def onStop(self, screen): |
| 194 | + if self.keyboard: |
| 195 | + mpos.ui.anim.smooth_hide(self.keyboard) |
| 196 | + |
| 197 | + def radio_event_handler(self, event): |
| 198 | + print("radio_event_handler called") |
| 199 | + if self.active_radio_index >= 0: |
| 200 | + print(f"removing old CHECKED state from child {self.active_radio_index}") |
| 201 | + old_cb = self.radio_container.get_child(self.active_radio_index) |
| 202 | + old_cb.remove_state(lv.STATE.CHECKED) |
| 203 | + self.active_radio_index = -1 |
| 204 | + for childnr in range(self.radio_container.get_child_count()): |
| 205 | + child = self.radio_container.get_child(childnr) |
| 206 | + state = child.get_state() |
| 207 | + print(f"radio_container child's state: {state}") |
| 208 | + if state & lv.STATE.CHECKED: # State can be something like 19 = lv.STATE.HOVERED (16) & lv.STATE.FOCUSED (2) & lv.STATE.CHECKED (1) |
| 209 | + self.active_radio_index = childnr |
| 210 | + break |
| 211 | + print(f"active_radio_index is now {self.active_radio_index}") |
| 212 | + |
| 213 | + def create_radio_button(self, parent, text, index): |
| 214 | + cb = lv.checkbox(parent) |
| 215 | + cb.set_text(text) |
| 216 | + cb.add_flag(lv.obj.FLAG.EVENT_BUBBLE) |
| 217 | + # Add circular style to indicator for radio button appearance |
| 218 | + style_radio = lv.style_t() |
| 219 | + style_radio.init() |
| 220 | + style_radio.set_radius(lv.RADIUS_CIRCLE) |
| 221 | + cb.add_style(style_radio, lv.PART.INDICATOR) |
| 222 | + style_radio_chk = lv.style_t() |
| 223 | + style_radio_chk.init() |
| 224 | + style_radio_chk.set_bg_image_src(None) |
| 225 | + cb.add_style(style_radio_chk, lv.PART.INDICATOR | lv.STATE.CHECKED) |
| 226 | + return cb |
| 227 | + |
| 228 | + def gotqr_result_callback_unused(self, result): |
| 229 | + print(f"QR capture finished, result: {result}") |
| 230 | + if result.get("result_code"): |
| 231 | + data = result.get("data") |
| 232 | + print(f"Setting textarea data: {data}") |
| 233 | + self.textarea.set_text(data) |
| 234 | + |
| 235 | + def cambutton_cb_unused(self, event): |
| 236 | + print("cambutton clicked!") |
| 237 | + self.startActivityForResult(Intent(activity_class=CameraApp).putExtra("scanqr_mode", True), self.gotqr_result_callback) |
| 238 | + |
| 239 | + def save_setting(self, setting): |
| 240 | + if ( setting["key"] =="light_dark_theme" or setting["key"] == "boot_mode" ) and self.radio_container: |
| 241 | + if setting["key"] == "boot_mode": |
| 242 | + options = [("Normal", "normal"), ("Bootloader", "bootloader")] |
| 243 | + else: |
| 244 | + options = [("Light", "light"), ("Dark", "dark")] |
| 245 | + selected_idx = self.active_radio_index |
| 246 | + if selected_idx == 0: |
| 247 | + new_value = options[0][1] |
| 248 | + elif selected_idx == 1: |
| 249 | + new_value = options[1][1] |
| 250 | + else: |
| 251 | + return # nothing to save |
| 252 | + elif self.textarea: |
| 253 | + new_value = self.textarea.get_text() |
| 254 | + else: |
| 255 | + new_value = "" |
| 256 | + editor = self.prefs.edit() |
| 257 | + editor.put_string(setting["key"], new_value) |
| 258 | + editor.commit() |
| 259 | + setting["value_label"].set_text(new_value if new_value else "(not set)") |
| 260 | + self.finish() |
0 commit comments