You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Apply theme changes (dark mode, color) immediately after saving
Also: - API: change "display" to mpos.ui.main_display - API: change mpos.ui.th to mpos.ui.task_handler
This commit is contained in:
+12
-9
@@ -1,17 +1,20 @@
|
||||
0.3.4 (unreleased)
|
||||
==================
|
||||
- Apply theme changes (dark mode, color) immediately after saving
|
||||
OSUpdate app: Major rework with improved reliability and user experience
|
||||
- OSUpdate app: add WiFi monitoring - shows "Waiting for WiFi..." instead of error when no connection
|
||||
- OSUpdate app: add automatic pause/resume on WiFi loss during downloads using HTTP Range headers
|
||||
- OSUpdate app: add user-friendly error messages with specific guidance for each error type
|
||||
- OSUpdate app: add "Check Again" button for easy retry after errors
|
||||
- OSUpdate app: add state machine for better app state management
|
||||
- OSUpdate app: add comprehensive test coverage (42 tests: 31 unit tests + 11 graphical tests)
|
||||
- OSUpdate app: refactor code into testable components (NetworkMonitor, UpdateChecker, UpdateDownloader)
|
||||
- OSUpdate app: improve download error recovery with progress preservation
|
||||
- OSUpdate app: improve timeout handling (5-minute wait for WiFi with clear messaging)
|
||||
- OSUpdate app: add WiFi monitoring - shows "Waiting for WiFi..." instead of error when no connection
|
||||
- OSUpdate app: add automatic pause/resume on WiFi loss during downloads using HTTP Range headers
|
||||
- OSUpdate app: add user-friendly error messages with specific guidance for each error type
|
||||
- OSUpdate app: add "Check Again" button for easy retry after errors
|
||||
- OSUpdate app: add state machine for better app state management
|
||||
- OSUpdate app: add comprehensive test coverage (42 tests: 31 unit tests + 11 graphical tests)
|
||||
- OSUpdate app: refactor code into testable components (NetworkMonitor, UpdateChecker, UpdateDownloader)
|
||||
- OSUpdate app: improve download error recovery with progress preservation
|
||||
- OSUpdate app: improve timeout handling (5-minute wait for WiFi with clear messaging)
|
||||
- Tests: add test infrastructure with mock classes for network, HTTP, and partition operations
|
||||
- Tests: add graphical test helper utilities for UI verification and screenshot capture
|
||||
- API: change "display" to mpos.ui.main_display
|
||||
- API: change mpos.ui.th to mpos.ui.task_handler
|
||||
|
||||
0.3.3
|
||||
=====
|
||||
|
||||
@@ -534,11 +534,11 @@ class MyAnimatedApp(Activity):
|
||||
def onResume(self, screen):
|
||||
# Register the frame callback
|
||||
self.last_time = time.ticks_ms()
|
||||
mpos.ui.th.add_event_cb(self.update_frame, 1)
|
||||
mpos.ui.task_handler.add_event_cb(self.update_frame, 1)
|
||||
|
||||
def onPause(self, screen):
|
||||
# Unregister when app goes to background
|
||||
mpos.ui.th.remove_event_cb(self.update_frame)
|
||||
mpos.ui.task_handler.remove_event_cb(self.update_frame)
|
||||
|
||||
def update_frame(self, a, b):
|
||||
# Calculate delta time for framerate independence
|
||||
@@ -670,7 +670,7 @@ def update_frame(self, a, b):
|
||||
def start_animation(self):
|
||||
self.spawn_timer = 0
|
||||
self.spawn_interval = 0.15 # seconds between spawns
|
||||
mpos.ui.th.add_event_cb(self.update_frame, 1)
|
||||
mpos.ui.task_handler.add_event_cb(self.update_frame, 1)
|
||||
|
||||
def update_frame(self, a, b):
|
||||
delta_time = time.ticks_diff(time.ticks_ms(), self.last_time) / 1000.0
|
||||
@@ -803,7 +803,7 @@ def check_collision(self):
|
||||
def start_animation(self):
|
||||
self.animation_running = True
|
||||
self.last_time = time.ticks_ms()
|
||||
mpos.ui.th.add_event_cb(self.update_frame, 1)
|
||||
mpos.ui.task_handler.add_event_cb(self.update_frame, 1)
|
||||
|
||||
# Optional: auto-stop after duration
|
||||
lv.timer_create(self.stop_animation, 15000, None).set_repeat_count(1)
|
||||
@@ -817,7 +817,7 @@ def update_frame(self, a, b):
|
||||
|
||||
# Stop when animation completes
|
||||
if not self.animation_running and len(self.particles) == 0:
|
||||
mpos.ui.th.remove_event_cb(self.update_frame)
|
||||
mpos.ui.task_handler.remove_event_cb(self.update_frame)
|
||||
print("Animation finished")
|
||||
```
|
||||
|
||||
@@ -827,11 +827,11 @@ def onResume(self, screen):
|
||||
# Only start if needed (e.g., game in progress)
|
||||
if self.game_started and not self.game_over:
|
||||
self.last_time = time.ticks_ms()
|
||||
mpos.ui.th.add_event_cb(self.update_frame, 1)
|
||||
mpos.ui.task_handler.add_event_cb(self.update_frame, 1)
|
||||
|
||||
def onPause(self, screen):
|
||||
# Always stop when app goes to background
|
||||
mpos.ui.th.remove_event_cb(self.update_frame)
|
||||
mpos.ui.task_handler.remove_event_cb(self.update_frame)
|
||||
```
|
||||
|
||||
### Performance Tips
|
||||
|
||||
@@ -45,10 +45,10 @@ class Confetti(Activity):
|
||||
self.setContentView(self.screen)
|
||||
|
||||
def onResume(self, screen):
|
||||
mpos.ui.th.add_event_cb(self.update_frame, 1)
|
||||
mpos.ui.task_handler.add_event_cb(self.update_frame, 1)
|
||||
|
||||
def onPause(self, screen):
|
||||
mpos.ui.th.remove_event_cb(self.update_frame)
|
||||
mpos.ui.task_handler.remove_event_cb(self.update_frame)
|
||||
|
||||
def spawn_confetti(self):
|
||||
"""Safely spawn a new confetti piece with unique img_idx"""
|
||||
|
||||
@@ -59,7 +59,7 @@ _BUFFER_SIZE = const(28800)
|
||||
fb1 = display_bus.allocate_framebuffer(_BUFFER_SIZE, lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_DMA)
|
||||
fb2 = display_bus.allocate_framebuffer(_BUFFER_SIZE, lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_DMA)
|
||||
|
||||
display = st7789.ST7789(
|
||||
mpos.ui.main_display = st7789.ST7789(
|
||||
data_bus=display_bus,
|
||||
frame_buffer1=fb1,
|
||||
frame_buffer2=fb2,
|
||||
@@ -71,9 +71,9 @@ display = st7789.ST7789(
|
||||
color_byte_order=st7789.BYTE_ORDER_BGR,
|
||||
rgb565_byte_swap=True,
|
||||
)
|
||||
display.init()
|
||||
display.set_power(True)
|
||||
display.set_backlight(100)
|
||||
mpos.ui.main_display.init()
|
||||
mpos.ui.main_display.set_power(True)
|
||||
mpos.ui.main_display.set_backlight(100)
|
||||
|
||||
# Touch handling:
|
||||
i2c_bus = i2c.I2C.Bus(host=I2C_BUS, scl=TP_SCL, sda=TP_SDA, freq=I2C_FREQ, use_locks=False)
|
||||
@@ -81,7 +81,7 @@ touch_dev = i2c.I2C.Device(bus=i2c_bus, dev_id=TP_ADDR, reg_bits=TP_REGBITS)
|
||||
indev=cst816s.CST816S(touch_dev,startup_rotation=lv.DISPLAY_ROTATION._180) # button in top left, good
|
||||
|
||||
lv.init()
|
||||
display.set_rotation(lv.DISPLAY_ROTATION._90) # must be done after initializing display and creating the touch drivers, to ensure proper handling
|
||||
mpos.ui.main_display.set_rotation(lv.DISPLAY_ROTATION._90) # must be done after initializing display and creating the touch drivers, to ensure proper handling
|
||||
|
||||
# Battery voltage ADC measuring
|
||||
import mpos.battery_voltage
|
||||
|
||||
@@ -63,7 +63,7 @@ STATE_HIGH = 1
|
||||
STATE_LOW = 0
|
||||
|
||||
# see ./lvgl_micropython/api_drivers/py_api_drivers/frozen/display/display_driver_framework.py
|
||||
display = st7789.ST7789(
|
||||
mpos.ui.main_display = st7789.ST7789(
|
||||
data_bus=display_bus,
|
||||
frame_buffer1=fb1,
|
||||
frame_buffer2=fb2,
|
||||
@@ -76,15 +76,15 @@ display = st7789.ST7789(
|
||||
reset_state=STATE_LOW
|
||||
)
|
||||
|
||||
display.init()
|
||||
display.set_power(True)
|
||||
display.set_backlight(100)
|
||||
mpos.ui.main_display.init()
|
||||
mpos.ui.main_display.set_power(True)
|
||||
mpos.ui.main_display.set_backlight(100)
|
||||
|
||||
display.set_color_inversion(False)
|
||||
mpos.ui.main_display.set_color_inversion(False)
|
||||
|
||||
lv.init()
|
||||
display.set_rotation(lv.DISPLAY_ROTATION._270) # must be done after initializing display and creating the touch drivers, to ensure proper handling
|
||||
display.set_params(0x36, bytearray([0x28]))
|
||||
mpos.ui.main_display.set_rotation(lv.DISPLAY_ROTATION._270) # must be done after initializing display and creating the touch drivers, to ensure proper handling
|
||||
mpos.ui.main_display.set_params(0x36, bytearray([0x28]))
|
||||
|
||||
# Button and joystick handling code:
|
||||
from machine import ADC, Pin
|
||||
|
||||
@@ -47,10 +47,10 @@ bus = lcd_bus.SDLBus(flags=0)
|
||||
|
||||
buf1 = bus.allocate_framebuffer(TFT_HOR_RES * TFT_VER_RES * 2, 0)
|
||||
|
||||
display = sdl_display.SDLDisplay(data_bus=bus,display_width=TFT_HOR_RES,display_height=TFT_VER_RES,frame_buffer1=buf1,color_space=lv.COLOR_FORMAT.RGB565)
|
||||
# display.set_dpi(65) # doesn't seem to change the default 130...
|
||||
display.init()
|
||||
mpos.ui.main_display = sdl_display.SDLDisplay(data_bus=bus,display_width=TFT_HOR_RES,display_height=TFT_VER_RES,frame_buffer1=buf1,color_space=lv.COLOR_FORMAT.RGB565)
|
||||
# display.set_dpi(65) # doesn't seem to change the default 130...
|
||||
mpos.ui.main_display.init()
|
||||
# main_display.set_dpi(65) # doesn't seem to change the default 130...
|
||||
|
||||
import sdl_pointer
|
||||
mouse = sdl_pointer.SDLPointer()
|
||||
|
||||
@@ -327,4 +327,6 @@ class SettingActivity(Activity):
|
||||
if changed_callback and old_value != new_value:
|
||||
print(f"Setting {setting['key']} changed from {old_value} to {new_value}, calling changed_callback...")
|
||||
changed_callback()
|
||||
if setting["key"] == "theme_light_dark" or setting["key"] == "theme_primary_color":
|
||||
mpos.ui.set_theme(self.prefs)
|
||||
self.finish()
|
||||
|
||||
@@ -19,7 +19,7 @@ class Activity:
|
||||
|
||||
def onResume(self, screen): # app goes to foreground
|
||||
self._has_foreground = True
|
||||
mpos.ui.th.add_event_cb(self.task_handler_callback, 1)
|
||||
mpos.ui.task_handler.add_event_cb(self.task_handler_callback, 1)
|
||||
|
||||
def onPause(self, screen): # app goes to background
|
||||
self._has_foreground = False
|
||||
|
||||
@@ -3,6 +3,7 @@ from .view import (
|
||||
screen_stack, remove_and_stop_current_activity, remove_and_stop_all_activities
|
||||
)
|
||||
from .gesture_navigation import handle_back_swipe, handle_top_swipe
|
||||
from .theme import set_theme
|
||||
from .topmenu import open_bar, close_bar, open_drawer, drawer_open, NOTIFICATION_BAR_HEIGHT
|
||||
from .focus import save_and_clear_current_focusgroup
|
||||
from .display import (
|
||||
@@ -17,6 +18,7 @@ from .util import shutdown, set_foreground_app, get_foreground_app
|
||||
__all__ = [
|
||||
"setContentView", "back_screen", "remove_and_stop_current_activity", "remove_and_stop_all_activities"
|
||||
"handle_back_swipe", "handle_top_swipe",
|
||||
"set_theme",
|
||||
"open_bar", "close_bar", "open_drawer", "drawer_open", "NOTIFICATION_BAR_HEIGHT",
|
||||
"save_and_clear_current_focusgroup",
|
||||
"get_display_width", "get_display_height",
|
||||
|
||||
@@ -12,17 +12,18 @@ def init_rootscreen():
|
||||
_vertical_resolution = disp.get_vertical_resolution()
|
||||
print(f"init_rootscreen set _vertical_resolution to {_vertical_resolution}")
|
||||
|
||||
# It seems this style
|
||||
style = lv.style_t()
|
||||
style.init()
|
||||
style.set_bg_opa(lv.OPA.TRANSP)
|
||||
#style.set_bg_opa(lv.OPA.TRANSP)
|
||||
style.set_border_width(0)
|
||||
style.set_outline_width(0)
|
||||
style.set_shadow_width(0)
|
||||
#style.set_outline_width(0)
|
||||
#style.set_shadow_width(0)
|
||||
style.set_pad_all(0)
|
||||
style.set_radius(0)
|
||||
screen.add_style(style, 0)
|
||||
screen.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
|
||||
screen.set_scroll_dir(lv.DIR.NONE)
|
||||
#screen.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
|
||||
#screen.set_scroll_dir(lv.DIR.NONE)
|
||||
|
||||
label = lv.label(screen)
|
||||
label.set_text("Welcome to MicroPythonOS")
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import lvgl as lv
|
||||
import mpos.config
|
||||
|
||||
def set_theme(prefs):
|
||||
# Load and set theme:
|
||||
theme_light_dark = prefs.get_string("theme_light_dark", "light") # default to a light theme
|
||||
theme_dark_bool = ( theme_light_dark == "dark" )
|
||||
primary_color = lv.theme_get_color_primary(None)
|
||||
color_string = prefs.get_string("theme_primary_color")
|
||||
if color_string:
|
||||
try:
|
||||
color_string = color_string.replace("0x", "").replace("#", "").strip().lower()
|
||||
color_int = int(color_string, 16)
|
||||
print(f"Setting primary color: {color_int}")
|
||||
primary_color = lv.color_hex(color_int)
|
||||
except Exception as e:
|
||||
print(f"Converting color setting '{color_string}' to lv_color_hex() got exception: {e}")
|
||||
|
||||
lv.theme_default_init(mpos.ui.main_display._disp_drv, primary_color, lv.color_hex(0xFBDC05), theme_dark_bool, lv.font_montserrat_12)
|
||||
#mpos.ui.main_display.set_theme(theme) # not needed, default theme is applied immediately
|
||||
@@ -222,8 +222,8 @@ def create_drawer(display=None):
|
||||
slider_label=lv.label(drawer)
|
||||
prefs = mpos.config.SharedPreferences("com.micropythonos.settings")
|
||||
brightness_int = prefs.get_int("display_brightness", 100)
|
||||
if display:
|
||||
display.set_backlight(brightness_int)
|
||||
if mpos.ui.main_display:
|
||||
mpos.ui.main_display.set_backlight(brightness_int)
|
||||
slider_label.set_text(f"Brightness: {brightness_int}%")
|
||||
slider_label.align(lv.ALIGN.TOP_MID,0,lv.pct(4))
|
||||
slider=lv.slider(drawer)
|
||||
@@ -234,8 +234,8 @@ def create_drawer(display=None):
|
||||
def brightness_slider_changed(e):
|
||||
brightness_int = slider.get_value()
|
||||
slider_label.set_text(f"Brightness: {brightness_int}%")
|
||||
if display:
|
||||
display.set_backlight(brightness_int)
|
||||
if mpos.ui.main_display:
|
||||
mpos.ui.main_display.set_backlight(brightness_int)
|
||||
def brightness_slider_released(e):
|
||||
brightness_int = slider.get_value()
|
||||
prefs = mpos.config.SharedPreferences("com.micropythonos.settings")
|
||||
|
||||
@@ -16,26 +16,10 @@ from mpos.content.package_manager import PackageManager
|
||||
|
||||
prefs = mpos.config.SharedPreferences("com.micropythonos.settings")
|
||||
|
||||
# Load and set theme:
|
||||
theme_light_dark = prefs.get_string("theme_light_dark", "light") # default to a light theme
|
||||
theme_dark_bool = ( theme_light_dark == "dark" )
|
||||
primary_color = lv.theme_get_color_primary(None)
|
||||
color_string = prefs.get_string("theme_primary_color")
|
||||
if color_string:
|
||||
try:
|
||||
color_string = color_string.replace("0x", "").replace("#", "").strip().lower()
|
||||
color_int = int(color_string, 16)
|
||||
print(f"Setting primary color: {color_int}")
|
||||
primary_color = lv.color_hex(color_int)
|
||||
except Exception as e:
|
||||
print(f"Converting color setting '{color_string}' to lv_color_hex() got exception: {e}")
|
||||
theme = lv.theme_default_init(display._disp_drv, primary_color, lv.color_hex(0xFBDC05), theme_dark_bool, lv.font_montserrat_12)
|
||||
|
||||
#display.set_theme(theme)
|
||||
|
||||
mpos.ui.set_theme(prefs)
|
||||
init_rootscreen()
|
||||
mpos.ui.topmenu.create_notification_bar()
|
||||
mpos.ui.topmenu.create_drawer(display)
|
||||
mpos.ui.topmenu.create_drawer(mpos.ui.display)
|
||||
mpos.ui.handle_back_swipe()
|
||||
mpos.ui.handle_top_swipe()
|
||||
|
||||
@@ -48,16 +32,17 @@ if focusgroup: # on esp32 this may not be set
|
||||
# Can be passed to TaskHandler, currently unused:
|
||||
def custom_exception_handler(e):
|
||||
print(f"custom_exception_handler called: {e}")
|
||||
mpos.ui.th.deinit()
|
||||
mpos.ui.task_handler.deinit()
|
||||
# otherwise it does focus_next and then crashes while doing lv.deinit()
|
||||
focusgroup.remove_all_objs()
|
||||
focusgroup.delete()
|
||||
lv.deinit()
|
||||
|
||||
import sys
|
||||
if sys.platform == "esp32":
|
||||
mpos.ui.th = task_handler.TaskHandler(duration=5) # 1ms gives highest framerate on esp32-s3's but might have side effects?
|
||||
mpos.ui.task_handler = task_handler.TaskHandler(duration=5) # 1ms gives highest framerate on esp32-s3's but might have side effects?
|
||||
else:
|
||||
mpos.ui.th = task_handler.TaskHandler(duration=5) # 5ms is recommended for MicroPython+LVGL on desktop (less results in lower framerate)
|
||||
mpos.ui.task_handler = task_handler.TaskHandler(duration=5) # 5ms is recommended for MicroPython+LVGL on desktop (less results in lower framerate)
|
||||
|
||||
try:
|
||||
import freezefs_mount_builtin
|
||||
|
||||
@@ -24,7 +24,7 @@ class TestStartApp(unittest.TestCase):
|
||||
init_rootscreen()
|
||||
mpos.ui.topmenu.create_notification_bar()
|
||||
mpos.ui.topmenu.create_drawer(display)
|
||||
mpos.ui.th = task_handler.TaskHandler(duration=5) # 5ms is recommended for MicroPython+LVGL on desktop (less results in lower framerate)
|
||||
mpos.ui.task_handler = task_handler.TaskHandler(duration=5) # 5ms is recommended for MicroPython+LVGL on desktop (less results in lower framerate)
|
||||
|
||||
|
||||
def test_normal(self):
|
||||
|
||||
Reference in New Issue
Block a user