From f2d29d45829f8da5674cbaff0240aa12b66752c8 Mon Sep 17 00:00:00 2001 From: Phil Oliver <3497406+poliver@users.noreply.github.com> Date: Thu, 18 Sep 2025 12:08:10 -0400 Subject: [PATCH] Flatten `BluetoothViewModel` (#3138) --- .../java/com/geeksville/mesh/MainActivity.kt | 4 +-- .../mesh/model/BluetoothViewModel.kt | 33 ------------------- .../mesh/navigation/ConnectionsNavigation.kt | 4 +-- .../main/java/com/geeksville/mesh/ui/Main.kt | 9 ++--- .../mesh/ui/connections/ConnectionsScreen.kt | 6 ++-- .../ui/connections/ConnectionsViewModel.kt | 17 +++++----- 6 files changed, 15 insertions(+), 58 deletions(-) delete mode 100644 app/src/main/java/com/geeksville/mesh/model/BluetoothViewModel.kt diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index d74a173a5..8d0e07f16 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -42,7 +42,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.geeksville.mesh.android.GeeksvilleApplication import com.geeksville.mesh.android.Logging import com.geeksville.mesh.android.prefs.UiPrefs -import com.geeksville.mesh.model.BluetoothViewModel import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.navigation.DEEP_LINK_BASE_URI import com.geeksville.mesh.ui.MainScreen @@ -58,7 +57,6 @@ import javax.inject.Inject class MainActivity : AppCompatActivity(), Logging { - private val bluetoothViewModel: BluetoothViewModel by viewModels() private val model: UIViewModel by viewModels() // This is aware of the Activity lifecycle and handles binding to the mesh service. @@ -108,7 +106,7 @@ class MainActivity : val appIntroCompleted by model.appIntroCompleted.collectAsStateWithLifecycle() if (appIntroCompleted) { - MainScreen(uIViewModel = model, bluetoothViewModel = bluetoothViewModel) + MainScreen(uIViewModel = model) } else { AppIntroductionScreen( onDone = { diff --git a/app/src/main/java/com/geeksville/mesh/model/BluetoothViewModel.kt b/app/src/main/java/com/geeksville/mesh/model/BluetoothViewModel.kt deleted file mode 100644 index fc28f8e5c..000000000 --- a/app/src/main/java/com/geeksville/mesh/model/BluetoothViewModel.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 . - */ - -package com.geeksville.mesh.model - -import androidx.lifecycle.ViewModel -import com.geeksville.mesh.repository.bluetooth.BluetoothRepository -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.map -import javax.inject.Inject - -/** Thin view model which adapts the view layer to the `BluetoothRepository`. */ -@HiltViewModel -class BluetoothViewModel @Inject constructor(private val bluetoothRepository: BluetoothRepository) : ViewModel() { - /** Called when permissions have been updated. This causes an explicit refresh of the bluetooth state. */ - fun permissionsUpdated() = bluetoothRepository.refreshState() - - val enabled = bluetoothRepository.state.map { it.enabled } -} diff --git a/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsNavigation.kt b/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsNavigation.kt index d11172362..8a16a7d92 100644 --- a/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsNavigation.kt +++ b/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsNavigation.kt @@ -24,12 +24,11 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.composable import androidx.navigation.navDeepLink import androidx.navigation.navigation -import com.geeksville.mesh.model.BluetoothViewModel import com.geeksville.mesh.ui.connections.ConnectionsScreen import com.geeksville.mesh.ui.settings.radio.components.LoRaConfigScreen /** Navigation graph for for the top level ConnectionsScreen - [ConnectionsRoutes.Connections]. */ -fun NavGraphBuilder.connectionsGraph(navController: NavHostController, bluetoothViewModel: BluetoothViewModel) { +fun NavGraphBuilder.connectionsGraph(navController: NavHostController) { @Suppress("ktlint:standard:max-line-length") navigation(startDestination = ConnectionsRoutes.Connections) { composable( @@ -40,7 +39,6 @@ fun NavGraphBuilder.connectionsGraph(navController: NavHostController, bluetooth val parentEntry = remember(backStackEntry) { navController.getBackStackEntry(ConnectionsRoutes.ConnectionsGraph) } ConnectionsScreen( - bluetoothViewModel = bluetoothViewModel, radioConfigViewModel = hiltViewModel(parentEntry), onClickNodeChip = { navController.navigate(NodesRoutes.NodeDetailGraph(it)) { diff --git a/app/src/main/java/com/geeksville/mesh/ui/Main.kt b/app/src/main/java/com/geeksville/mesh/ui/Main.kt index 1e69265c4..2d310142a 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt @@ -81,7 +81,6 @@ import com.geeksville.mesh.android.AddNavigationTracking import com.geeksville.mesh.android.BuildUtils.debug import com.geeksville.mesh.android.setAttributes import com.geeksville.mesh.model.BTScanModel -import com.geeksville.mesh.model.BluetoothViewModel import com.geeksville.mesh.model.DeviceVersion import com.geeksville.mesh.model.Node import com.geeksville.mesh.model.UIViewModel @@ -148,11 +147,7 @@ enum class TopLevelDestination(@StringRes val label: Int, val icon: ImageVector, @OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class) @Suppress("LongMethod", "CyclomaticComplexMethod") @Composable -fun MainScreen( - uIViewModel: UIViewModel = hiltViewModel(), - bluetoothViewModel: BluetoothViewModel = hiltViewModel(), - scanModel: BTScanModel = hiltViewModel(), -) { +fun MainScreen(uIViewModel: UIViewModel = hiltViewModel(), scanModel: BTScanModel = hiltViewModel()) { val navController = rememberNavController() val connectionState by uIViewModel.connectionState.collectAsStateWithLifecycle() val requestChannelSet by uIViewModel.requestChannelSet.collectAsStateWithLifecycle() @@ -396,7 +391,7 @@ fun MainScreen( nodesGraph(navController, uiViewModel = uIViewModel) mapGraph(navController, uiViewModel = uIViewModel) channelsGraph(navController, uiViewModel = uIViewModel) - connectionsGraph(navController, bluetoothViewModel) + connectionsGraph(navController) settingsGraph(navController) } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt index 70d4855ad..23022fa13 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt @@ -62,7 +62,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.geeksville.mesh.ConfigProtos import com.geeksville.mesh.R import com.geeksville.mesh.model.BTScanModel -import com.geeksville.mesh.model.BluetoothViewModel import com.geeksville.mesh.model.DeviceListEntry import com.geeksville.mesh.model.Node import com.geeksville.mesh.navigation.ConfigRoute @@ -102,7 +101,6 @@ fun String?.isIPAddress(): Boolean = if (Build.VERSION.SDK_INT < Build.VERSION_C fun ConnectionsScreen( connectionsViewModel: ConnectionsViewModel = hiltViewModel(), scanModel: BTScanModel = hiltViewModel(), - bluetoothViewModel: BluetoothViewModel = hiltViewModel(), radioConfigViewModel: RadioConfigViewModel = hiltViewModel(), onClickNodeChip: (Int) -> Unit, onNavigateToSettings: () -> Unit, @@ -120,7 +118,7 @@ fun ConnectionsScreen( val info by connectionsViewModel.myNodeInfo.collectAsStateWithLifecycle() val ourNode by connectionsViewModel.ourNodeInfo.collectAsStateWithLifecycle() val selectedDevice by scanModel.selectedNotNullFlow.collectAsStateWithLifecycle() - val bluetoothEnabled by bluetoothViewModel.enabled.collectAsStateWithLifecycle(false) + val bluetoothState by connectionsViewModel.bluetoothState.collectAsStateWithLifecycle() val regionUnset = config.lora.region == ConfigProtos.Config.LoRaConfig.RegionCode.UNSET val bleDevices by scanModel.bleDevicesForUi.collectAsStateWithLifecycle() @@ -264,7 +262,7 @@ fun ConnectionsScreen( btDevices = bleDevices, selectedDevice = selectedDevice, scanModel = scanModel, - bluetoothEnabled = bluetoothEnabled, + bluetoothEnabled = bluetoothState.enabled, ) } diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsViewModel.kt index 3c2a27244..491aee075 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsViewModel.kt @@ -24,6 +24,7 @@ import com.geeksville.mesh.android.prefs.UiPrefs import com.geeksville.mesh.database.NodeRepository import com.geeksville.mesh.database.entity.MyNodeEntity import com.geeksville.mesh.model.Node +import com.geeksville.mesh.repository.bluetooth.BluetoothRepository import com.geeksville.mesh.repository.datastore.RadioConfigRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -37,8 +38,9 @@ import javax.inject.Inject class ConnectionsViewModel @Inject constructor( - private val radioConfigRepository: RadioConfigRepository, - private val nodeRepository: NodeRepository, + radioConfigRepository: RadioConfigRepository, + nodeRepository: NodeRepository, + bluetoothRepository: BluetoothRepository, private val uiPrefs: UiPrefs, ) : ViewModel() { val localConfig: StateFlow = @@ -48,14 +50,13 @@ constructor( LocalConfig.getDefaultInstance(), ) - val connectionState - get() = radioConfigRepository.connectionState + val connectionState = radioConfigRepository.connectionState - val myNodeInfo: StateFlow - get() = nodeRepository.myNodeInfo + val myNodeInfo: StateFlow = nodeRepository.myNodeInfo - val ourNodeInfo: StateFlow - get() = nodeRepository.ourNodeInfo + val ourNodeInfo: StateFlow = nodeRepository.ourNodeInfo + + val bluetoothState = bluetoothRepository.state private val _hasShownNotPairedWarning = MutableStateFlow(uiPrefs.hasShownNotPairedWarning) val hasShownNotPairedWarning: StateFlow = _hasShownNotPairedWarning.asStateFlow()