feat(widget): Add Local Stats glance widget#4642
Conversation
This commit introduces a new home screen widget that displays key statistics about the local Meshtastic node. The new `LocalStatsWidget` is built using Glance for AppWidgets and shows the following information: * Battery level * Channel utilization * Air utilization (TX) * Number of online nodes vs. total nodes * Device uptime The widget updates automatically when the connection state changes or when new telemetry data is received from the device. This is achieved by triggering widget updates from the `MeshConnectionManager`. To support this feature, necessary dependencies for Glance have been added, and the widget components, including the `GlanceAppWidget`, `GlanceAppWidgetReceiver`, and XML provider info, have been implemented and registered in the `AndroidManifest.xml`. Signed-off-by: James Rich <[email protected]>
This commit significantly enhances the Local Stats widget by displaying more detailed information and handling various connection states.
The widget now shows the current connection status (Connecting, Disconnected, Device Sleeping) when not fully connected to a node. When connected, it displays a richer set of data points derived from the new `local_stats` telemetry packet, including:
* Packet traffic (TX/RX/Dupe)
* Relay statistics
* Diagnostic info like noise floor, bad packets, and dropped packets
The battery level indicator has been improved to show "Powered" when the device is externally powered (battery level > 100). The local node's short name is now displayed in a chip in the widget header.
Additionally, a minor string resource typo ("Lux" to "Look") has been corrected, and a new "Powered" string has been added.
Signed-off-by: James Rich <[email protected]>
This commit improves the display of local device statistics in both the service notification and the home screen widget. When a device's reported battery level is greater than 100%, it will now be displayed as "Powered" instead of showing a percentage. This provides a more accurate status for devices connected to an external power source. Additionally, the channel and air utilization statistics will no longer be shown if the values are not available. This prevents the display of "0%" for utilization when the data has not yet been received from the device. Signed-off-by: James Rich <[email protected]>
This change refactors how local device statistics are handled and exposed, ensuring the home screen widget updates correctly even when the full node object is not available. A new `StateFlow` for `localStats` has been added to the `NodeRepository`. This flow is updated whenever new telemetry data containing local stats is received from the device. The `LocalStatsWidget` now subscribes directly to this new `localStats` flow in the `NodeRepository` instead of deriving the stats from the local node's telemetry. This decouples the widget's data source from the full node object, providing a more direct and reliable stream of statistics for rendering. Signed-off-by: James Rich <[email protected]>
This commit refactors the `LocalStatsWidget` to prevent text from overflowing its container. The main statistics (Battery, Channel Utilization, Air Utilization) and the secondary statistics (Traffic, Relay, Diagnostics) have each been wrapped in their own `Column`. This ensures that they are rendered as distinct vertical groups, improving the layout and preventing visual bugs where text could overflow. Signed-off-by: James Rich <[email protected]>
…posable state This commit refactors the `LocalStatsWidget` to improve its structure and data handling. The widget now uses `Scaffold` and `TitleBar` from the Glance library for a more standardized layout. This replaces the previous custom `Column` and `Row` implementation for the header. State collection has been simplified by removing `collectAsState`. Data from repositories and state handlers is now accessed directly within the `provideContent` block. This change makes the widget's update logic more direct and less reliant on Compose runtime observation. Signed-off-by: James Rich <[email protected]>
This commit refactors the `LocalStatsWidget` for improved state management, a more responsive UI, and a cleaner architecture. It also introduces visual refinements to make the widget more compact and adaptable to various sizes. A new `LocalStatsWidgetStateProvider` class has been created to centralize and streamline state management. This provider consolidates data flows from `NodeRepository` and `ServiceRepository` into a single `StateFlow<LocalStatsWidgetUiState>`, simplifying the widget's logic and ensuring it recomposes efficiently. The `MeshConnectionManager` now triggers widget updates when local node information or statistics change, ensuring the displayed data is always current. Key UI and layout enhancements include: - Use of `SizeMode.Exact` for more predictable sizing. - Adaptive layout that adjusts for smaller widget sizes by hiding or condensing non-essential information like TX air utilization, relay stats, and diagnostic data. - A loading indicator is now shown during the "Connecting" state. - Minor visual refinements to spacing, fonts, and corner radius for a more polished look. Signed-off-by: James Rich <[email protected]>
This commit introduces a manual refresh button to the Local Stats widget and refines its layout and data handling. A refresh icon has been added to the widget's title bar. Tapping this button triggers an `ActionCallback` that requests the latest device metrics and local statistics from the node, allowing users to manually update the displayed data on demand. The widget's data collection logic has been improved. The state flow now uses `SharingStarted.WhileSubscribed` to avoid unnecessary work when the widget is not active. Additionally, the widget's layout has been tweaked for better presentation across different sizes: * The `NodeChip` has been removed from the main content area for a cleaner look. * The breakpoints for condensed layouts have been adjusted. * Vertical spacing between elements has been reduced for a more compact appearance. Signed-off-by: James Rich <[email protected]>
This commit refactors the `LocalStatsWidget` to improve its visual layout, resilience to different sizes, and code structure. The widget has been redesigned to be more informative and visually appealing. Key changes include: - A major redesign of the widget layout, moving away from a size-dependent structure to a more flexible and consistent presentation. - The introduction of a `CompositionLocalProvider` to ensure the widget has access to the correct `Context`, `Configuration`, and `Density`, improving rendering reliability. - Code has been broken down into smaller, more manageable composable functions like `StatsContent`, `Disconnected`, and `Footer` for better readability and maintenance. - A `NodeChip` has been added to display the local node's short name and colors, providing better at-a-glance identification. - String resources are now correctly resolved using `stringResource` from the Compose resources library, with a fix in `ContextExt.kt` to suppress the `SpreadOperator` warning for formatted strings. - The use of hardcoded size checks (e.g., `isCondensedHeight`) has been removed in favor of a responsive design that works better across various widget sizes. - A minor correction in `MeshServiceNotificationsImpl.kt` replaces a magic number `100` with the `MAX_BATTERY_LEVEL` constant for checking if a device is externally powered. Signed-off-by: James Rich <[email protected]>
This commit refines the user interface of the LocalStatsWidget for a cleaner and more polished appearance. Key changes include: - Wrapping the entire widget content within `GlanceTheme` to ensure consistent theming. - Adding vertical alignment to the main statistics row, ensuring the node chip and stats are properly aligned. - Increasing the padding within the `NodeChip` for better visual spacing. - Adding bottom padding to the footer to prevent it from sitting too close to the edge of the widget. Signed-off-by: James Rich <[email protected]>
This commit introduces support for Glance widget previews, a feature available from Android 15 (Vanilla Ice Cream) onwards. This allows the Meshtastic app to display a rich, live preview of the Local Stats widget directly in the widget picker. To achieve this, the following changes were made: - The `providePreview` method is implemented in `LocalStatsWidget.kt` to generate a composable preview using mock data. - A check for Android 15 or higher is added in `MeshUtilApplication.kt` to publish the generated widget preview on app startup. - The `glance` dependency is updated to `1.2.0-rc01` and the `androidx-glance-preview` library is added. - A new `previewLayout` is defined for the widget, which is used by the system on older Android versions where scalable previews are not supported. Signed-off-by: James Rich <[email protected]>
This commit refactors the `LocalStatsWidget` to enhance its user interface, improve state management, and make it more responsive to different sizes. A key change is the introduction of a `mapToUiState` function within `LocalStatsWidgetStateProvider`. This function transforms raw data from repositories into a display-ready `LocalStatsWidgetUiState`, centralizing UI logic and string formatting. The state flow is now sampled to limit updates to once every two seconds, reducing unnecessary recompositions. The widget's UI has been significantly refined for better readability and compactness, especially on smaller widget sizes: - **Adaptive Layout:** The layout now adapts to `isNarrow` and `isShort` conditions, adjusting font sizes and hiding non-essential details to fit smaller spaces. - **Heap Memory Display:** The widget can now display heap memory usage (`Free / Total`) as a new progress bar statistic. - **Click Action:** The entire widget is now clickable, launching the main app activity. - **Visual Polish:** Spacing, padding, and font sizes have been fine-tuned for a cleaner look. The "Disconnected" view now shows the app icon instead of a progress indicator when not actively connecting. - **String Abbreviation:** The traffic statistics string has been shortened from "Dupes" to "D:" to save space. Under the hood, the `WidgetContent` has been broken down into `FullStatsContent` and smaller, more focused composables. Mock data has been simplified and moved to a `mockState` object. Build dependencies for `glance-appwidget-preview` were also corrected. Signed-off-by: James Rich <[email protected]>
❌ 1 Tests Failed:
View the top 1 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
This commit introduces unit tests for the `MeshConnectionManager` to verify that widget updates are triggered correctly. To support this, the following changes were made: - The `androidx.glance.appwidget` library has been added as a test dependency. - The `MeshConnectionManager` now accepts a `Context` in its constructor, which is necessary for mocking widget update calls. - `MeshConnectionManagerTest` has been updated to provide the mocked `Context` and to mock the static `updateAll` method from `GlanceAppWidget`. - The test setup now also mocks additional data flows like `ourNodeInfo` and `localStats` from the `NodeRepository`. Signed-off-by: James Rich <[email protected]>
This commit introduces persistence for local node statistics (`LocalStats`) by leveraging Jetpack DataStore. Previously, `LocalStats` were stored in memory within the `NodeRepository`, meaning they were lost when the application was closed. Now, a `LocalStatsSerializer` and `LocalStatsDataSource` have been implemented to save and retrieve `LocalStats` from a protobuf file. The `NodeRepository` has been updated to use this new data source, ensuring that the latest statistics from the locally connected node are persisted across application restarts. Signed-off-by: James Rich <[email protected]>
This commit adds heap memory information to the local device statistics notification. When available (`heap_free_bytes` or `heap_total_bytes` is greater than 0), the notification will now include a line detailing the free and total heap memory in bytes. Signed-off-by: James Rich <[email protected]>
The `NodeRepository` constructor has been updated to accept `LocalStatsDataSource` as a dependency. This change is reflected in the corresponding test file, `NodeRepositoryTest.kt`, where the mock `LocalStatsDataSource` is now provided during the repository's instantiation. Signed-off-by: James Rich <[email protected]>
…cies This commit refactors the `LocalStatsWidget` to improve its stability and performance. String resources are now resolved within the `LocalStatsWidgetStateProvider` instead of directly in the Glance composable. This avoids potential issues with recomposition and ensures a more stable widget. The state flow processing in the provider has also been simplified by removing an unnecessary `flatMapLatest` and `flow` builder, directly mapping the input to the UI state. In addition, the `AdaptiveContactsScreen` has been updated to use the `PredictiveBackHandler` for a better user experience on supported Android versions. Signed-off-by: James Rich <[email protected]>
This commit refactors the `LocalStatsWidget` update mechanism to be more efficient and removes redundant operations. Widget updates are now triggered from a single, consolidated `merge` flow in `MeshConnectionManager`. This flow combines `connectionState`, `ourNodeInfo`, and `localStats` streams, ensuring the widget is updated only when necessary and avoiding excessive IPC calls. Previously, separate collectors for each flow could lead to redundant updates. The `getString` utility in `ContextExt` has been enhanced by introducing suspending `getStringSuspend` variants. This allows string resource resolution to occur within a coroutine context without blocking the main thread. Consequently, `LocalStatsWidgetState` has been updated to use these new suspending functions. A minor string resource typo for "Lux" has also been corrected. Signed-off-by: James Rich <[email protected]>
This commit enhances the user experience for the `LocalStatsWidget` on Android 15+ and streamlines its update mechanism. For devices running Android 15 (Vanilla Ice Cream) or newer, the widget preview generation has been significantly improved. The system now attempts to generate an initial preview, waits for actual node data to become available, and then pushes a second, more realistic preview reflecting the real device state. This ensures that the widget preview in the picker is not just a mock-up but a true representation of the widget's appearance with live data. The widget update logic has been simplified. Instead of merging multiple flows in `MeshConnectionManager` to trigger updates, the widget is now kickstarted once upon service creation. The widget itself is now responsible for collecting state changes via its `StateProvider`, which simplifies the manager's logic and avoids redundant IPC calls. Consequently, the explicit sampling in `LocalStatsWidgetStateProvider` has been removed, as updates are now driven by the widget's own composition lifecycle. Signed-off-by: James Rich <[email protected]>
There was a problem hiding this comment.
Pull request overview
Adds a new Glance-based home screen widget to display local node statistics, including a refresh action and a state provider that consolidates connection/node/stats flows to drive widget updates.
Changes:
- Introduces
LocalStatsWidget(GlanceAppWidget) + receiver, refresh action callback, and widget provider/preview XML resources. - Persists local stats via a new
LocalStatsDataStore and exposes it fromNodeRepository; triggers updates fromMeshConnectionManager. - Updates notifications formatting and enables Android 15+ scalable widget previews; adds required Glance dependencies and some string tweaks.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| gradle/libs.versions.toml | Adds Glance version + library coordinates to the version catalog. |
| app/build.gradle.kts | Adds Glance (appwidget/material3/preview) dependencies for widget implementation and previews. |
| app/src/main/AndroidManifest.xml | Registers the new widget receiver and provider metadata. |
| app/src/main/java/com/geeksville/mesh/widget/LocalStatsWidget.kt | Implements the Glance widget UI, responsive sizing, and preview rendering. |
| app/src/main/java/com/geeksville/mesh/widget/LocalStatsWidgetReceiver.kt | Adds GlanceAppWidgetReceiver for the new widget. |
| app/src/main/java/com/geeksville/mesh/widget/LocalStatsWidgetState.kt | Adds widget UI state model + provider that combines repository/service flows. |
| app/src/main/java/com/geeksville/mesh/widget/RefreshLocalStatsAction.kt | Adds a widget refresh action to request telemetry from the node. |
| app/src/main/java/com/geeksville/mesh/service/MeshConnectionManager.kt | Triggers widget kickstart/update and persists local stats from telemetry updates. |
| app/src/main/java/com/geeksville/mesh/service/MeshServiceNotificationsImpl.kt | Uses cached local stats and extends notification formatting (powered/heap/utilization changes). |
| app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt | Adds Android 15+ widget preview publishing using Glance preview APIs. |
| core/resources/src/commonMain/composeResources/values/strings.xml | Adds/updates strings for heap/updated-at/powered and shortens traffic label. |
| core/resources/src/androidMain/kotlin/org/meshtastic/core/resources/ContextExt.kt | Adds suspending string helpers and switches formatted string resolution to String.format. |
| core/datastore/src/main/kotlin/org/meshtastic/core/datastore/serializer/LocalStatsSerializer.kt | Adds proto serializer for LocalStats DataStore. |
| core/datastore/src/main/kotlin/org/meshtastic/core/datastore/LocalStatsDataSource.kt | Adds DataStore-backed data source for saving/reading LocalStats. |
| core/datastore/src/main/kotlin/org/meshtastic/core/datastore/di/DataStoreModule.kt | Provides DataStore<LocalStats> via Hilt. |
| core/data/src/main/kotlin/org/meshtastic/core/data/repository/NodeRepository.kt | Exposes localStats flow and an update method to persist stats. |
| core/data/src/test/kotlin/org/meshtastic/core/data/repository/NodeRepositoryTest.kt | Updates repository construction for the new dependency. |
| app/src/test/java/com/geeksville/mesh/service/MeshConnectionManagerTest.kt | Updates test wiring and mocks for Glance widget update calls + new repository flows. |
| feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/ui/contact/AdaptiveContactsScreen.kt | Switches to PredictiveBackHandler for predictive back support. |
| app/src/main/res/xml/local_stats_widget_info.xml | Adds AppWidgetProviderInfo metadata for the widget. |
| app/src/main/res/layout/widget_local_stats_preview.xml | Adds a widget picker preview layout. |
| app/src/main/res/drawable/ic_refresh.xml | Adds refresh icon for the widget action. |
core/data/src/main/kotlin/org/meshtastic/core/data/repository/NodeRepository.kt
Show resolved
Hide resolved
app/src/main/java/com/geeksville/mesh/service/MeshServiceNotificationsImpl.kt
Outdated
Show resolved
Hide resolved
app/src/main/java/com/geeksville/mesh/widget/LocalStatsWidget.kt
Outdated
Show resolved
Hide resolved
app/src/main/java/com/geeksville/mesh/widget/LocalStatsWidgetState.kt
Outdated
Show resolved
Hide resolved
This commit adds a timestamp to the footer of the Local Stats home screen widget, indicating when the data was last updated. Changes include: - A new string resource `local_stats_updated_at` has been added. - The `LocalStatsWidgetUiState` is updated to include the formatted timestamp. - The widget's footer layout is refactored to a `Column` to accommodate the new timestamp text below the existing node count and uptime information. Signed-off-by: James Rich <[email protected]>
a4433b0 to
beb275e
Compare
This commit enhances the local stats widget's reliability and user experience. The widget preview update process is now more robust. It will wait up to 30 seconds for fresh node data before rendering the preview. If no data is received within this period, it logs a timeout message instead of waiting indefinitely, preventing potential hangs on startup. The widget's data handling has been improved to gracefully manage scenarios where fresh telemetry data (`LocalStats`) is not yet available. It now falls back to older, persisted device metrics from the database, ensuring that some statistics are always displayed if they exist. The UI has been updated to hide sections for which no data is available (e.g., Heap, Traffic, Relays). Key changes include: - A division-by-zero error in the heap usage calculation has been fixed. - `localStats` is now non-nullable throughout the data layer, simplifying logic. - String resources for the widget have been consolidated and improved. - Tests have been updated to reflect the new non-nullable data model. Signed-off-by: James Rich <[email protected]>
The explicit package qualification for `org.meshtastic.proto.LocalStats` has been removed in favor of a direct import. This change simplifies the code and improves readability within the `LocalStatsWidgetState.kt` file. Signed-off-by: James Rich <[email protected]>
| fun updateTelemetry(telemetry: Telemetry) { | ||
| telemetry.local_stats?.let { nodeRepository.updateLocalStats(it) } | ||
| updateStatusNotification(telemetry) | ||
| } |
There was a problem hiding this comment.
updateAll() is only called once during start(), and updateTelemetry() updates nodeRepository.localStats without triggering a widget update. Since Glance widgets don't continuously recompose from collectAsState() after the update pass completes, the widget UI will likely stay stale after telemetry/connection changes (including after the manual refresh action). Consider calling LocalStatsWidget().updateAll(context) (ideally throttled/debounced) when relevant state changes, e.g., after writing local_stats and/or when connectionStateHolder.connectionState changes.
| // Kickstart the widget composition. The widget internally uses collectAsState() | ||
| // and its own sampled StateFlow to drive updates automatically without excessive IPC and recreation. | ||
| scope.launch { | ||
| try { | ||
| LocalStatsWidget().updateAll(context) | ||
| } catch (@Suppress("TooGenericExceptionCaught") e: Exception) { |
There was a problem hiding this comment.
The comment says the widget will "drive updates automatically" via collectAsState() and an internal StateFlow, but Glance rendering is typically snapshot-based per update; a single updateAll() here won't keep the widget fresh. Either adjust the comment to reflect the actual update mechanism, or add explicit widget update triggers on state changes (with throttling) so the behavior matches the intent.
app/src/main/java/com/geeksville/mesh/widget/LocalStatsWidget.kt
Outdated
Show resolved
Hide resolved
This commit refactors the `LocalStatsWidget` to use localized strings for its mock preview state. The hardcoded `mockState` has been replaced with a `createMockWidgetState()` suspend function. This new function fetches strings dynamically using `getStringSuspend`, ensuring that the widget's preview in tools like Glance is properly localized. Additionally, tests for `LocalStatsWidgetStateProvider` and `MeshConnectionManager` have been slightly cleaned up for clarity. Signed-off-by: James Rich <[email protected]>
This introduces a new home screen widget for displaying local node statistics using Glance for App Widgets. This provides users with at-a-glance information about their device and the mesh network directly from their home screen.
A new
LocalStatsWidgethas been created, along with itsGlanceAppWidgetReceiverand supporting XML files. The widget has a responsive layout that adapts to different sizes, showing more or less detail accordingly.To manage the widget's data, a new
LocalStatsWidgetStateProviderhas been implemented. This class centralizes state management by consolidating data flows fromNodeRepositoryandServiceRepositoryinto a singleStateFlow<LocalStatsWidgetUiState>, which simplifies the widget's logic and ensures efficient updates.Key features and changes include:
Scaffold-based layout with aTitleBarshowing the app icon and a manual refresh button.ActionCallbackto request the latest device metrics and local statistics from the node.MeshConnectionManagernow triggers widget updates when connection state, local node information, or statistics change.build.gradle.ktsand updated the version to1.2.0-rc01.MeshUtilApplication.kt.Screen_recording_20260224_220700.mp4