Files
MicroPythonOS/internal_filesystem/builtin/apps/com.micropythonos.settings/assets/settings.py
T
2025-06-24 13:22:08 +02:00

290 lines
14 KiB
Python

from mpos.apps import Activity, ActivityNavigator, Intent
import mpos.config
import mpos.ui
# Used to list and edit all settings:
class SettingsActivity(Activity):
def __init__(self):
super().__init__()
self.prefs = None
theme_colors = [
("Aqua Blue", "00ffff"),
("Bitcoin Orange", "f0a010"),
("Coral Red", "ff7f50"),
("Dark Slate", "2f4f4f"),
("Forest Green", "228b22"),
("Piggy Pink", "ff69b4"),
("Matrix Green", "03a062"),
("Midnight Blue", "191970"),
("Nostr Purple", "ff00ff"),
("Saddle Brown", "8b4513"),
("Sky Blue", "87ceeb"),
("Solarized Yellow", "b58900"),
("Vivid Violet", "9f00ff"),
("Amethyst", "9966cc"),
("Burnt Orange", "cc5500"),
("Charcoal Gray", "36454f"),
("Crimson", "dc143c"),
("Emerald", "50c878"),
("Goldenrod", "daa520"),
("Indigo", "4b0082"),
("Lime", "00ff00"),
("Teal", "008080"),
("Turquoise", "40e0d0")
]
self.settings = [
{"title": "Light/Dark Theme", "key": "theme_light_dark", "value_label": None, "cont": None, "ui": "radiobuttons", "ui_options": [("Light", "light"), ("Dark", "dark")]},
{"title": "Theme Color", "key": "theme_primary_color", "value_label": None, "cont": None, "placeholder": "HTML hex color, like: EC048C", "ui": "dropdown", "ui_options": theme_colors},
{"title": "Restart to Bootloader", "key": "boot_mode", "value_label": None, "cont": None, "ui": "radiobuttons", "ui_options": [("Normal", "normal"), ("Bootloader", "bootloader")]}, # special that doesn't get saved
# This is currently only in the drawer but would make sense to have it here for completeness:
#{"title": "Display Brightness", "key": "display_brightness", "value_label": None, "cont": None, "placeholder": "A value from 0 to 100."},
# Maybe also add font size (but ideally then all fonts should scale up/down)
#{"title": "Timezone", "key": "timezone", "value_label": None, "cont": None, "placeholder": "Example: Europe/Prague"},
]
def onCreate(self):
screen = lv.obj()
print("creating SettingsActivity ui...")
screen.set_style_pad_all(mpos.ui.pct_of_display_width(2), 0)
screen.set_flex_flow(lv.FLEX_FLOW.COLUMN)
screen.set_style_border_width(0, 0)
self.setContentView(screen)
def onResume(self, screen):
# reload settings because the SettingsActivity might have changed them - could be optimized to only load if it did:
self.prefs = mpos.config.SharedPreferences("com.micropythonos.settings")
#wallet_type = self.prefs.get_string("wallet_type") # unused
# Create settings entries
screen.clean()
for setting in self.settings:
# Container for each setting
setting_cont = lv.obj(screen)
setting_cont.set_width(lv.pct(100))
setting_cont.set_height(lv.SIZE_CONTENT)
setting_cont.set_style_border_width(1, 0)
setting_cont.set_style_border_side(lv.BORDER_SIDE.BOTTOM, 0)
setting_cont.set_style_pad_all(mpos.ui.pct_of_display_width(2), 0)
setting_cont.add_flag(lv.obj.FLAG.CLICKABLE)
setting["cont"] = setting_cont # Store container reference for visibility control
# Title label (bold, larger)
title = lv.label(setting_cont)
title.set_text(setting["title"])
title.set_style_text_font(lv.font_montserrat_16, 0)
title.set_pos(0, 0)
# Value label (smaller, below title)
value = lv.label(setting_cont)
value.set_text(self.prefs.get_string(setting["key"], "(not set)"))
value.set_style_text_font(lv.font_montserrat_12, 0)
value.set_style_text_color(lv.color_hex(0x666666), 0)
value.set_pos(0, 20)
setting["value_label"] = value # Store reference for updating
setting_cont.add_event_cb(
lambda e, s=setting: self.startSettingActivity(s), lv.EVENT.CLICKED, None
)
def startSettingActivity(self, setting):
intent = Intent(activity_class=SettingActivity)
intent.putExtra("setting", setting)
self.startActivity(intent)
# Used to edit one setting:
class SettingActivity(Activity):
active_radio_index = 0 # Track active radio button index
# Widgets:
keyboard = None
textarea = None
dropdown = None
radio_container = None
def __init__(self):
super().__init__()
self.prefs = mpos.config.SharedPreferences("com.micropythonos.settings")
self.setting = None
def onCreate(self):
setting = self.getIntent().extras.get("setting")
settings_screen_detail = lv.obj()
settings_screen_detail.set_style_pad_all(mpos.ui.pct_of_display_width(2), 0)
settings_screen_detail.set_flex_flow(lv.FLEX_FLOW.COLUMN)
top_cont = lv.obj(settings_screen_detail)
top_cont.set_width(lv.pct(100))
top_cont.set_style_border_width(0, 0)
top_cont.set_height(lv.SIZE_CONTENT)
top_cont.set_style_pad_all(mpos.ui.pct_of_display_width(1), 0)
top_cont.set_flex_flow(lv.FLEX_FLOW.ROW)
top_cont.set_style_flex_main_place(lv.FLEX_ALIGN.SPACE_BETWEEN, 0)
setting_label = lv.label(top_cont)
setting_label.set_text(setting["title"])
setting_label.align(lv.ALIGN.TOP_LEFT,0,0)
setting_label.set_style_text_font(lv.font_montserrat_26, 0)
ui = setting.get("ui")
ui_options = setting.get("ui_options")
current_setting = self.prefs.get_string(setting["key"])
if ui and ui == "radiobuttons" and ui_options:
# Create container for radio buttons
self.radio_container = lv.obj(settings_screen_detail)
self.radio_container.set_width(lv.pct(100))
self.radio_container.set_height(lv.SIZE_CONTENT)
self.radio_container.set_flex_flow(lv.FLEX_FLOW.COLUMN)
self.radio_container.add_event_cb(self.radio_event_handler, lv.EVENT.CLICKED, None)
# Create radio buttons and check the right one
self.active_radio_index = -1 # none
for i, (option_text, option_value) in enumerate(ui_options):
cb = self.create_radio_button(self.radio_container, option_text, i)
if current_setting == option_value:
self.active_radio_index = i
cb.add_state(lv.STATE.CHECKED)
elif ui and ui == "dropdown" and ui_options:
self.dropdown = lv.dropdown(settings_screen_detail)
self.dropdown.set_width(lv.pct(100))
options_with_newlines = "\n".join(f"{option[0]} ({option[1]})" for option in ui_options)
self.dropdown.set_options(options_with_newlines)
# select the right one:
for i, (option_text, option_value) in enumerate(ui_options):
if current_setting == option_value:
self.dropdown.set_selected(i)
break # no need to check the rest because only one can be selected
else:
# Textarea for other settings
self.textarea = lv.textarea(settings_screen_detail)
self.textarea.set_width(lv.pct(100))
self.textarea.set_height(lv.SIZE_CONTENT)
self.textarea.align_to(top_cont, lv.ALIGN.OUT_BOTTOM_MID, 0, 0)
if current_setting:
self.textarea.set_text(current_setting)
placeholder = setting.get("placeholder")
if placeholder:
self.textarea.set_placeholder_text(placeholder)
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)
self.textarea.add_event_cb(lambda *args: mpos.ui.anim.smooth_hide(self.keyboard), lv.EVENT.DEFOCUSED, None)
# Initialize keyboard (hidden initially)
self.keyboard = lv.keyboard(lv.layer_sys())
self.keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
self.keyboard.set_style_min_height(150, 0)
self.keyboard.add_flag(lv.obj.FLAG.HIDDEN)
self.keyboard.add_event_cb(lambda *args: mpos.ui.anim.smooth_hide(self.keyboard), lv.EVENT.READY, None)
self.keyboard.add_event_cb(lambda *args: mpos.ui.anim.smooth_hide(self.keyboard), lv.EVENT.CANCEL, None)
self.keyboard.set_textarea(self.textarea)
# Button container
btn_cont = lv.obj(settings_screen_detail)
btn_cont.set_width(lv.pct(100))
btn_cont.set_style_border_width(0, 0)
btn_cont.set_height(lv.SIZE_CONTENT)
btn_cont.set_flex_flow(lv.FLEX_FLOW.ROW)
btn_cont.set_style_flex_main_place(lv.FLEX_ALIGN.SPACE_BETWEEN, 0)
# Save button
save_btn = lv.button(btn_cont)
save_btn.set_size(lv.pct(45), lv.SIZE_CONTENT)
save_label = lv.label(save_btn)
save_label.set_text("Save")
save_label.center()
save_btn.add_event_cb(lambda e, s=setting: self.save_setting(s), lv.EVENT.CLICKED, None)
# Cancel button
cancel_btn = lv.button(btn_cont)
cancel_btn.set_size(lv.pct(45), lv.SIZE_CONTENT)
cancel_label = lv.label(cancel_btn)
cancel_label.set_text("Cancel")
cancel_label.center()
cancel_btn.add_event_cb(lambda e: self.finish(), lv.EVENT.CLICKED, None)
if False: # No scan QR button for text settings because they're all short right now
cambutton = lv.button(settings_screen_detail)
cambutton.align(lv.ALIGN.BOTTOM_MID,0,0)
cambutton.set_size(lv.pct(100), lv.pct(30))
cambuttonlabel = lv.label(cambutton)
cambuttonlabel.set_text("Scan data from QR code")
cambuttonlabel.set_style_text_font(lv.font_montserrat_18, 0)
cambuttonlabel.align(lv.ALIGN.TOP_MID, 0, 0)
cambuttonlabel2 = lv.label(cambutton)
cambuttonlabel2.set_text("Tip: Create your own QR code,\nusing https://genqrcode.com or another tool.")
cambuttonlabel2.set_style_text_font(lv.font_montserrat_10, 0)
cambuttonlabel2.align(lv.ALIGN.BOTTOM_MID, 0, 0)
cambutton.add_event_cb(self.cambutton_cb, lv.EVENT.CLICKED, None)
self.setContentView(settings_screen_detail)
def onStop(self, screen):
if self.keyboard:
mpos.ui.anim.smooth_hide(self.keyboard)
def radio_event_handler(self, event):
print("radio_event_handler called")
if self.active_radio_index >= 0:
print(f"removing old CHECKED state from child {self.active_radio_index}")
old_cb = self.radio_container.get_child(self.active_radio_index)
old_cb.remove_state(lv.STATE.CHECKED)
self.active_radio_index = -1
for childnr in range(self.radio_container.get_child_count()):
child = self.radio_container.get_child(childnr)
state = child.get_state()
print(f"radio_container child's state: {state}")
if state & lv.STATE.CHECKED: # State can be something like 19 = lv.STATE.HOVERED (16) & lv.STATE.FOCUSED (2) & lv.STATE.CHECKED (1)
self.active_radio_index = childnr
break
print(f"active_radio_index is now {self.active_radio_index}")
def create_radio_button(self, parent, text, index):
cb = lv.checkbox(parent)
cb.set_text(text)
cb.add_flag(lv.obj.FLAG.EVENT_BUBBLE)
# Add circular style to indicator for radio button appearance
style_radio = lv.style_t()
style_radio.init()
style_radio.set_radius(lv.RADIUS_CIRCLE)
cb.add_style(style_radio, lv.PART.INDICATOR)
style_radio_chk = lv.style_t()
style_radio_chk.init()
style_radio_chk.set_bg_image_src(None)
cb.add_style(style_radio_chk, lv.PART.INDICATOR | lv.STATE.CHECKED)
return cb
def gotqr_result_callback_unused(self, result):
print(f"QR capture finished, result: {result}")
if result.get("result_code"):
data = result.get("data")
print(f"Setting textarea data: {data}")
self.textarea.set_text(data)
def cambutton_cb_unused(self, event):
print("cambutton clicked!")
self.startActivityForResult(Intent(activity_class=CameraApp).putExtra("scanqr_mode", True), self.gotqr_result_callback)
def save_setting(self, setting):
if setting["key"] == "boot_mode" and self.radio_container: # special case that isn't saved
if self.active_radio_index == 1:
from mpos.bootloader import ResetIntoBootloader
intent = Intent(activity_class=ResetIntoBootloader)
ActivityNavigator.startActivity(intent)
return
ui = setting.get("ui")
ui_options = setting.get("ui_options")
if ui and ui == "radiobuttons" and ui_options:
selected_idx = self.active_radio_index
new_value = ""
if selected_idx >= 0:
new_value = ui_options[selected_idx][1]
elif ui and ui == "dropdown" and ui_options:
selected_index = self.dropdown.get_selected()
print(f"selected item: {selected_index}")
new_value = ui_options[selected_index][1]
elif self.textarea:
new_value = self.textarea.get_text()
else:
new_value = ""
editor = self.prefs.edit()
editor.put_string(setting["key"], new_value)
editor.commit()
setting["value_label"].set_text(new_value if new_value else "(not set)")
self.finish()