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 . -->