Add stateInWhileSubscribed extension (#3456)

This commit is contained in:
Phil Oliver
2025-10-13 16:04:29 -04:00
committed by GitHub
parent 5c745bdd90
commit 3a232fc33f
17 changed files with 96 additions and 199 deletions

View File

@@ -52,6 +52,7 @@ import org.meshtastic.core.datastore.model.RecentAddress
import org.meshtastic.core.model.util.anonymize
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import timber.log.Timber
import javax.inject.Inject
@@ -168,33 +169,21 @@ constructor(
val mockDevice = DeviceListEntry.Mock("Demo Mode")
val bleDevicesForUi: StateFlow<List<DeviceListEntry>> =
bleDevicesFlow.stateIn(viewModelScope, SharingStarted.WhileSubscribed(SHARING_STARTED_TIMEOUT_MS), emptyList())
bleDevicesFlow.stateInWhileSubscribed(initialValue = emptyList())
/** UI StateFlow for discovered TCP devices. */
val discoveredTcpDevicesForUi: StateFlow<List<DeviceListEntry>> =
processedDiscoveredTcpDevicesFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(SHARING_STARTED_TIMEOUT_MS),
listOf(),
)
processedDiscoveredTcpDevicesFlow.stateInWhileSubscribed(initialValue = listOf())
/** UI StateFlow for recently connected TCP devices that are not currently discovered. */
val recentTcpDevicesForUi: StateFlow<List<DeviceListEntry>> =
filteredRecentTcpDevicesFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(SHARING_STARTED_TIMEOUT_MS),
listOf(),
)
filteredRecentTcpDevicesFlow.stateInWhileSubscribed(initialValue = listOf())
val usbDevicesForUi: StateFlow<List<DeviceListEntry>> =
combine(usbDevicesFlow, showMockInterface) { usb, showMock ->
usb + if (showMock) listOf(mockDevice) else emptyList()
}
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(SHARING_STARTED_TIMEOUT_MS),
if (showMockInterface.value) listOf(mockDevice) else emptyList(),
)
.stateInWhileSubscribed(initialValue = if (showMockInterface.value) listOf(mockDevice) else emptyList())
init {
serviceRepository.statusMessage.onEach { errorText.value = it }.launchIn(viewModelScope)
@@ -217,11 +206,7 @@ constructor(
val selectedNotNullFlow: StateFlow<String> =
selectedAddressFlow
.map { it ?: NO_DEVICE_SELECTED }
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(SHARING_STARTED_TIMEOUT_MS),
selectedAddressFlow.value ?: NO_DEVICE_SELECTED,
)
.stateInWhileSubscribed(initialValue = selectedAddressFlow.value ?: NO_DEVICE_SELECTED)
val scanResult = MutableLiveData<MutableMap<String, DeviceListEntry>>(mutableMapOf())
@@ -385,4 +370,3 @@ constructor(
}
const val NO_DEVICE_SELECTED = "n"
private const val SHARING_STARTED_TIMEOUT_MS = 5000L

View File

@@ -42,7 +42,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.meshtastic.core.analytics.platform.PlatformAnalytics
import org.meshtastic.core.data.repository.FirmwareReleaseRepository
@@ -58,6 +57,7 @@ import org.meshtastic.core.service.MeshServiceNotifications
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.toSharedContact
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.AdminProtos
import org.meshtastic.proto.AppOnlyProtos
import org.meshtastic.proto.MeshProtos
@@ -171,10 +171,7 @@ constructor(
get() = serviceRepository.meshService
val unreadMessageCount =
packetRepository
.getUnreadCountTotal()
.map { it.coerceAtLeast(0) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000L), 0)
packetRepository.getUnreadCountTotal().map { it.coerceAtLeast(0) }.stateInWhileSubscribed(initialValue = 0)
// hardware info about our local device (can be null)
val myNodeInfo: StateFlow<MyNodeEntity?>

View File

@@ -18,20 +18,18 @@
package com.geeksville.mesh.ui.connections
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.stateIn
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.data.repository.RadioConfigRepository
import org.meshtastic.core.database.entity.MyNodeEntity
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.prefs.ui.UiPrefs
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.LocalOnlyProtos.LocalConfig
import javax.inject.Inject
@@ -46,11 +44,7 @@ constructor(
private val uiPrefs: UiPrefs,
) : ViewModel() {
val localConfig: StateFlow<LocalConfig> =
radioConfigRepository.localConfigFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000L),
LocalConfig.getDefaultInstance(),
)
radioConfigRepository.localConfigFlow.stateInWhileSubscribed(initialValue = LocalConfig.getDefaultInstance())
val connectionState = serviceRepository.connectionState

