mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor(feature/settings): Extract Settings and RadioConfig ViewModels to commonMain
This commit is contained in:
parent
3c872b2d01
commit
091452a559
9 changed files with 72 additions and 296 deletions
|
|
@ -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<NavKey>): AndroidRadioConfigViewModel {
|
||||
val viewModel = koinViewModel<AndroidRadioConfigViewModel>()
|
||||
internal fun getRadioConfigViewModel(backStack: NavBackStack<NavKey>): RadioConfigViewModel {
|
||||
val viewModel = koinViewModel<RadioConfigViewModel>()
|
||||
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<NavKey>): AndroidRa
|
|||
fun EntryProviderScope<NavKey>.settingsGraph(backStack: NavBackStack<NavKey>) {
|
||||
entry<SettingsRoutes.SettingsGraph> {
|
||||
SettingsScreen(
|
||||
settingsViewModel = koinViewModel<AndroidSettingsViewModel>(),
|
||||
settingsViewModel = koinViewModel<SettingsViewModel>(),
|
||||
viewModel = getRadioConfigViewModel(backStack),
|
||||
onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) },
|
||||
) {
|
||||
|
|
@ -101,7 +101,7 @@ fun EntryProviderScope<NavKey>.settingsGraph(backStack: NavBackStack<NavKey>) {
|
|||
|
||||
entry<SettingsRoutes.Settings> {
|
||||
SettingsScreen(
|
||||
settingsViewModel = koinViewModel<AndroidSettingsViewModel>(),
|
||||
settingsViewModel = koinViewModel<SettingsViewModel>(),
|
||||
viewModel = getRadioConfigViewModel(backStack),
|
||||
onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) },
|
||||
) {
|
||||
|
|
@ -118,7 +118,7 @@ fun EntryProviderScope<NavKey>.settingsGraph(backStack: NavBackStack<NavKey>) {
|
|||
}
|
||||
|
||||
entry<SettingsRoutes.ModuleConfiguration> {
|
||||
val settingsViewModel: AndroidSettingsViewModel = koinViewModel()
|
||||
val settingsViewModel: SettingsViewModel = koinViewModel()
|
||||
val excludedModulesUnlocked by settingsViewModel.excludedModulesUnlocked.collectAsStateWithLifecycle()
|
||||
ModuleConfigurationScreen(
|
||||
viewModel = getRadioConfigViewModel(backStack),
|
||||
|
|
@ -209,14 +209,14 @@ fun EntryProviderScope<NavKey>.settingsGraph(backStack: NavBackStack<NavKey>) {
|
|||
fun <R : Route> EntryProviderScope<NavKey>.configComposable(
|
||||
route: KClass<R>,
|
||||
backStack: NavBackStack<NavKey>,
|
||||
content: @Composable (AndroidRadioConfigViewModel) -> Unit,
|
||||
content: @Composable (RadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
addEntryProvider(route) { content(getRadioConfigViewModel(backStack)) }
|
||||
}
|
||||
|
||||
inline fun <reified R : Route> EntryProviderScope<NavKey>.configComposable(
|
||||
backStack: NavBackStack<NavKey>,
|
||||
noinline content: @Composable (AndroidRadioConfigViewModel) -> Unit,
|
||||
noinline content: @Composable (RadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
entry<R> { content(getRadioConfigViewModel(backStack)) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<MyNodeInfo?> = 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ class SettingsViewModelTest {
|
|||
meshLocationUseCase = mockk(relaxed = true),
|
||||
exportDataUseCase = mockk(relaxed = true),
|
||||
isOtaCapableUseCase = mockk(relaxed = true),
|
||||
fileService = mockk(relaxed = true),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue