Skip to main content

compose-ui

Build shared Compose Multiplatform UI in Meshtastic-Android - adaptive layouts on Material 3 Adaptive, plus the string and resource rules. Use this whenever you add or change a composable, add a user-facing string, or work on tablet, desktop or landscape layout. Consult the bundled `strings-index.txt` rather than opening the raw `strings.xml`, which is guarded for size.

Quellinformationen

Repository
meshtastic/Meshtastic-Android
Letzte Quellaktivität
9. Oktober 2026 um 12:28
Erkannte Sprache von SKILL.md
Englisch
Sterne
1.866
Forks
522

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
2 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
compose-ui
description
Build shared Compose Multiplatform UI in Meshtastic-Android - adaptive layouts on Material 3 Adaptive, plus the string and resource rules. Use this whenever you add or change a composable, add a user-facing string, or work on tablet, desktop or landscape layout. Consult the bundled `strings-index.txt` rather than opening the raw `strings.xml`, which is guarded for size.
# Skill: Compose Multiplatform (CMP) UI ## Description Guidelines for building shared UI, adaptive layouts, and handling strings/resources in Meshtastic-Android. The codebase uses Material 3 Adaptive. ## 1. UI Components & Layouts - **Material 3 / Adaptive:** Use `currentWindowAdaptiveInfoV2()`, which includes the Large (1200dp) and XL (1600dp) width classes; `currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)` is deprecated in its favour. Investigate 3-pane "Power User" scenes using Navigation 3 Scenes and draggable dividers for desktopApp/tablets. - **Dialogs & Alerts:** Use centralized components like `AlertHost(alertManager)` from `core:ui/commonMain`. Do NOT trigger alerts inline or duplicate alert logic. Use `SharedDialogs(uiViewModel)` for general popups. - **Placeholders:** Use `PlaceholderScreen(name)` from `core:ui/commonMain` for unimplemented desktopApp/JVM features. - **Empty states:** Use `EmptyState(icon, title, supportingText, action)` from `core:ui/commonMain` for an empty list or pane rather than a hand-built icon-and-text column. - **Theme Picker:** Use `ThemePickerDialog` from `feature:settings/commonMain`. - **Platform Implementations:** Inject platform-specific behavior (e.g., Map providers) via `CompositionLocal` from the `androidApp` or `desktopApp` shells. Do not tightly couple Google Maps dependencies to `commonMain`; the MapLibre surfaces live in `:feature:map-maplibre`, not in a `core` module. ## 2. Strings & Resources - **Multiplatform Resources:** MUST use `core:resources` (e.g., `stringResource(Res.string.your_key)`). Never use hardcoded strings. - **ViewModels/Coroutines:** Use the asynchronous `getStringSuspend(Res.string.your_key)`. NEVER use blocking `getString()` in a coroutine context. - **Formatting Constraints:** CMP `stringResource` only supports `%N$s` (string) and `%N$d` (integer). - **No Float formatting:** Formats like `%N$.1f` pass through unsubstituted. Pre-format in Kotlin using `NumberFormatter.format(value, decimalPlaces)` from `core:common` and pass as a string argument (`%N$s`): ```kotlin val formatted = NumberFormatter.format(batteryLevel, 1) // "73.5" stringResource(Res.string.battery_percent, formatted) // uses %1$s ``` - **Percent Literals:** Use bare `%` (not `%%`) for literal percent signs in CMP-consumed strings. ### String Formatting Decision Tree Choose the right tool for the job: | Scenario | Tool | Example | |----------|------|---------| | **Metric display** (temp, voltage, %, signal) | `MetricFormatter.*` | `MetricFormatter.temperature(25.0f, isFahrenheit)` → `"77.0°F"` | | **Simple number + unit** | `NumberFormatter` + interpolation | `"${NumberFormatter.format(val, 1)} dB"` | | **Localized template from strings.xml** | `stringResource(Res.string.key, preFormattedArgs)` | `stringResource(Res.string.battery, formatted)` | | **Non-composable template** (notifications, plain functions) | `formatString(template, args)` | `formatString(template, label, value)` | | **Hex formatting** | `formatString` | `formatString("!%08x", nodeNum)` | | **Date/time** | `DateFormatter` | `DateFormatter.format(instant)` | **Rules:** 1. **NEVER use `%.Nf` in strings.xml** — CMP cannot substitute them. Use `%N$s` and pre-format floats. 2. **Prefer `MetricFormatter`** over scattered `formatString("%.1f°C", temp)` calls. 3. **`formatString` (pure Kotlin)** is a pure-Kotlin `commonMain` implementation for: hex formats, multi-arg templates fetched at runtime, and chart axis formatters. Located in `core:common` `Formatter.kt`. 4. **`NumberFormatter`** always uses `.` as decimal separator — intentional for mesh networking precision. - **Workflow to Add a String:** 1. Add to `core/resources/src/commonMain/composeResources/values/strings.xml`. 2. Run `python3 scripts/sort-strings.py` — keeps the file sorted and regenerates `strings-index.txt`. 3. Use the generated `org.meshtastic.core.resources.<key>` symbol. 4. Validate UI presentation. - **Schema strings are generated, not written.** Every label and description in the protobufs field metadata is in `values/schema_strings.xml`, keyed by schema path: `Config.LoRaConfig.hop_limit` is `Res.string.schema_lora_hop_limit`, its summary `schema_lora_hop_limit_description`, the enum value `PositionFlags.DOP` `schema_position_positionflags_dop` (all indexed under `### SCHEMA` in `strings-index.txt`). A control that edits one whole schema field uses that key; a control that edits a bit, a threshold, a negation or drops a unit keeps a hand-written string. Never edit the generated file or write a `schema_` key by hand; `:schema-strings:test` fails on both. Wrong wording is a `protobufs` change. A merged protobufs pin bump triggers a `scheduled-updates` run on main that regenerates the file (it records the pin it was built from); run `./gradlew :schema-strings:sync` yourself only when you need a new key before that PR lands. ## 3. Tooling & Capabilities - **Image Loading:** Use `libs.coil` (Coil Compose) in feature modules. Configuration/Networking for Coil (`coil-network-ktor3`) happens strictly in the `androidApp` and `desktopApp` host modules. - **QR Codes:** Use `rememberQrCodePainter` from `core:ui/commonMain` powered by `qrcode-kotlin`. No ZXing or Android Bitmap APIs in shared code. ## 4. Compose Previews - **Preview in commonMain:** CMP 1.11+ supports `@Preview` in `commonMain` via `compose-multiplatform-ui-tooling-preview`. Place preview functions alongside their composables. - **Import:** Use `androidx.compose.ui.tooling.preview.Preview`. The JetBrains-prefixed import (`org.jetbrains.compose.ui.tooling.preview.Preview`) is deprecated. ## 5. Dialog & State Patterns - **Dialog State Preservation:** Use `rememberSaveable` for dialog state (search queries, selected tabs, expanded flags) to preserve across configuration changes. Boolean and String types are auto-saveable — no custom `Saver` needed. ## 6. Driving the running desktop app CMP 1.12+ ships an MCP server inside Compose Hot Reload; `.mcp.json` registers it as `compose-hot-reload` (`:desktopApp:hotMcpServer`). With `./gradlew :desktopApp:hotRun` running, it drives the **live** app — inspect, input and reload without a rebuild. - **Tools:** `status`, `reload`, `await_reload`, `get_semantic_tree`, `click`, `type_text`, `scroll`, `get_logs`, `get_ui_error`, `take_screenshot`. - **Poll `status` until `connected: true`** before anything else — the server accepts requests before the app has connected to it. - **`get_semantic_tree` is the assertion surface:** roles, text, `selected`/`focused`, available actions and bounds. `click` addresses nodes by `nodeId` taken from that tree. Prefer it over `take_screenshot`, whose output depends on the host renderer. - **`reload` after editing sources** applies the change into the running app; use `await_reload` instead when the app was started with `--auto`. ## Reference Anchors - **Shared Strings:** `core/resources/src/commonMain/composeResources/values/strings.xml` - **Platform abstraction contract:** `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt` - **Provider wiring:** `androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt`
Auf GitHub ansehen