View File

@@ -24,9 +24,7 @@ import com.geeksville.mesh.model.Contact
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.data.repository.PacketRepository
@@ -37,6 +35,7 @@ import org.meshtastic.core.model.util.getChannel
import org.meshtastic.core.model.util.getShortDate
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.channelSet
import javax.inject.Inject
import kotlin.collections.map
@@ -55,12 +54,7 @@ constructor(
val connectionState = serviceRepository.connectionState
val channels =
radioConfigRepository.channelSetFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000),
channelSet {},
)
val channels = radioConfigRepository.channelSetFlow.stateInWhileSubscribed(initialValue = channelSet {})
val contactList =
combine(
@@ -117,11 +111,7 @@ constructor(
)
}
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
.stateInWhileSubscribed(initialValue = emptyList())
fun getNode(userId: String?) = nodeRepository.getNode(userId ?: DataPacket.ID_BROADCAST)

View File

@@ -23,9 +23,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.meshtastic.core.analytics.DataPair
import org.meshtastic.core.analytics.platform.PlatformAnalytics
@@ -33,6 +31,7 @@ import org.meshtastic.core.data.repository.RadioConfigRepository
import org.meshtastic.core.model.util.toChannelSet
import org.meshtastic.core.proto.getChannelList
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.AppOnlyProtos
import org.meshtastic.proto.ChannelProtos
import org.meshtastic.proto.ConfigProtos.Config
@@ -55,18 +54,9 @@ constructor(
val connectionState = serviceRepository.connectionState
val localConfig =
radioConfigRepository.localConfigFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000),
LocalConfig.getDefaultInstance(),
)
radioConfigRepository.localConfigFlow.stateInWhileSubscribed(initialValue = LocalConfig.getDefaultInstance())
val channels =
radioConfigRepository.channelSetFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000),
channelSet {},
)
val channels = radioConfigRepository.channelSetFlow.stateInWhileSubscribed(initialValue = channelSet {})
// managed mode disables all access to configuration
val isManaged: Boolean

View File

