From 3935b0018e1df33c5af56279640ce879a83f3e0b Mon Sep 17 00:00:00 2001 From: Thomas Farstrike Date: Tue, 3 Jun 2025 10:20:17 +0200 Subject: [PATCH] Piggy: use Intents to start new Activities --- .../assets/displaywallet.py | 60 ++++--- internal_filesystem/lib/mpos/apps.py | 156 ++++++++++++++++++ 2 files changed, 190 insertions(+), 26 deletions(-) diff --git a/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/displaywallet.py b/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/displaywallet.py index 9872a1eb..d5e0be12 100644 --- a/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/displaywallet.py +++ b/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/displaywallet.py @@ -1,4 +1,4 @@ -from mpos.apps import Activity +from mpos.apps import Activity, Intent import mpos.config import mpos.ui @@ -10,6 +10,7 @@ class MainActivity(Activity): def __init__(self): self.wallet = None self.receive_qr_data = None + self.destination = None # widgets self.balance_label = None self.receive_qr = None @@ -84,8 +85,9 @@ class MainActivity(Activity): self.payments_label.set_text(f"Could not start {wallet_type} backend.") def onStop(self, main_screen): - if self.wallet: + if self.wallet and self.destination != FullscreenQR: self.wallet.stop() + self.destination = None def redraw_balance_cb(self): # this gets called from another thread (the wallet) so make sure it happens in the LVGL thread using lv.async_call(): @@ -95,30 +97,20 @@ class MainActivity(Activity): # this gets called from another thread (the wallet) so make sure it happens in the LVGL thread using lv.async_call(): lv.async_call(lambda l: self.payments_label.set_text(str(self.wallet.payment_list)), None) - def settings_button_tap(self, event): - settings_activity = SettingsActivity() - settings_activity.onCreate() + self.startActivity(Intent(activity_class=SettingsActivity)) def main_ui_set_defaults(self): self.balance_label.set_text(lv.SYMBOL.REFRESH) self.payments_label.set_text(lv.SYMBOL.REFRESH) - self.receive_qr.update("EMPTY", len("EMPTY")) + self.receive_qr.update("EMPTY PLACEHOLDER", len("EMPTY PLACEHOLDER")) def qr_clicked_cb(self, event): + print("QR clicked") if not self.receive_qr_data: return - print("QR clicked") - qr_screen = lv.obj() - big_receive_qr = lv.qrcode(qr_screen) - big_receive_qr.set_size(240) # TODO: make this dynamic - big_receive_qr.set_dark_color(lv.color_black()) - big_receive_qr.set_light_color(lv.color_white()) - big_receive_qr.center() - big_receive_qr.set_style_border_color(lv.color_white(), 0) - big_receive_qr.set_style_border_width(3, 0); - big_receive_qr.update(self.receive_qr_data, len(self.receive_qr_data)) - mpos.ui.load_screen(qr_screen) + self.destination = FullscreenQR + self.startActivity(Intent(activity_class=FullscreenQR).putExtra("receive_qr_data", self.receive_qr_data)) # Used to list and edit all settings: class SettingsActivity(Activity): @@ -190,18 +182,19 @@ class SettingsActivity(Activity): setting["cont"].remove_flag(lv.obj.FLAG.HIDDEN) def startSettingActivity(self, setting): - sa = SettingActivity(setting) - sa.onCreate() - + intent = Intent(activity_class=SettingActivity) + intent.putExtra("setting", setting) + self.startActivity(intent) # Used to edit one setting: class SettingActivity(Activity): - def __init__(self, setting): + def __init__(self): super().__init__() self.prefs = mpos.config.SharedPreferences("com.lightningpiggy.displaywallet") - self.setting = setting + self.setting = None def onCreate(self): + setting = self.getIntent().extras.get("setting") settings_screen_detail = lv.obj() settings_screen_detail.set_style_pad_all(10, 0) settings_screen_detail.set_flex_flow(lv.FLEX_FLOW.COLUMN) @@ -214,7 +207,7 @@ class SettingActivity(Activity): top_cont.set_style_flex_main_place(lv.FLEX_ALIGN.SPACE_BETWEEN, 0) setting_label = lv.label(top_cont) - setting_label.set_text(self.setting["title"]) + setting_label.set_text(setting["title"]) setting_label.align(lv.ALIGN.TOP_LEFT,0,0) setting_label.set_style_text_font(lv.font_montserrat_22, 0) @@ -226,7 +219,7 @@ class SettingActivity(Activity): cambuttonlabel.center() cambutton.add_event_cb(self.cambutton_cb, lv.EVENT.CLICKED, None) - if self.setting["key"] == "wallet_type": + if setting["key"] == "wallet_type": cambutton.add_flag(lv.obj.FLAG.HIDDEN) # Create container for radio buttons self.radio_container = lv.obj(settings_screen_detail) @@ -249,7 +242,7 @@ class SettingActivity(Activity): self.textarea = lv.textarea(settings_screen_detail) self.textarea.set_width(lv.pct(100)) self.textarea.set_height(lv.SIZE_CONTENT) - self.textarea.set_text(self.prefs.get_string(self.setting["key"], "")) + self.textarea.set_text(self.prefs.get_string(setting["key"], "")) self.textarea.add_event_cb(self.show_keyboard, lv.EVENT.CLICKED, None) self.textarea.add_event_cb(self.show_keyboard, lv.EVENT.FOCUSED, None) self.textarea.add_event_cb(self.hide_keyboard, lv.EVENT.DEFOCUSED, None) @@ -275,7 +268,7 @@ class SettingActivity(Activity): save_label = lv.label(save_btn) save_label.set_text("Save") save_label.center() - save_btn.add_event_cb(lambda e, s=self.setting: self.save_setting(s), lv.EVENT.CLICKED, None) + 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) @@ -354,3 +347,18 @@ class SettingActivity(Activity): setting["value_label"].set_text(new_value if new_value else "Not set") self.finish() + +class FullscreenQR(Activity): + + def onCreate(self): + receive_qr_data = self.getIntent().extras.get("receive_qr_data") + qr_screen = lv.obj() + big_receive_qr = lv.qrcode(qr_screen) + big_receive_qr.set_size(240) # TODO: make this dynamic + big_receive_qr.set_dark_color(lv.color_black()) + big_receive_qr.set_light_color(lv.color_white()) + big_receive_qr.center() + big_receive_qr.set_style_border_color(lv.color_white(), 0) + big_receive_qr.set_style_border_width(3, 0); + big_receive_qr.update(receive_qr_data, len(receive_qr_data)) + self.setContentView(qr_screen) diff --git a/internal_filesystem/lib/mpos/apps.py b/internal_filesystem/lib/mpos/apps.py index dfee06e1..9693601c 100644 --- a/internal_filesystem/lib/mpos/apps.py +++ b/internal_filesystem/lib/mpos/apps.py @@ -193,6 +193,12 @@ def auto_connect(): class Activity: + def __init__(self): + self.intent = None # Store the intent that launched this activity + + def getIntent(self): + return self.intent + def onCreate(self): pass def onStart(self, screen): @@ -211,3 +217,153 @@ class Activity: def finish(self): mpos.ui.back_screen() + + def startActivity(self, intent): + ActivityNavigator.startActivity(intent) + +class Intent: + def __init__(self, activity_class=None, action=None, data=None, extras=None): + self.activity_class = activity_class # Explicit target (e.g., SettingsActivity) + self.action = action # Action string (e.g., "view", "share") + self.data = data # Single data item (e.g., URL) + self.extras = extras or {} # Dictionary for additional data + self.flags = {} # Simplified flags: {"clear_top": bool, "no_history": bool, "no_animation": bool} + + def addFlag(self, flag, value=True): + self.flags[flag] = value + return self + + def putExtra(self, key, value): + self.extras[key] = value + return self + + +class ActivityNavigator: + + def startActivity(intent): + if not isinstance(intent, Intent): + raise ValueError("Must provide an Intent") + if intent.action: # Implicit intent: resolve handlers + handlers = APP_REGISTRY.get(intent.action, []) + if len(handlers) == 1: + intent.activity_class = handlers[0] + ActivityNavigator._launch_activity(intent) + elif handlers: + _show_chooser(intent, handlers) + else: + raise ValueError(f"No handlers for action: {intent.action}") + else: + # Explicit intent + ActivityNavigator._launch_activity(intent) + + def _launch_activity(intent): + activity = intent.activity_class() + activity.intent = intent + activity.onCreate() + + def _show_chooser(intent, handlers): + chooser_intent = Intent(ChooserActivity, extras={"original_intent": intent, "handlers": [h.__name__ for h in handlers]}) + _launch_activity(chooser_intent) + + +class ChooserActivity(Activity): + def __init__(self): + super().__init__() + + def onCreate(self): + screen = lv.obj() + # Get handlers from intent extras + original_intent = self.getIntent().extras.get("original_intent") + handlers = self.getIntent().extras.get("handlers", []) + label = lv.label(screen) + label.set_text("Choose an app") + label.set_pos(10, 10) + + for i, handler_name in enumerate(handlers): + btn = lv.btn(screen) + btn.set_user_data(f"handler_{i}") + btn_label = lv.label(btn) + btn_label.set_text(handler_name) + btn.set_pos(10, 50 * (i + 1) + 10) + btn.add_event_cb(lambda e, h=handler_name, oi=original_intent: self._select_handler(h, oi), lv.EVENT.CLICKED) + self.setContentView(screen) + + def _select_handler(self, handler_name, original_intent): + for handler in APP_REGISTRY.get(original_intent.action, []): + if handler.__name__ == handler_name: + original_intent.activity_class = handler + navigator.startActivity(original_intent) + break + navigator.finish() # Close chooser + + def onStop(self, screen): + if self.getIntent() and self.getIntent().getStringExtra("destination") == "ChooserActivity": + print("Stopped for Chooser") + else: + print("Stopped for other screen") + + +class ViewActivity(Activity): + def __init__(self): + super().__init__() + + def onCreate(self): + screen = lv.obj() + # Get content from intent (prefer extras.url, fallback to data) + content = self.getIntent().extras.get("url", self.getIntent().data or "No content") + label = lv.label(screen) + label.set_user_data("content_label") + label.set_text(f"Viewing: {content}") + label.center() + self.setContentView(screen) + + def onStart(self, screen): + content = self.getIntent().extras.get("url", self.getIntent().data or "No content") + for i in range(screen.get_child_cnt()): + if screen.get_child(i).get_user_data() == "content_label": + screen.get_child(i).set_text(f"Viewing: {content}") + + def onStop(self, screen): + if self.getIntent() and self.getIntent().getStringExtra("destination") == "ViewActivity": + print("Stopped for View") + else: + print("Stopped for other screen") + +class ShareActivity(Activity): + def __init__(self): + super().__init__() + + def onCreate(self): + screen = lv.obj() + # Get text from intent (prefer extras.text, fallback to data) + text = self.getIntent().extras.get("text", self.getIntent().data or "No text") + label = lv.label(screen) + label.set_user_data("share_label") + label.set_text(f"Share: {text}") + label.set_pos(10, 10) + + btn = lv.btn(screen) + btn.set_user_data("share_btn") + btn_label = lv.label(btn) + btn_label.set_text("Share") + btn.set_pos(10, 50) + btn.add_event_cb(lambda e: self._share_content(text), lv.EVENT.CLICKED) + self.setContentView(screen) + + def _share_content(self, text): + # Dispatch to another app (e.g., MessagingActivity) or simulate sharing + print(f"Sharing: {text}") # Placeholder for actual sharing + # Example: Launch another share handler + navigator.startActivity(Intent(action="share", data=text)) + navigator.finish() # Close ShareActivity + + def onStop(self, screen): + if self.getIntent() and self.getIntent().getStringExtra("destination") == "ShareActivity": + print("Stopped for Share") + else: + print("Stopped for other screen") + +APP_REGISTRY = { # This should be handled by a new class PackageManager: + "view": [ViewActivity], # Hypothetical activities + "share": [ShareActivity] +}