diff --git a/app/src/main/kotlin/org/meshtastic/app/navigation/SettingsNavigation.kt b/app/src/main/kotlin/org/meshtastic/app/navigation/SettingsNavigation.kt index 80f1cb43c..4d69c03fa 100644 --- a/app/src/main/kotlin/org/meshtastic/app/navigation/SettingsNavigation.kt +++ b/app/src/main/kotlin/org/meshtastic/app/navigation/SettingsNavigation.kt @@ -27,8 +27,8 @@ import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey import org.koin.compose.viewmodel.koinViewModel import org.meshtastic.app.settings.AndroidDebugViewModel -import org.meshtastic.app.settings.AndroidRadioConfigViewModel -import org.meshtastic.app.settings.AndroidSettingsViewModel +import org.meshtastic.feature.settings.radio.RadioConfigViewModel +import org.meshtastic.feature.settings.SettingsViewModel import org.meshtastic.core.navigation.NodesRoutes import org.meshtastic.core.navigation.Route import org.meshtastic.core.navigation.SettingsRoutes @@ -74,8 +74,8 @@ import kotlin.reflect.KClass @PublishedApi @Composable -internal fun getRadioConfigViewModel(backStack: NavBackStack): AndroidRadioConfigViewModel { - val viewModel = koinViewModel() +internal fun getRadioConfigViewModel(backStack: NavBackStack): RadioConfigViewModel { + val viewModel = koinViewModel() LaunchedEffect(backStack) { val destNum = backStack.lastOrNull { it is SettingsRoutes.Settings }?.let { (it as SettingsRoutes.Settings).destNum } @@ -91,7 +91,7 @@ internal fun getRadioConfigViewModel(backStack: NavBackStack): AndroidRa fun EntryProviderScope.settingsGraph(backStack: NavBackStack) { entry { SettingsScreen( - settingsViewModel = koinViewModel(), + settingsViewModel = koinViewModel(), viewModel = getRadioConfigViewModel(backStack), onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, ) { @@ -101,7 +101,7 @@ fun EntryProviderScope.settingsGraph(backStack: NavBackStack) { entry { SettingsScreen( - settingsViewModel = koinViewModel(), + settingsViewModel = koinViewModel(), viewModel = getRadioConfigViewModel(backStack), onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, ) { @@ -118,7 +118,7 @@ fun EntryProviderScope.settingsGraph(backStack: NavBackStack) { } entry { - val settingsViewModel: AndroidSettingsViewModel = koinViewModel() + val settingsViewModel: SettingsViewModel = koinViewModel() val excludedModulesUnlocked by settingsViewModel.excludedModulesUnlocked.collectAsStateWithLifecycle() ModuleConfigurationScreen( viewModel = getRadioConfigViewModel(backStack), @@ -209,14 +209,14 @@ fun EntryProviderScope.settingsGraph(backStack: NavBackStack) { fun EntryProviderScope.configComposable( route: KClass, backStack: NavBackStack, - content: @Composable (AndroidRadioConfigViewModel) -> Unit, + content: @Composable (RadioConfigViewModel) -> Unit, ) { addEntryProvider(route) { content(getRadioConfigViewModel(backStack)) } } inline fun EntryProviderScope.configComposable( backStack: NavBackStack, - noinline content: @Composable (AndroidRadioConfigViewModel) -> Unit, + noinline content: @Composable (RadioConfigViewModel) -> Unit, ) { entry { content(getRadioConfigViewModel(backStack)) } } diff --git a/app/src/main/kotlin/org/meshtastic/app/settings/AndroidRadioConfigViewModel.kt b/app/src/main/kotlin/org/meshtastic/app/settings/AndroidRadioConfigViewModel.kt deleted file mode 100644 index ab57c13b8..000000000 --- a/app/src/main/kotlin/org/meshtastic/app/settings/AndroidRadioConfigViewModel.kt +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2025-2026 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 org.meshtastic.app.settings - -import android.Manifest -import android.app.Application -import android.content.pm.PackageManager -import android.location.Location -import android.net.Uri -import androidx.annotation.RequiresPermission -import androidx.core.content.ContextCompat -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.viewModelScope -import co.touchlab.kermit.Logger -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import okio.buffer -import okio.sink -import okio.source -import org.koin.core.annotation.KoinViewModel -import org.meshtastic.core.domain.usecase.settings.AdminActionsUseCase -import org.meshtastic.core.domain.usecase.settings.ExportProfileUseCase -import org.meshtastic.core.domain.usecase.settings.ExportSecurityConfigUseCase -import org.meshtastic.core.domain.usecase.settings.ImportProfileUseCase -import org.meshtastic.core.domain.usecase.settings.InstallProfileUseCase -import org.meshtastic.core.domain.usecase.settings.ProcessRadioResponseUseCase -import org.meshtastic.core.domain.usecase.settings.RadioConfigUseCase -import org.meshtastic.core.domain.usecase.settings.ToggleAnalyticsUseCase -import org.meshtastic.core.domain.usecase.settings.ToggleHomoglyphEncodingUseCase -import org.meshtastic.core.repository.AnalyticsPrefs -import org.meshtastic.core.repository.HomoglyphPrefs -import org.meshtastic.core.repository.LocationRepository -import org.meshtastic.core.repository.MapConsentPrefs -import org.meshtastic.core.repository.NodeRepository -import org.meshtastic.core.repository.PacketRepository -import org.meshtastic.core.repository.RadioConfigRepository -import org.meshtastic.core.repository.ServiceRepository -import org.meshtastic.feature.settings.radio.RadioConfigViewModel -import org.meshtastic.proto.Config -import org.meshtastic.proto.DeviceProfile -import java.io.FileOutputStream - -@KoinViewModel -class AndroidRadioConfigViewModel( - savedStateHandle: SavedStateHandle, - private val app: Application, - radioConfigRepository: RadioConfigRepository, - packetRepository: PacketRepository, - serviceRepository: ServiceRepository, - nodeRepository: NodeRepository, - private val locationRepository: LocationRepository, - mapConsentPrefs: MapConsentPrefs, - analyticsPrefs: AnalyticsPrefs, - homoglyphEncodingPrefs: HomoglyphPrefs, - toggleAnalyticsUseCase: ToggleAnalyticsUseCase, - toggleHomoglyphEncodingUseCase: ToggleHomoglyphEncodingUseCase, - importProfileUseCase: ImportProfileUseCase, - exportProfileUseCase: ExportProfileUseCase, - exportSecurityConfigUseCase: ExportSecurityConfigUseCase, - installProfileUseCase: InstallProfileUseCase, - radioConfigUseCase: RadioConfigUseCase, - adminActionsUseCase: AdminActionsUseCase, - processRadioResponseUseCase: ProcessRadioResponseUseCase, -) : RadioConfigViewModel( - savedStateHandle, - radioConfigRepository, - packetRepository, - serviceRepository, - nodeRepository, - locationRepository, - mapConsentPrefs, - analyticsPrefs, - homoglyphEncodingPrefs, - toggleAnalyticsUseCase, - toggleHomoglyphEncodingUseCase, - importProfileUseCase, - exportProfileUseCase, - exportSecurityConfigUseCase, - installProfileUseCase, - radioConfigUseCase, - adminActionsUseCase, - processRadioResponseUseCase, -) { - @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION) - override suspend fun getCurrentLocation(): Location? = if ( - ContextCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) == - PackageManager.PERMISSION_GRANTED - ) { - locationRepository.getLocations().firstOrNull() - } else { - null - } - - override fun importProfile(uri: Any, onResult: (DeviceProfile) -> Unit) { - if (uri is Uri) { - viewModelScope.launch(Dispatchers.IO) { - try { - app.contentResolver.openInputStream(uri)?.source()?.buffer()?.use { inputStream -> - importProfileUseCase(inputStream).onSuccess(onResult).onFailure { throw it } - } - } catch (ex: Exception) { - Logger.e { "Import DeviceProfile error: ${ex.message}" } - // Error handling simplified for this example - } - } - } - } - - override fun exportProfile(uri: Any, profile: DeviceProfile) { - if (uri is Uri) { - viewModelScope.launch { - withContext(Dispatchers.IO) { - try { - app.contentResolver.openFileDescriptor(uri, "wt")?.use { parcelFileDescriptor -> - FileOutputStream(parcelFileDescriptor.fileDescriptor).sink().buffer().use { outputStream -> - exportProfileUseCase(outputStream, profile) - .onSuccess { /* Success */ } - .onFailure { throw it } - } - } - } catch (ex: Exception) { - Logger.e { "Can't write file error: ${ex.message}" } - } - } - } - } - } - - override fun exportSecurityConfig(uri: Any, securityConfig: Config.SecurityConfig) { - if (uri is Uri) { - viewModelScope.launch { - withContext(Dispatchers.IO) { - try { - app.contentResolver.openFileDescriptor(uri, "wt")?.use { parcelFileDescriptor -> - FileOutputStream(parcelFileDescriptor.fileDescriptor).sink().buffer().use { outputStream -> - exportSecurityConfigUseCase(outputStream, securityConfig) - .onSuccess { /* Success */ } - .onFailure { throw it } - } - } - } catch (ex: Exception) { - Logger.e { "Can't write security keys JSON error: ${ex.message}" } - } - } - } - } - } -} diff --git a/app/src/main/kotlin/org/meshtastic/app/settings/AndroidSettingsViewModel.kt b/app/src/main/kotlin/org/meshtastic/app/settings/AndroidSettingsViewModel.kt deleted file mode 100644 index 61f9c2c29..000000000 --- a/app/src/main/kotlin/org/meshtastic/app/settings/AndroidSettingsViewModel.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2025-2026 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 org.meshtastic.app.settings - -import android.app.Application -import android.net.Uri -import androidx.lifecycle.viewModelScope -import co.touchlab.kermit.Logger -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import okio.BufferedSink -import okio.buffer -import okio.sink -import org.koin.core.annotation.KoinViewModel -import org.meshtastic.core.common.BuildConfigProvider -import org.meshtastic.core.common.database.DatabaseManager -import org.meshtastic.core.domain.usecase.settings.ExportDataUseCase -import org.meshtastic.core.domain.usecase.settings.IsOtaCapableUseCase -import org.meshtastic.core.domain.usecase.settings.MeshLocationUseCase -import org.meshtastic.core.domain.usecase.settings.SetAppIntroCompletedUseCase -import org.meshtastic.core.domain.usecase.settings.SetDatabaseCacheLimitUseCase -import org.meshtastic.core.domain.usecase.settings.SetLocaleUseCase -import org.meshtastic.core.domain.usecase.settings.SetMeshLogSettingsUseCase -import org.meshtastic.core.domain.usecase.settings.SetProvideLocationUseCase -import org.meshtastic.core.domain.usecase.settings.SetThemeUseCase -import org.meshtastic.core.model.RadioController -import org.meshtastic.core.repository.MeshLogPrefs -import org.meshtastic.core.repository.NodeRepository -import org.meshtastic.core.repository.RadioConfigRepository -import org.meshtastic.core.repository.UiPrefs -import org.meshtastic.feature.settings.SettingsViewModel -import java.io.FileNotFoundException -import java.io.FileOutputStream - -@KoinViewModel -@Suppress("LongParameterList") -class AndroidSettingsViewModel( - private val app: Application, - radioConfigRepository: RadioConfigRepository, - radioController: RadioController, - nodeRepository: NodeRepository, - uiPrefs: UiPrefs, - buildConfigProvider: BuildConfigProvider, - databaseManager: DatabaseManager, - meshLogPrefs: MeshLogPrefs, - setThemeUseCase: SetThemeUseCase, - setLocaleUseCase: SetLocaleUseCase, - setAppIntroCompletedUseCase: SetAppIntroCompletedUseCase, - setProvideLocationUseCase: SetProvideLocationUseCase, - setDatabaseCacheLimitUseCase: SetDatabaseCacheLimitUseCase, - setMeshLogSettingsUseCase: SetMeshLogSettingsUseCase, - meshLocationUseCase: MeshLocationUseCase, - exportDataUseCase: ExportDataUseCase, - isOtaCapableUseCase: IsOtaCapableUseCase, -) : SettingsViewModel( - radioConfigRepository, - radioController, - nodeRepository, - uiPrefs, - buildConfigProvider, - databaseManager, - meshLogPrefs, - setThemeUseCase, - setLocaleUseCase, - setAppIntroCompletedUseCase, - setProvideLocationUseCase, - setDatabaseCacheLimitUseCase, - setMeshLogSettingsUseCase, - meshLocationUseCase, - exportDataUseCase, - isOtaCapableUseCase, -) { - override fun saveDataCsv(uri: Any, filterPortnum: Int?) { - if (uri is Uri) { - viewModelScope.launch { writeToUri(uri) { writer -> performDataExport(writer, filterPortnum) } } - } - } - - private suspend inline fun writeToUri(uri: Uri, crossinline block: suspend (BufferedSink) -> Unit) { - withContext(Dispatchers.IO) { - try { - app.contentResolver.openFileDescriptor(uri, "wt")?.use { parcelFileDescriptor -> - FileOutputStream(parcelFileDescriptor.fileDescriptor).sink().buffer().use { writer -> - block.invoke(writer) - } - } - } catch (ex: FileNotFoundException) { - Logger.e { "Can't write file error: ${ex.message}" } - } - } - } -} diff --git a/feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt b/feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt index 4150417da..e3bf3afd4 100644 --- a/feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt +++ b/feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt @@ -16,6 +16,8 @@ */ package org.meshtastic.feature.settings +import org.meshtastic.core.common.util.toMeshtasticUri + import android.app.Activity import android.content.Intent import androidx.activity.compose.rememberLauncherForActivityResult @@ -97,14 +99,14 @@ fun SettingsScreen( rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) { showEditDeviceProfileDialog = true - it.data?.data?.let { uri -> viewModel.importProfile(uri) { profile -> deviceProfile = profile } } + it.data?.data?.let { uri -> viewModel.importProfile(uri.toMeshtasticUri()) { profile -> deviceProfile = profile } } } } val exportConfigLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) { - it.data?.data?.let { uri -> viewModel.exportProfile(uri, deviceProfile!!) } + it.data?.data?.let { uri -> viewModel.exportProfile(uri.toMeshtasticUri(), deviceProfile!!) } } } @@ -234,7 +236,7 @@ fun SettingsScreen( cacheLimit = settingsViewModel.dbCacheLimit.collectAsStateWithLifecycle().value, onSetCacheLimit = { settingsViewModel.setDbCacheLimit(it) }, nodeShortName = ourNode?.user?.short_name ?: "", - onExportData = { settingsViewModel.saveDataCsv(it) }, + onExportData = { settingsViewModel.saveDataCsv(it.toMeshtasticUri()) }, ) AppInfoSection( diff --git a/feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigItemList.kt b/feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigItemList.kt index 94627644f..80fe7e597 100644 --- a/feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigItemList.kt +++ b/feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigItemList.kt @@ -16,7 +16,9 @@ */ package org.meshtastic.feature.settings.radio.component +import org.meshtastic.core.common.util.toMeshtasticUri import android.app.Activity + import android.content.Intent import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -94,7 +96,7 @@ fun SecurityConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) { val exportConfigLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) { - it.data?.data?.let { uri -> viewModel.exportSecurityConfig(uri, securityConfig) } + it.data?.data?.let { uri -> viewModel.exportSecurityConfig(uri.toMeshtasticUri(), securityConfig) } } } diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt index 262959da7..5081c8cb2 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt @@ -47,11 +47,13 @@ import org.meshtastic.core.repository.NodeRepository import org.meshtastic.core.repository.RadioConfigRepository import org.meshtastic.core.repository.UiPrefs import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed +import org.meshtastic.core.repository.FileService +import org.meshtastic.core.common.util.MeshtasticUri import org.meshtastic.proto.LocalConfig @KoinViewModel @Suppress("LongParameterList", "TooManyFunctions") -open class SettingsViewModel( +class SettingsViewModel( radioConfigRepository: RadioConfigRepository, private val radioController: RadioController, private val nodeRepository: NodeRepository, @@ -68,6 +70,7 @@ open class SettingsViewModel( private val meshLocationUseCase: MeshLocationUseCase, private val exportDataUseCase: ExportDataUseCase, private val isOtaCapableUseCase: IsOtaCapableUseCase, + private val fileService: FileService, ) : ViewModel() { val myNodeInfo: StateFlow = nodeRepository.myNodeInfo @@ -161,11 +164,13 @@ open class SettingsViewModel( * @param uri The destination URI for the CSV file. * @param filterPortnum If provided, only packets with this port number will be exported. */ - open fun saveDataCsv(uri: Any, filterPortnum: Int? = null) { - // To be implemented in platform-specific subclass + fun saveDataCsv(uri: MeshtasticUri, filterPortnum: Int? = null) { + viewModelScope.launch { + fileService.write(uri) { writer -> performDataExport(writer, filterPortnum) } + } } - protected suspend fun performDataExport(writer: BufferedSink, filterPortnum: Int?) { + private suspend fun performDataExport(writer: BufferedSink, filterPortnum: Int?) { val myNodeNum = myNodeNum ?: return exportDataUseCase(writer, myNodeNum, filterPortnum) } diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt index 5d7c5951b..089b22b89 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt @@ -73,6 +73,10 @@ import org.meshtastic.proto.MeshPacket import org.meshtastic.proto.ModuleConfig import org.meshtastic.proto.User +import org.meshtastic.core.repository.FileService +import org.meshtastic.core.repository.LocationService +import org.meshtastic.core.common.util.MeshtasticUri + /** Data class that represents the current RadioConfig state. */ data class RadioConfigState( val isLocal: Boolean = false, @@ -113,6 +117,8 @@ open class RadioConfigViewModel( private val radioConfigUseCase: RadioConfigUseCase, private val adminActionsUseCase: AdminActionsUseCase, private val processRadioResponseUseCase: ProcessRadioResponseUseCase, + private val locationService: LocationService, + private val fileService: FileService, ) : ViewModel() { var analyticsAllowedFlow = analyticsPrefs.analyticsAllowed @@ -150,7 +156,7 @@ open class RadioConfigViewModel( val currentDeviceProfile get() = _currentDeviceProfile.value - open suspend fun getCurrentLocation(): Any? = null + suspend fun getCurrentLocation(): Any? = locationService.getCurrentLocation() init { combine(destNumFlow, nodeRepository.nodeDBbyNum) { id, nodes -> nodes[id] ?: nodes.values.firstOrNull() } @@ -363,16 +369,44 @@ open class RadioConfigViewModel( viewModelScope.launch { radioConfigUseCase.removeFixedPosition(destNum) } } - open fun importProfile(uri: Any, onResult: (DeviceProfile) -> Unit) { - // To be implemented in platform-specific subclass + fun importProfile(uri: MeshtasticUri, onResult: (DeviceProfile) -> Unit) { + viewModelScope.launch { + try { + fileService.read(uri) { source -> + importProfileUseCase(source).onSuccess(onResult).onFailure { throw it } + } + } catch (ex: Exception) { + Logger.e { "Import DeviceProfile error: ${ex.message}" } + } + } } - open fun exportProfile(uri: Any, profile: DeviceProfile) { - // To be implemented in platform-specific subclass + fun exportProfile(uri: MeshtasticUri, profile: DeviceProfile) { + viewModelScope.launch { + try { + fileService.write(uri) { sink -> + exportProfileUseCase(sink, profile) + .onSuccess { /* Success */ } + .onFailure { throw it } + } + } catch (ex: Exception) { + Logger.e { "Can't write file error: ${ex.message}" } + } + } } - open fun exportSecurityConfig(uri: Any, securityConfig: Config.SecurityConfig) { - // To be implemented in platform-specific subclass + fun exportSecurityConfig(uri: MeshtasticUri, securityConfig: Config.SecurityConfig) { + viewModelScope.launch { + try { + fileService.write(uri) { sink -> + exportSecurityConfigUseCase(sink, securityConfig) + .onSuccess { /* Success */ } + .onFailure { throw it } + } + } catch (ex: Exception) { + Logger.e { "Can't write security keys JSON error: ${ex.message}" } + } + } } fun installProfile(protobuf: DeviceProfile) { diff --git a/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/SettingsViewModelTest.kt b/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/SettingsViewModelTest.kt index dfa71983d..1e94d311e 100644 --- a/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/SettingsViewModelTest.kt +++ b/feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/SettingsViewModelTest.kt @@ -80,6 +80,7 @@ class SettingsViewModelTest { meshLocationUseCase = mockk(relaxed = true), exportDataUseCase = mockk(relaxed = true), isOtaCapableUseCase = mockk(relaxed = true), + fileService = mockk(relaxed = true), ) } diff --git a/feature/settings/src/test/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModelTest.kt b/feature/settings/src/test/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModelTest.kt index 676fb9a0c..7bb3ed283 100644 --- a/feature/settings/src/test/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModelTest.kt +++ b/feature/settings/src/test/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModelTest.kt @@ -83,6 +83,8 @@ class RadioConfigViewModelTest { private val radioConfigUseCase: RadioConfigUseCase = mockk(relaxed = true) private val adminActionsUseCase: AdminActionsUseCase = mockk(relaxed = true) private val processRadioResponseUseCase: ProcessRadioResponseUseCase = mockk(relaxed = true) + private val locationService: org.meshtastic.core.repository.LocationService = mockk(relaxed = true) + private val fileService: org.meshtastic.core.repository.FileService = mockk(relaxed = true) private lateinit var viewModel: RadioConfigViewModel @@ -110,7 +112,6 @@ class RadioConfigViewModelTest { private fun createViewModel() = RadioConfigViewModel( savedStateHandle = SavedStateHandle(), - app = mockk(), radioConfigRepository = radioConfigRepository, packetRepository = packetRepository, serviceRepository = serviceRepository, @@ -128,6 +129,8 @@ class RadioConfigViewModelTest { radioConfigUseCase = radioConfigUseCase, adminActionsUseCase = adminActionsUseCase, processRadioResponseUseCase = processRadioResponseUseCase, + locationService = locationService, + fileService = fileService, ) @Test