@@ -21,12 +21,11 @@ import android.os.RemoteException
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.meshtastic.core.data.repository.RadioConfigRepository
import org.meshtastic.core.proto.getChannelList
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.AppOnlyProtos
import org.meshtastic.proto.ChannelProtos
import org.meshtastic.proto.ConfigProtos.Config
@@ -44,19 +43,10 @@ constructor(
private val serviceRepository: ServiceRepository,
) : ViewModel() {
val channels =
radioConfigRepository.channelSetFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000L),
channelSet {},
)
val channels = radioConfigRepository.channelSetFlow.stateInWhileSubscribed(initialValue = channelSet {})
private val localConfig =
radioConfigRepository.localConfigFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000L),
LocalConfig.getDefaultInstance(),
)
radioConfigRepository.localConfigFlow.stateInWhileSubscribed(initialValue = LocalConfig.getDefaultInstance())
/** Set the radio config (also updates our saved copy in preferences). */
fun setChannels(channelSet: AppOnlyProtos.ChannelSet) = viewModelScope.launch {

View File

@@ -20,14 +20,13 @@ package org.meshtastic.core.ui.share
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.service.ServiceAction
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.AdminProtos
import javax.inject.Inject
@@ -40,13 +39,7 @@ constructor(
) : ViewModel() {
val unfilteredNodes: StateFlow<List<Node>> =
nodeRepository
.getNodes()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
nodeRepository.getNodes().stateInWhileSubscribed(initialValue = emptyList())
fun addSharedContact(sharedContact: AdminProtos.SharedContact) =
viewModelScope.launch { serviceRepository.onServiceAction(ServiceAction.ImportContact(sharedContact)) }

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@file:Suppress("Wrapping", "UnusedImports", "SpacingAroundColon")
package org.meshtastic.core.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
/**
* Extension for converting a [Flow] to a [StateFlow] in a [ViewModel] context.
*
* @param initialValue the initial value of the state flow
* @param stopTimeout configures a delay between the disappearance of the last subscriber and the stopping of the
* sharing coroutine.
*/
context(viewModel: ViewModel)
fun <T> Flow<T>.stateInWhileSubscribed(initialValue: T, stopTimeout: Duration = 5.seconds): StateFlow<T> = stateIn(
scope = viewModel.viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = stopTimeout.inWholeMilliseconds),
initialValue = initialValue,
)

View File

@@ -17,10 +17,7 @@
package org.meshtastic.feature.map
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import org.meshtastic.core.common.BuildConfigProvider
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.data.repository.PacketRepository
@@ -28,6 +25,7 @@ import org.meshtastic.core.data.repository.RadioConfigRepository
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.prefs.map.MapPrefs
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.LocalOnlyProtos.LocalConfig
import javax.inject.Inject
@@ -50,11 +48,7 @@ constructor(
}
val localConfig =
radioConfigRepository.localConfigFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000L),
LocalConfig.getDefaultInstance(),
)
radioConfigRepository.localConfigFlow.stateInWhileSubscribed(initialValue = LocalConfig.getDefaultInstance())
val config
get() = localConfig.value

View File

