feat/decoupling (#4685)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-03 07:15:28 -06:00 committed by GitHub
parent 40244f8337
commit 2c49db8041
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
254 changed files with 5132 additions and 2666 deletions

View file

@ -40,7 +40,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.Node
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.administration
import org.meshtastic.core.resources.preserve_favorites

View file

@ -32,11 +32,6 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.meshtastic.core.common.BuildConfigProvider
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.data.repository.RadioConfigRepository
import org.meshtastic.core.database.DatabaseManager
import org.meshtastic.core.database.entity.MyNodeEntity
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.domain.usecase.settings.ExportDataUseCase
import org.meshtastic.core.domain.usecase.settings.IsOtaCapableUseCase
import org.meshtastic.core.domain.usecase.settings.MeshLocationUseCase
@ -45,9 +40,14 @@ import org.meshtastic.core.domain.usecase.settings.SetDatabaseCacheLimitUseCase
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.MyNodeInfo
import org.meshtastic.core.model.Node
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.prefs.meshlog.MeshLogPrefs
import org.meshtastic.core.prefs.ui.UiPrefs
import org.meshtastic.core.repository.DatabaseManager
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.RadioConfigRepository
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.LocalConfig
import java.io.BufferedWriter
@ -77,7 +77,7 @@ constructor(
private val exportDataUseCase: ExportDataUseCase,
private val isOtaCapableUseCase: IsOtaCapableUseCase,
) : ViewModel() {
val myNodeInfo: StateFlow<MyNodeEntity?> = nodeRepository.myNodeInfo
val myNodeInfo: StateFlow<MyNodeInfo?> = nodeRepository.myNodeInfo
val myNodeNum
get() = myNodeInfo.value?.myNodeNum
@ -170,7 +170,7 @@ constructor(
*/
@Suppress("detekt:CyclomaticComplexMethod", "detekt:LongMethod")
fun saveDataCsv(uri: Uri, filterPortnum: Int? = null) {
viewModelScope.launch(Dispatchers.Main) {
viewModelScope.launch {
val myNodeNum = myNodeNum ?: return@launch
writeToUri(uri) { writer -> exportDataUseCase(writer, myNodeNum, filterPortnum) }
}

View file

@ -37,13 +37,13 @@ import org.meshtastic.core.common.util.nowInstant
import org.meshtastic.core.common.util.toDate
import org.meshtastic.core.common.util.toInstant
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.database.entity.Packet
import org.meshtastic.core.model.getTracerouteResponse
import org.meshtastic.core.model.util.decodeOrNull
import org.meshtastic.core.model.util.toReadableString
import org.meshtastic.core.prefs.meshlog.MeshLogPrefs
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.debug_clear
import org.meshtastic.core.resources.debug_clear_logs_confirm

View file

@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.meshtastic.core.prefs.filter.FilterPrefs
import org.meshtastic.core.service.filter.MessageFilterService
import org.meshtastic.core.repository.MessageFilter
import javax.inject.Inject
@HiltViewModel
@ -30,7 +30,7 @@ class FilterSettingsViewModel
@Inject
constructor(
private val filterPrefs: FilterPrefs,
private val messageFilterService: MessageFilterService,
private val messageFilter: MessageFilter,
) : ViewModel() {
private val _filterEnabled = MutableStateFlow(filterPrefs.filterEnabled)
@ -51,7 +51,7 @@ constructor(
if (current.add(trimmed)) {
filterPrefs.filterWords = current
_filterWords.value = current.toList().sorted()
messageFilterService.rebuildPatterns()
messageFilter.rebuildPatterns()
}
}
@ -60,7 +60,7 @@ constructor(
if (current.remove(word)) {
filterPrefs.filterWords = current
_filterWords.value = current.toList().sorted()
messageFilterService.rebuildPatterns()
messageFilter.rebuildPatterns()
}
}
}

View file

@ -40,7 +40,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.Node
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.clean_node_database_description
import org.meshtastic.core.resources.clean_node_database_title

View file

@ -24,8 +24,8 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.getString
import org.meshtastic.core.common.util.nowSeconds
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.domain.usecase.settings.CleanNodeDatabaseUseCase
import org.meshtastic.core.model.Node
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.are_you_sure
import org.meshtastic.core.resources.clean_node_database_confirmation

View file

@ -43,11 +43,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.compose.resources.StringResource
import org.meshtastic.core.data.repository.LocationRepository
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.data.repository.PacketRepository
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.domain.usecase.settings.AdminActionsUseCase
import org.meshtastic.core.domain.usecase.settings.ExportProfileUseCase
import org.meshtastic.core.domain.usecase.settings.ExportSecurityConfigUseCase
@ -59,15 +54,20 @@ import org.meshtastic.core.domain.usecase.settings.RadioResponseResult
import org.meshtastic.core.domain.usecase.settings.ToggleAnalyticsUseCase
import org.meshtastic.core.domain.usecase.settings.ToggleHomoglyphEncodingUseCase
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.MyNodeInfo
import org.meshtastic.core.model.Node
import org.meshtastic.core.model.Position
import org.meshtastic.core.navigation.SettingsRoutes
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
import org.meshtastic.core.prefs.homoglyph.HomoglyphPrefs
import org.meshtastic.core.prefs.map.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.core.resources.Res
import org.meshtastic.core.resources.UiText
import org.meshtastic.core.resources.cant_shutdown
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.ui.util.getChannelList
import org.meshtastic.feature.settings.navigation.ConfigRoute
import org.meshtastic.feature.settings.navigation.ModuleRoute
@ -217,7 +217,7 @@ constructor(
Logger.d { "RadioConfigViewModel created" }
}
private val myNodeInfo: StateFlow<MyNodeEntity?>
private val myNodeInfo: StateFlow<MyNodeInfo?>
get() = nodeRepository.myNodeInfo
val myNodeNum

View file

@ -33,7 +33,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.Node
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.cancel
import org.meshtastic.core.resources.send

View file

@ -24,8 +24,8 @@ import androidx.compose.ui.graphics.Color
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.database.model.getColorFrom
import org.meshtastic.core.database.model.getStringResFrom
import org.meshtastic.core.model.getColorFrom
import org.meshtastic.core.model.getStringResFrom
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.tak
import org.meshtastic.core.resources.tak_config

View file

@ -29,8 +29,8 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.database.model.isUnmessageableRole
import org.meshtastic.core.model.Capabilities
import org.meshtastic.core.model.isUnmessageableRole
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.hardware_model
import org.meshtastic.core.resources.licensed_amateur_radio

View file

@ -30,9 +30,6 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.meshtastic.core.common.BuildConfigProvider
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.data.repository.RadioConfigRepository
import org.meshtastic.core.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
@ -44,8 +41,13 @@ import org.meshtastic.core.domain.usecase.settings.SetThemeUseCase
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.prefs.meshlog.MeshLogPrefs
import org.meshtastic.core.prefs.ui.UiPrefs
import org.meshtastic.core.repository.DatabaseManager
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.RadioConfigRepository
import org.robolectric.annotation.Config
@OptIn(ExperimentalCoroutinesApi::class)
@Config(sdk = [34])
class SettingsViewModelTest {
private val testDispatcher = StandardTestDispatcher()
@ -58,14 +60,14 @@ class SettingsViewModelTest {
private val databaseManager: DatabaseManager = mockk(relaxed = true)
private val meshLogPrefs: MeshLogPrefs = mockk(relaxed = true)
private val setThemeUseCase: SetThemeUseCase = mockk(relaxed = true)
private val setAppIntroCompletedUseCase: SetAppIntroCompletedUseCase = mockk(relaxed = true)
private val setProvideLocationUseCase: SetProvideLocationUseCase = mockk(relaxed = true)
private val setDatabaseCacheLimitUseCase: SetDatabaseCacheLimitUseCase = mockk(relaxed = true)
private val setMeshLogSettingsUseCase: SetMeshLogSettingsUseCase = mockk(relaxed = true)
private val meshLocationUseCase: MeshLocationUseCase = mockk(relaxed = true)
private val exportDataUseCase: ExportDataUseCase = mockk(relaxed = true)
private val isOtaCapableUseCase: IsOtaCapableUseCase = mockk(relaxed = true)
private lateinit var setThemeUseCase: SetThemeUseCase
private lateinit var setAppIntroCompletedUseCase: SetAppIntroCompletedUseCase
private lateinit var setProvideLocationUseCase: SetProvideLocationUseCase
private lateinit var setDatabaseCacheLimitUseCase: SetDatabaseCacheLimitUseCase
private lateinit var setMeshLogSettingsUseCase: SetMeshLogSettingsUseCase
private lateinit var meshLocationUseCase: MeshLocationUseCase
private lateinit var exportDataUseCase: ExportDataUseCase
private lateinit var isOtaCapableUseCase: IsOtaCapableUseCase
private lateinit var viewModel: SettingsViewModel
@ -73,6 +75,15 @@ class SettingsViewModelTest {
fun setUp() {
Dispatchers.setMain(testDispatcher)
setThemeUseCase = mockk(relaxed = true)
setAppIntroCompletedUseCase = mockk(relaxed = true)
setProvideLocationUseCase = mockk(relaxed = true)
setDatabaseCacheLimitUseCase = mockk(relaxed = true)
setMeshLogSettingsUseCase = mockk(relaxed = true)
meshLocationUseCase = mockk(relaxed = true)
exportDataUseCase = mockk(relaxed = true)
isOtaCapableUseCase = mockk(relaxed = true)
// Return real StateFlows to avoid ClassCastException
every { databaseManager.cacheLimit } returns MutableStateFlow(100)
every { nodeRepository.myNodeInfo } returns MutableStateFlow(null)

View file

@ -33,8 +33,8 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.meshtastic.core.data.repository.MeshLogRepository
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.prefs.meshlog.MeshLogPrefs
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.ui.util.AlertManager
@OptIn(ExperimentalCoroutinesApi::class)

View file

@ -23,12 +23,12 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.meshtastic.core.prefs.filter.FilterPrefs
import org.meshtastic.core.service.filter.MessageFilterService
import org.meshtastic.core.repository.MessageFilter
class FilterSettingsViewModelTest {
private val filterPrefs: FilterPrefs = mockk(relaxed = true)
private val messageFilterService: MessageFilterService = mockk(relaxed = true)
private val messageFilter: MessageFilter = mockk(relaxed = true)
private lateinit var viewModel: FilterSettingsViewModel
@ -37,7 +37,7 @@ class FilterSettingsViewModelTest {
every { filterPrefs.filterEnabled } returns true
every { filterPrefs.filterWords } returns setOf("apple", "banana")
viewModel = FilterSettingsViewModel(filterPrefs = filterPrefs, messageFilterService = messageFilterService)
viewModel = FilterSettingsViewModel(filterPrefs = filterPrefs, messageFilter = messageFilter)
}
@Test
@ -52,7 +52,7 @@ class FilterSettingsViewModelTest {
viewModel.addFilterWord("cherry")
verify { filterPrefs.filterWords = any() }
verify { messageFilterService.rebuildPatterns() }
verify { messageFilter.rebuildPatterns() }
assertEquals(listOf("apple", "banana", "cherry"), viewModel.filterWords.value)
}
@ -61,7 +61,7 @@ class FilterSettingsViewModelTest {
viewModel.removeFilterWord("apple")
verify { filterPrefs.filterWords = any() }
verify { messageFilterService.rebuildPatterns() }
verify { messageFilter.rebuildPatterns() }
assertEquals(listOf("banana"), viewModel.filterWords.value)
}
}

View file

@ -30,8 +30,8 @@ import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.domain.usecase.settings.CleanNodeDatabaseUseCase
import org.meshtastic.core.model.Node
import org.meshtastic.core.ui.util.AlertManager
@OptIn(ExperimentalCoroutinesApi::class)

View file

@ -34,10 +34,6 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.meshtastic.core.data.repository.LocationRepository
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.data.repository.PacketRepository
import org.meshtastic.core.data.repository.RadioConfigRepository
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.domain.usecase.settings.AdminActionsUseCase
import org.meshtastic.core.domain.usecase.settings.ExportProfileUseCase
import org.meshtastic.core.domain.usecase.settings.ExportSecurityConfigUseCase
@ -48,10 +44,14 @@ import org.meshtastic.core.domain.usecase.settings.RadioConfigUseCase
import org.meshtastic.core.domain.usecase.settings.RadioResponseResult
import org.meshtastic.core.domain.usecase.settings.ToggleAnalyticsUseCase
import org.meshtastic.core.domain.usecase.settings.ToggleHomoglyphEncodingUseCase
import org.meshtastic.core.model.Node
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
import org.meshtastic.core.prefs.homoglyph.HomoglyphPrefs
import org.meshtastic.core.prefs.map.MapConsentPrefs
import org.meshtastic.core.service.ServiceRepository
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.proto.ChannelSet
import org.meshtastic.proto.ChannelSettings
import org.meshtastic.proto.Config