diff --git a/feature/widget/src/main/kotlin/org/meshtastic/feature/widget/AndroidAppWidgetUpdater.kt b/feature/widget/src/main/kotlin/org/meshtastic/feature/widget/AndroidAppWidgetUpdater.kt
index 415e0e11d..c6cef8aa3 100644
--- a/feature/widget/src/main/kotlin/org/meshtastic/feature/widget/AndroidAppWidgetUpdater.kt
+++ b/feature/widget/src/main/kotlin/org/meshtastic/feature/widget/AndroidAppWidgetUpdater.kt
@@ -17,22 +17,48 @@
package org.meshtastic.feature.widget
import android.content.Context
+import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.glance.appwidget.updateAll
import co.touchlab.kermit.Logger
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
import org.koin.core.annotation.Single
import org.meshtastic.core.repository.AppWidgetUpdater
+private const val WIDGET_UPDATE_DEBOUNCE_MS = 500L
+
@Single
-class AndroidAppWidgetUpdater(private val context: Context) : AppWidgetUpdater {
+class AndroidAppWidgetUpdater(private val context: Context, stateProvider: LocalStatsWidgetStateProvider) :
+ AppWidgetUpdater {
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
+
+ init {
+ // Observe state changes and trigger a widget re-render whenever the data changes.
+ // Glance compositions are ephemeral — the widget cannot self-update via collectAsState()
+ // alone, so we must call updateAll() externally to drive re-renders.
+ @OptIn(FlowPreview::class)
+ scope.launch {
+ stateProvider.state
+ .debounce(WIDGET_UPDATE_DEBOUNCE_MS)
+ .distinctUntilChanged { old, new -> old.copy(updateTimeMillis = 0) == new.copy(updateTimeMillis = 0) }
+ .collect { if (hasWidgetInstances()) updateAll() }
+ }
+ }
+
+ private suspend fun hasWidgetInstances(): Boolean =
+ GlanceAppWidgetManager(context).getGlanceIds(LocalStatsWidget::class.java).isNotEmpty()
+
override suspend fun updateAll() {
- // Kickstart the widget composition.
- // The widget internally uses collectAsState() and its own sampled StateFlow
- // to drive updates automatically without excessive IPC and recreation.
@Suppress("TooGenericExceptionCaught")
try {
LocalStatsWidget().updateAll(context)
} catch (e: Exception) {
- co.touchlab.kermit.Logger.e(e) { "Failed to update widgets" }
+ Logger.e(e) { "Failed to update widgets" }
}
}
}
diff --git a/feature/widget/src/main/kotlin/org/meshtastic/feature/widget/LocalStatsWidgetState.kt b/feature/widget/src/main/kotlin/org/meshtastic/feature/widget/LocalStatsWidgetState.kt
index ee40bd60b..b8aca2664 100644
--- a/feature/widget/src/main/kotlin/org/meshtastic/feature/widget/LocalStatsWidgetState.kt
+++ b/feature/widget/src/main/kotlin/org/meshtastic/feature/widget/LocalStatsWidgetState.kt
@@ -76,8 +76,6 @@ data class LocalStatsWidgetUiState(
val updateTimeMillis: Long = 0,
)
-private const val WIDGET_SUBSCRIPTION_TIMEOUT_MS = 5_000L
-
@Single
class LocalStatsWidgetStateProvider(nodeRepository: NodeRepository, serviceRepository: ServiceRepository) {
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
@@ -100,12 +98,7 @@ class LocalStatsWidgetStateProvider(nodeRepository: NodeRepository, serviceRepos
.map { input ->
mapToUiState(input.connectionState, input.totalNodes, input.onlineNodes, input.stats, input.localNode)
}
- .distinctUntilChanged()
- .stateIn(
- scope = scope,
- started = SharingStarted.WhileSubscribed(WIDGET_SUBSCRIPTION_TIMEOUT_MS),
- initialValue = LocalStatsWidgetUiState(),
- )
+ .stateIn(scope = scope, started = SharingStarted.Eagerly, initialValue = LocalStatsWidgetUiState())
private data class StateInput(
val connectionState: ConnectionState,
diff --git a/feature/widget/src/main/res/values/strings.xml b/feature/widget/src/main/res/values/strings.xml
new file mode 100644
index 000000000..1e47c86ee
--- /dev/null
+++ b/feature/widget/src/main/res/values/strings.xml
@@ -0,0 +1,20 @@
+
+
+
+ Meshtastic
+
diff --git a/feature/widget/src/main/res/xml/widget_local_stats_info.xml b/feature/widget/src/main/res/xml/widget_local_stats_info.xml
index da9863cd9..6dde1ea1e 100644
--- a/feature/widget/src/main/res/xml/widget_local_stats_info.xml
+++ b/feature/widget/src/main/res/xml/widget_local_stats_info.xml
@@ -16,6 +16,7 @@
~ along with this program. If not, see .
-->