Core Features Source section: `https://appsudo.com/doc#core-features` Key services documented - `ThreadExecutor`: background work. - `HttpClient`: async HTTP calls. - `WebSocketClient`: persistent, bidirectional WebSocket communication. - `MediaPicker`: image/video/file selection. - `Helper.log(...)`: lightweight debugging helper. - `Logger.write(...)`: logging. - `DataStore`: persistent key-value storage. - `DataLoader.load_json(...)`: JSON file loading. - `ImageHelper.get_bitmap(...)`: image loading. - `InputManager`: synthetic controller input and vibration. - `Dialog`: modal dialogs. - `Notification`: toast and system notifications. - `AppOAuthManager`: receive OAuth redirect callbacks. Source-backed examples `ThreadExecutor`: ```python from core import ThreadExecutor, ThreadRunnable def my_run(self): # background work here import time time.sleep(2) def process_data(self): runner = ThreadRunnable(self.my_run) executor = ThreadExecutor(runner) executor.start() executor.join() ``` `HttpClient`: ```python from runtime import HttpClient url = "https://api.example.com/data" headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } HttpClient.current().get(url, headers, self.handle_response) ``` `WebSocketClient`: ```python from runtime import WebSocketClient import json def on_open(client): print("connected") client.send("Hello, WebSocket Echo Server!") client.send(json.dumps({"type": "test", "value": 42})) client.send(b"\x01\x02\x03") def on_message(client, message): if isinstance(message, str): print("text:", message) else: print("binary:", len(message), "bytes") def on_close(client, code, reason): print("closed:", code, reason) def on_error(client, error): print("error:", error) client = WebSocketClient( "wss://echo.websocket.org", headers={"Authorization": "Bearer ", "X-Client": "myapp"}, on_open=on_open, on_message=on_message, on_close=on_close, on_error=on_error, ) client.connect() ``` `DataStore`: ```python from runtime.DataStore import DataStore DataStore.set("username", self.username, lambda r: None) DataStore.get("username", self.on_username_loaded) DataStore.increment("counter", self.on_counter_updated) ``` `DataLoader`: ```python from runtime.DataLoader import DataLoader config = DataLoader.load_json(self.application_path, "config") api_key = config.get("api_key", "default_key") ``` `InputManager`: ```python from runtime.InputManager import InputManager from runtime.InputManager import K_BUTTON_A, K_DPAD_UP input_manager = InputManager.create() input_manager.trigger(K_BUTTON_A) # Defaults to InputManager.ACTION_DOWN input_manager.trigger(K_BUTTON_A, InputManager.ACTION_UP) input_manager.trigger(K_DPAD_UP, InputManager.ACTION_DOWN) input_manager.trigger(K_DPAD_UP, InputManager.ACTION_UP) InputManager.vibrate() ``` `Dialog`: ```python from runtime import Dialog Dialog.show( "OK", lambda: print("ok"), "Cancel", lambda: print("cancelled"), title="Delete this item?", ) ``` `Notification`: ```python from runtime import Notification import time Notification.toast("Saved") notif_id = Notification.sendNotification("Build complete", "Your project finished in 12.4s", {"projectId": 42}) ten_minutes_from_now = int(time.time() * 1000) + 10 * 60 * 1000 reminder_id = Notification.scheduleNotification("Stretch break", "Stand up and walk for five minutes.", ten_minutes_from_now, {"category": "wellness"}) ``` `AppOAuthManager` WebView flow: ```python from appsudo import App, AppOAuthManager from components.view import ViewInflater class GcalApp(App): def setup(self): self.ui_view = self.inflater.inflate("webview", None) def post_setup(self): webview = self.ui_view.find_element_by_id("webview") secured_url = webview.get_secured_url( f"file://{self.application_path}/static/index.html?clientId={CLIENT_ID}" ) webview.set_url(secured_url) AppOAuthManager.register_callback(self.on_oauth_redirect) ``` Most important usage notes - `HttpClient` is the documented pattern for GET/POST/PUT/DELETE requests. - `WebSocketClient(url, *, protocols=None, headers=None, on_open=None, on_message=None, on_close=None, on_error=None)` connects to `ws://` or `wss://` endpoints; all arguments after `url` are keyword-only. - `protocols` offers optional subprotocol names and `headers` adds custom HTTP handshake headers. - `connect()` is non-blocking and returns the client, `send(data)` sends text for `str` and binary for `bytes`/`bytearray` after `on_open`, and `close(code=1000, reason="")` closes the connection. - WebSocket callbacks receive the client first: `on_open(client)`, `on_message(client, message)`, `on_close(client, code, reason)`, and `on_error(client, error)`. Messages are `str` for text and `bytes` for binary data. - A real-world `WebSocketClient` usage example is available in the [Socket Hub](https://store.appsudo.com/store/components/6) component in the Appsudo Store. - `DataStore` is the documented persistent key-value storage system, and the examples use callback-based access. - `DataLoader.load_json(application_path, "config")` is a documented configuration-loading pattern. - `InputManager.create()` returns an instance whose `trigger(key_constant, action=InputManager.ACTION_DOWN)` method emits synthetic controller input. - `InputManager.ACTION_DOWN` dispatches a key or button press, and `InputManager.ACTION_UP` dispatches a release. - `InputManager` is strictly intended for games that use the pygame framework built into Appsudo, including custom game-controller components and similar game input interfaces. - `InputManager.vibrate(...)` triggers haptic feedback. - `Dialog` supports confirm-only, confirm-with-description, text input, and custom-view variants. - `Notification.toast(...)` is for in-app transient feedback. - `Notification.sendNotification(...)` and `scheduleNotification(...)` handle system notifications. - `AppOAuthManager` only intercepts the exact redirect URI: `com.appsudo.app.miniapp://callback` Code Snippets From Docs - The blocks below were extracted from the corresponding section of `appsudo.com/doc`. Snippet 1 ``` from core import ThreadExecutor, ThreadRunnable def my_run(self): # Background work goes here import time time.sleep(2) def process_data(self): # Assuming this is part of a class runner = ThreadRunnable(self.my_run) executor = ThreadExecutor(runner) executor.start() # Optionally, you can check thread state or control execution print(f"Thread state: {executor.get_state()}") print(f"Thread ID: {executor.get_id()}") print(f"Thread is alive: {executor.is_alive()}") # Wait for thread to complete (if needed) executor.join() ``` Snippet 2 ``` from runtime import HttpClient # GET request def fetch_data(self): # Assuming this is part of a class url = "https://api.example.com/data" headers = { "Authorization": f"Bearer {self.api_key}", # Assuming self.api_key is defined "Content-Type": "application/json" } HttpClient.current().get(url, headers, self.handle_response) # Assuming self.handle_response # POST request def submit_data(self): # Assuming this is part of a class url = "https://api.example.com/submit" payload = { "name": "John Doe", "email": "john@example.com", "message": "Hello, world!" } headers = { "Content-Type": "application/json" } HttpClient.current().post(url, payload, headers, self.handle_response) # Assuming self.handle_response # PUT request def update_data(self): # Assuming this is part of a class url = "https://api.example.com/data/123" payload = { "name": "Updated Name", "email": "updated@example.com" } headers = { "Content-Type": "application/json" } HttpClient.current().put(url, payload, headers, self.handle_response) # Assuming self.handle_response # DELETE request def delete_data(self): # Assuming this is part of a class url = "https://api.example.com/data/123" headers = { "Authorization": f"Bearer {self.api_key}" # Assuming self.api_key is defined } HttpClient.current().delete(url, headers, self.handle_response) # Assuming self.handle_response # Response handler def handle_response(self, response): # Assuming this is part of a class import json try: data = json.loads(response) print(f"Response data: {data}") except Exception as e: print(f"Error parsing response: {e}") ``` Snippet 2a ``` from runtime import HttpClient # Multipart file upload def upload_file(self): # Assuming this is part of a class url = "https://upload.example.com/api/files" with open(self.application_path + "/res/images/photo.jpg", "rb") as f: file_bytes = f.read() boundary = "----AppsudoBoundary" body = ( f"--{boundary}\r\n" f"Content-Disposition: form-data; name=\"name\"\r\n\r\n" f"profile_photo\r\n" f"--{boundary}\r\n" f"Content-Disposition: form-data; name=\"file\"; filename=\"photo.jpg\"\r\n" f"Content-Type: image/jpeg\r\n\r\n" ).encode("utf-8") + file_bytes + f"\r\n--{boundary}--\r\n".encode("utf-8") headers = { "Content-Type": f"multipart/form-data; boundary={boundary}" } HttpClient.current().post(url, body, headers, self.handle_response) # Assuming self.handle_response ``` Snippet 2b ``` from runtime import WebSocketClient import json def on_open(client): print("connected") client.send("Hello, WebSocket Echo Server!") client.send(json.dumps({"type": "test", "value": 42})) client.send(b"\x01\x02\x03") def on_message(client, message): if isinstance(message, str): print("text:", message) else: print("binary:", len(message), "bytes") def on_close(client, code, reason): print("closed:", code, reason) def on_error(client, error): print("error:", error) client = WebSocketClient( "wss://echo.websocket.org", headers={"Authorization": "Bearer ", "X-Client": "myapp"}, on_open=on_open, on_message=on_message, on_close=on_close, on_error=on_error, ) client.connect() ``` Snippet 3 ``` from media import MediaPicker # Pick multiple images def on_images_picked(self, file_paths): for path in file_paths: print(f"Selected image: {path}") picker = MediaPicker() picker.pickImages(self.on_images_picked) # Pick multiple videos def on_videos_picked(self, file_paths): for path in file_paths: print(f"Selected video: {path}") picker.pickVideos(self.on_videos_picked) # Pick multiple files of any type def on_files_picked(self, file_paths): for path in file_paths: print(f"Selected file: {path}") picker.pickFiles(self.on_files_picked) ``` Snippet 4 ``` from runtime.Helper import Helper # Log a message for debugging Helper.log("Debug message") # Perform other utility operations as needed in your app ``` Snippet 5 ``` from core import Logger # Write a log message Logger.write("User logged in") Logger.write("Error occurred: " + str(exception)) # Assuming 'exception' is defined ``` Snippet 6 ``` from runtime.DataStore import DataStore # Save data def save_settings(self): # Assuming this is part of a class DataStore.set("username", self.username, lambda r: None) # Assuming self.username DataStore.set("theme", self.theme, self.on_theme_saved) # Assuming self.theme and self.on_theme_saved def on_theme_saved(self, result): # Assuming this is part of a class print("Theme saved successfully") # Retrieve data def load_settings(self): # Assuming this is part of a class DataStore.get("username", self.on_username_loaded) # Assuming self.on_username_loaded DataStore.get("theme", self.on_theme_loaded) # Assuming self.on_theme_loaded def on_username_loaded(self, value): # Assuming this is part of a class if value: self.username = value # self.update_ui() # Assuming self.update_ui def on_theme_loaded(self, value): # Assuming this is part of a class if value: self.theme = value # self.apply_theme() # Assuming self.apply_theme # Increment a counter def increment_counter(self): # Assuming this is part of a class DataStore.increment("counter", self.on_counter_updated) # Assuming self.on_counter_updated def on_counter_updated(self, new_value): # Assuming this is part of a class self.counter = new_value # self.update_counter_display() # Assuming self.update_counter_display ``` Snippet 7 ``` from runtime.DataLoader import DataLoader from core import Logger # Load JSON configuration (assuming self.application_path is defined) config = DataLoader.load_json(self.application_path, "config") api_key = config.get("api_key", "default_key") server_url = config.get("server_url", "https://default.example.com") # Use the loaded configuration Logger.write(f"Using API key: {api_key}") Logger.write(f"Connecting to server: {server_url}") ``` Snippet 8 ``` from runtime.ImageHelper import ImageHelper from components.media import Image # Load an image from a local file path # Assuming self.application_path is defined: bitmap_local = ImageHelper.get_bitmap(self.application_path + "/res/images/logo.png") # Use the bitmap with an Image component image_component = Image() image_component.set_src(bitmap_local) # Inspect or encode a bitmap width = ImageHelper.get_bitmap_width(bitmap_local) height = ImageHelper.get_bitmap_height(bitmap_local) raw_bytes = ImageHelper.get_bitmap_bytes(bitmap_local) base64_str = ImageHelper.get_bitmap_base64(bitmap_local) # Work directly from a file path file_bytes = ImageHelper.get_bytes(self.application_path + "/res/images/logo.png") file_base64 = ImageHelper.get_base64(self.application_path + "/res/images/logo.png") ``` Snippet 9 ``` from runtime.InputManager import InputManager from runtime.InputManager import ( K_DPAD_UP, K_DPAD_DOWN, K_DPAD_LEFT, K_DPAD_RIGHT, K_BUTTON_A, K_BUTTON_B, K_BUTTON_X, K_BUTTON_Y, K_BUTTON_START, K_BUTTON_SELECT, K_BUTTON_L1, K_BUTTON_R1, K_BUTTON_L2, K_BUTTON_R2, ) input_manager = InputManager.create() input_manager.trigger(K_BUTTON_A) # Defaults to InputManager.ACTION_DOWN input_manager.trigger(K_BUTTON_A, InputManager.ACTION_UP) input_manager.trigger(K_DPAD_UP, InputManager.ACTION_DOWN) input_manager.trigger(K_DPAD_UP, InputManager.ACTION_UP) InputManager.vibrate() InputManager.vibrate(500) ``` Snippet 10 ``` from runtime import Dialog from components.view import ViewInflater Dialog.show( "OK", lambda: print("ok"), "Cancel", lambda: print("cancelled"), title="Delete this item?", ) Dialog.confirm( "Yes", lambda: do_purge(), "No", lambda: print("kept"), title="Empty trash", description="All 14 items will be removed. This cannot be undone.", ) def renamed(new_name): print("renamed to", new_name) Dialog.input( "Save", renamed, "Cancel", lambda: print("cancelled"), title="Rename file", hint="New file name", ) inflater = ViewInflater(self) form_view = inflater.inflate("rename_form", None) Dialog.showView( form_view, "Apply", lambda: self.commit_form(), "Cancel", lambda: self.discard_form(), title="Edit preferences", ) ``` Snippet 11 ``` from runtime import Notification import time Notification.toast("Saved") notif_id = Notification.sendNotification( "Build complete", "Your project finished in 12.4s", {"projectId": 42}, ) Notification.cancelNotification(notif_id) ten_minutes_from_now = int(time.time() * 1000) + 10 * 60 * 1000 reminder_id = Notification.scheduleNotification( "Stretch break", "Stand up and walk for five minutes.", ten_minutes_from_now, {"category": "wellness"}, ) Notification.cancelScheduleNotification(reminder_id) ``` Snippet 12 ``` from appsudo import App, AppOAuthManager from components.view import ViewInflater CLIENT_ID = "" REDIRECT_URI = "com.appsudo.app.miniapp://callback" AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth" SCOPES = "https://www.googleapis.com/auth/calendar.readonly" class GcalApp(App): def __init__(self, application_path): App.__init__(self, "Gcal", application_path) self.inflater = ViewInflater(self) self.ui_view = None self.setup() def setup(self): self.ui_view = self.inflater.inflate("webview", None) def post_setup(self): webview = self.ui_view.find_element_by_id("webview") secured_url = webview.get_secured_url( f"file://{self.application_path}/static/index.html?clientId={CLIENT_ID}" ) webview.set_url(secured_url) AppOAuthManager.register_callback(self.on_oauth_redirect) def on_oauth_redirect(self, url): webview = self.ui_view.find_element_by_id("webview") webview.eval(f"window.handleGoogleCallback({url!r});") def run(self): App.run(self) self.render(self.ui_view.render()) self.post_setup() ``` Snippet 13 ``` from appsudo import App, AppOAuthManager from runtime.HttpClient import HttpClient from runtime.DataStore import DataStore import urllib.parse CLIENT_ID = "" REDIRECT_URI = "com.appsudo.app.miniapp://callback" TOKEN_URL = "https://oauth2.googleapis.com/token" class GcalApp(App): def post_setup(self): AppOAuthManager.register_callback(self.on_oauth_redirect) def on_oauth_redirect(self, url): parsed = urllib.parse.urlparse(url) params = urllib.parse.parse_qs(parsed.query) code = (params.get("code") or [None])[0] if not code: return client = HttpClient() response = client.post( TOKEN_URL, { "code": code, "client_id": CLIENT_ID, "redirect_uri": REDIRECT_URI, "grant_type": "authorization_code", }, ) token = response.json() DataStore.put("gcal_access_token", token["access_token"]) DataStore.put("gcal_refresh_token", token.get("refresh_token", "")) ``` Detailed Coverage Map - The entries below were derived from the section structure in `appsudo.com/doc`. Articles - `feature-threadexecutor`: ThreadExecutor Methods/attributes: __init__, start, get_state, get_id, get_name, is_alive, interrupt, join, sleep, yield_ - `feature-httpclient`: HttpClient Methods/attributes: current, get, post, put, delete - `feature-websocketclient`: WebSocketClient Methods/attributes: WebSocketClient, url, protocols, headers, connect, send, close, on_open, on_message, on_close, on_error - `feature-mediapicker`: MediaPicker Methods/attributes: pickImages, pickVideos, pickFiles - `feature-helper`: Helper Methods/attributes: log - `feature-logger`: Logger Methods/attributes: write - `feature-datastore`: DataStore Methods/attributes: set, get, increment - `feature-dataloader`: DataLoader Methods/attributes: load_json - `feature-imagehelper`: ImageHelper Methods/attributes: get_bitmap, get_bitmap_width, get_bitmap_height, get_bytes, get_base64, get_bitmap_bytes, get_bitmap_base64 - `feature-inputmanager`: InputManager Methods/attributes: InputManager.create, trigger, InputManager.ACTION_DOWN, InputManager.ACTION_UP, InputManager.vibrate, K_DPAD_UP, K_DPAD_DOWN, K_DPAD_LEFT, K_DPAD_RIGHT, K_BUTTON_A, K_BUTTON_B, K_BUTTON_X, K_BUTTON_Y, K_BUTTON_START, K_BUTTON_SELECT, K_BUTTON_L1, K_BUTTON_R1, K_BUTTON_L2, K_BUTTON_R2 - `feature-dialog`: Dialog Methods/attributes: Dialog.show, Dialog.confirm, Dialog.input, Dialog.showView - `feature-notification`: Notification Methods/attributes: Notification.toast, Notification.sendNotification, Notification.cancelNotification, Notification.scheduleNotification, Notification.cancelScheduleNotification - `feature-appoauthmanager`: AppOAuthManager Methods/attributes: AppOAuthManager.register_callback