@@ -32,13 +32,11 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -53,6 +51,7 @@ import org.meshtastic.core.datastore.UiPreferencesDataSource
import org.meshtastic.core.prefs.map.GoogleMapsPrefs
import org.meshtastic.core.prefs.map.MapPrefs
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.ConfigProtos
import timber.log.Timber
import java.io.File
@@ -97,9 +96,7 @@ constructor(
val errorFlow: SharedFlow<String> = _errorFlow.asSharedFlow()
val customTileProviderConfigs: StateFlow<List<CustomTileProviderConfig>> =
customTileProviderRepository
.getCustomTileProviders()
.stateIn(scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList())
customTileProviderRepository.getCustomTileProviders().stateInWhileSubscribed(initialValue = emptyList())
private val _selectedCustomTileProviderUrl = MutableStateFlow<String?>(null)
val selectedCustomTileProviderUrl: StateFlow<String?> = _selectedCustomTileProviderUrl.asStateFlow()
@@ -110,11 +107,7 @@ constructor(
val displayUnits =
radioConfigRepository.deviceProfileFlow
.mapNotNull { it.config.display.units }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC,
)
.stateInWhileSubscribed(initialValue = ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC)
fun addCustomTileProvider(name: String, urlTemplate: String) {
viewModelScope.launch {

View File

@@ -23,12 +23,10 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.data.repository.PacketRepository
@@ -38,6 +36,7 @@ import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.prefs.map.MapPrefs
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.MeshProtos
import timber.log.Timber
import java.util.concurrent.TimeUnit
@@ -78,11 +77,7 @@ abstract class BaseMapViewModel(
nodeRepository
.getNodes()
.map { nodes -> nodes.filterNot { node -> node.isIgnored } }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
.stateInWhileSubscribed(initialValue = emptyList())
val waypoints: StateFlow<Map<Int, Packet>> =
packetRepository
@@ -94,7 +89,7 @@ abstract class BaseMapViewModel(
it.data.waypoint!!.expire == 0 || it.data.waypoint!!.expire > System.currentTimeMillis() / 1000
}
}
.stateIn(scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyMap())
.stateInWhileSubscribed(initialValue = emptyMap())
private val showOnlyFavorites = MutableStateFlow(mapPrefs.showOnlyFavorites)
@@ -119,9 +114,7 @@ abstract class BaseMapViewModel(
val ourNodeInfo: StateFlow<Node?> = nodeRepository.ourNodeInfo
val isConnected =
serviceRepository.connectionState
.map { it.isConnected() }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), false)
serviceRepository.connectionState.map { it.isConnected() }.stateInWhileSubscribed(initialValue = false)
fun toggleOnlyFavorites() {
val current = showOnlyFavorites.value
@@ -187,9 +180,7 @@ abstract class BaseMapViewModel(
) { favoritesOnly, showWaypoints, showPrecisionCircle, lastHeardFilter, lastHeardTrackFilter ->
MapFilterState(favoritesOnly, showWaypoints, showPrecisionCircle, lastHeardFilter, lastHeardTrackFilter)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
.stateInWhileSubscribed(
initialValue =
MapFilterState(
showOnlyFavorites.value,

View File

@@ -19,16 +19,13 @@ package org.meshtastic.feature.map.node
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.toRoute
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.toList
import org.meshtastic.core.common.BuildConfigProvider
import org.meshtastic.core.data.repository.MeshLogRepository
@@ -36,6 +33,7 @@ import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.navigation.NodesRoutes
import org.meshtastic.core.prefs.map.MapPrefs
import org.meshtastic.core.proto.toPosition
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.feature.map.model.CustomTileSource
import org.meshtastic.proto.MeshProtos.Position
import org.meshtastic.proto.Portnums.PortNum
@@ -57,7 +55,7 @@ constructor(
nodeRepository.nodeDBbyNum
.mapLatest { it[destNum] }
.distinctUntilChanged()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000L), null)
.stateInWhileSubscribed(initialValue = null)
val applicationId = buildConfigProvider.applicationId
@@ -73,7 +71,7 @@ constructor(
}
.toList()
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000L), emptyList())
.stateInWhileSubscribed(initialValue = emptyList())
val tileSource
get() = CustomTileSource.getTileSource(mapPrefs.mapStyle)

View File

@@ -23,12 +23,10 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.meshtastic.core.data.repository.NodeRepository
@@ -43,6 +41,7 @@ import org.meshtastic.core.prefs.ui.UiPrefs
import org.meshtastic.core.service.MeshServiceNotifications
import org.meshtastic.core.service.ServiceAction
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.channelSet
import org.meshtastic.proto.sharedContact
import timber.log.Timber
@@ -69,40 +68,21 @@ constructor(
val connectionState = serviceRepository.connectionState
val nodeList: StateFlow<List<Node>> =
nodeRepository
.getNodes()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
val nodeList: StateFlow<List<Node>> = nodeRepository.getNodes().stateInWhileSubscribed(initialValue = emptyList())
val channels =
radioConfigRepository.channelSetFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000),
channelSet {},
)
val channels = radioConfigRepository.channelSetFlow.stateInWhileSubscribed(channelSet {})
private val _showQuickChat = MutableStateFlow(uiPrefs.showQuickChat)
val showQuickChat: StateFlow<Boolean> = _showQuickChat
val quickChatActions =
quickChatActionRepository
.getAllActions()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
val quickChatActions = quickChatActionRepository.getAllActions().stateInWhileSubscribed(initialValue = emptyList())
private val contactKeyForMessages: MutableStateFlow<String?> = MutableStateFlow(null)
private val messagesForContactKey: StateFlow<List<Message>> =
contactKeyForMessages
.filterNotNull()
.flatMapLatest { contactKey -> packetRepository.getMessagesFrom(contactKey, ::getNode) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
.stateInWhileSubscribed(initialValue = emptyList())
fun setTitle(title: String) {
viewModelScope.launch { _title.value = title }

View File

@@ -21,21 +21,17 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.meshtastic.core.data.repository.QuickChatActionRepository
import org.meshtastic.core.database.entity.QuickChatAction
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import javax.inject.Inject
@HiltViewModel
class QuickChatViewModel @Inject constructor(private val quickChatActionRepository: QuickChatActionRepository) :
ViewModel() {
val quickChatActions
get() =
quickChatActionRepository
.getAllActions()
.stateIn(viewModelScope, SharingStarted.Companion.WhileSubscribed(5_000), emptyList())
get() = quickChatActionRepository.getAllActions().stateInWhileSubscribed(initialValue = emptyList())
fun updateActionPositions(actions: List<QuickChatAction>) {
viewModelScope.launch(Dispatchers.IO) {

View File

@@ -24,13 +24,11 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.data.repository.RadioConfigRepository
@@ -39,6 +37,7 @@ import org.meshtastic.core.database.model.NodeSortOption
import org.meshtastic.core.datastore.UiPreferencesDataSource
import org.meshtastic.core.service.ServiceAction
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.AdminProtos
import timber.log.Timber
import javax.inject.Inject
@@ -55,19 +54,9 @@ constructor(
val ourNodeInfo: StateFlow<Node?> = nodeRepository.ourNodeInfo
val onlineNodeCount =
nodeRepository.onlineNodeCount.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = 0,
)
val onlineNodeCount = nodeRepository.onlineNodeCount.stateInWhileSubscribed(initialValue = 0)
val totalNodeCount =
nodeRepository.totalNodeCount.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = 0,
)
val totalNodeCount = nodeRepository.totalNodeCount.stateInWhileSubscribed(initialValue = 0)
val connectionState = serviceRepository.connectionState
@@ -103,11 +92,7 @@ constructor(
tempInFahrenheit = profile.moduleConfig.telemetry.environmentDisplayFahrenheit,
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = NodesUiState(),
)
.stateInWhileSubscribed(initialValue = NodesUiState())
val nodeList: StateFlow<List<Node>> =
combine(nodeFilter, nodeSortOption, ::Pair)
@@ -122,20 +107,10 @@ constructor(
)
.map { list -> list.filter { it.isIgnored == filter.showIgnored } }
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
.stateInWhileSubscribed(initialValue = emptyList())
val unfilteredNodeList: StateFlow<List<Node>> =
nodeRepository
.getNodes()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
nodeRepository.getNodes().stateInWhileSubscribed(initialValue = emptyList())
fun setNodeFilterText(text: String) {
nodeFilterText.value = text

View File

@@ -24,14 +24,12 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -47,6 +45,7 @@ import org.meshtastic.core.model.util.positionToMeter
import org.meshtastic.core.prefs.ui.UiPrefs
import org.meshtastic.core.service.IMeshService
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.LocalOnlyProtos.LocalConfig
import org.meshtastic.proto.MeshProtos
import org.meshtastic.proto.Portnums
@@ -81,16 +80,10 @@ constructor(
val ourNodeInfo: StateFlow<Node?> = nodeRepository.ourNodeInfo
val isConnected =
serviceRepository.connectionState
.map { it.isConnected() }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000L), false)
serviceRepository.connectionState.map { it.isConnected() }.stateInWhileSubscribed(initialValue = false)
val localConfig: StateFlow<LocalConfig> =
radioConfigRepository.localConfigFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000L),
LocalConfig.getDefaultInstance(),
)
radioConfigRepository.localConfigFlow.stateInWhileSubscribed(initialValue = LocalConfig.getDefaultInstance())
val meshService: IMeshService?
get() = serviceRepository.meshService
@@ -105,7 +98,7 @@ constructor(
uiPrefs.shouldProvideNodeLocation(myNodeEntity.myNodeNum)
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), false)
.stateInWhileSubscribed(initialValue = false)
private val _excludedModulesUnlocked = MutableStateFlow(false)
val excludedModulesUnlocked: StateFlow<Boolean> = _excludedModulesUnlocked.asStateFlow()

View File

@@ -27,16 +27,15 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.meshtastic.core.data.repository.MeshLogRepository
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.database.entity.MeshLog
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.AdminProtos
import org.meshtastic.proto.MeshProtos
import org.meshtastic.proto.PaxcountProtos
@@ -205,10 +204,7 @@ constructor(
) : ViewModel() {
val meshLog: StateFlow<ImmutableList<UiMeshLog>> =
meshLogRepository
.getAllLogs()
.map(::toUiState)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), persistentListOf())
meshLogRepository.getAllLogs().map(::toUiState).stateInWhileSubscribed(initialValue = persistentListOf())
// --- Managers ---
val searchManager = LogSearchManager()