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.

来源信息

仓库
meshtastic/Meshtastic-Android
最近来源活动
2026年10月3日 13:58
检测到的 SKILL.md 语言
英语
星标
1,862
分支
520

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
2 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
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`
在 GitHub 查看