Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
baed3d6
chore(conductor): Add new track 'deep_dive_docs_20260316'
jamesarich Mar 16, 2026
105763b
conductor(checkpoint): Checkpoint end of Phase 1: Audit & Discovery
jamesarich Mar 16, 2026
7212ff1
conductor(checkpoint): Checkpoint end of Phase 2: Documentation Updates
jamesarich Mar 16, 2026
dcddc24
chore(conductor): Mark track 'deep_dive_docs_20260316' as complete
jamesarich Mar 16, 2026
1120e96
chore(conductor): Archive track 'deep_dive_docs_20260316'
jamesarich Mar 16, 2026
ec6ad7a
docs(roadmap): Add ViewModel extraction and KMP mocking tasks
jamesarich Mar 16, 2026
0f0f029
chore(conductor): Add new track 'extract_viewmodels_20260316' for Vie…
jamesarich Mar 16, 2026
81e5a4a
feat(core/common): Add MeshtasticUri abstraction
jamesarich Mar 16, 2026
30c1b59
conductor(plan): Mark task 'Implement MeshtasticUri' as complete
jamesarich Mar 16, 2026
1ffa7d2
feat(core/service): Implement Android and JVM Location and File services
jamesarich Mar 16, 2026
1c25abd
conductor(plan): Mark tasks 'Define File/Location interfaces' and 'Cr…
jamesarich Mar 16, 2026
89c6fd5
conductor(checkpoint): Checkpoint end of Phase 1
jamesarich Mar 16, 2026
3c872b2
conductor(plan): Mark phase 'Phase 1: Infrastructure & Abstractions' …
jamesarich Mar 16, 2026
091452a
refactor(feature/settings): Extract Settings and RadioConfig ViewMode…
jamesarich Mar 16, 2026
8dd1113
conductor(plan): Mark task 'Extract AndroidSettingsViewModel & Androi…
jamesarich Mar 16, 2026
52c2f6e
refactor(feature/node): Extract MetricsViewModel to commonMain
jamesarich Mar 16, 2026
e3dc5f1
conductor(plan): Mark task 'Extract AndroidMetricsViewModel' as complete
jamesarich Mar 16, 2026
e1a0387
refactor(feature/settings): Extract DebugViewModel to commonMain
jamesarich Mar 16, 2026
fdf34f5
test(features): Migrate ViewModel tests to commonTest
jamesarich Mar 16, 2026
6e54cb3
conductor(plan): Mark task 'Ensure commonTest coverage' as complete
jamesarich Mar 16, 2026
d00b384
conductor(checkpoint): Checkpoint end of Phase 2
jamesarich Mar 16, 2026
aef6188
conductor(plan): Mark phase 'Phase 2: Feature Module Extractions (Set…
jamesarich Mar 16, 2026
3ea2b2a
conductor(checkpoint): Checkpoint end of Phase 2
jamesarich Mar 16, 2026
cb7f596
conductor(plan): Mark phase 'Phase 2: Feature Module Extractions' as …
jamesarich Mar 16, 2026
c59243d
conductor(checkpoint): Checkpoint end of Phase 3
jamesarich Mar 16, 2026
3780cb0
conductor(plan): Mark phase 'Phase 3: Core UI & Cleanup' as complete
jamesarich Mar 16, 2026
8a86161
chore(conductor): Mark track 'extract_viewmodels_20260316' as complete
jamesarich Mar 16, 2026
3e958f4
chore(conductor): Archive track 'extract_viewmodels_20260316'
jamesarich Mar 16, 2026
7cb5ce0
docs: Update KMP status and roadmap reflecting ViewModel extractions
jamesarich Mar 16, 2026
b53b0f2
chore: remove completed ViewModel extraction track documentation
jamesarich Mar 16, 2026
0f73049
spotless
jamesarich Mar 16, 2026
9101069
fix(pr): Address Copilot review comments
jamesarich Mar 16, 2026
f0c3136
spotless
jamesarich Mar 16, 2026
25f3a9f
test: Move service, worker, and radio tests out of app module
jamesarich Mar 16, 2026
6531f3d
build(test): Increase maxHeapSize for Test executors to 2g
jamesarich Mar 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 0 additions & 23 deletions app/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,7 @@
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>LongMethod:TCPInterface.kt$TCPInterface$private suspend fun startConnect()</ID>
<ID>LongParameterList:AndroidMetricsViewModel.kt$AndroidMetricsViewModel$( savedStateHandle: SavedStateHandle, private val app: Application, dispatchers: CoroutineDispatchers, meshLogRepository: MeshLogRepository, serviceRepository: ServiceRepository, nodeRepository: NodeRepository, tracerouteSnapshotRepository: TracerouteSnapshotRepository, nodeRequestActions: NodeRequestActions, alertManager: AlertManager, getNodeDetailsUseCase: GetNodeDetailsUseCase, )</ID>
<ID>LongParameterList:AndroidNodeListViewModel.kt$AndroidNodeListViewModel$( savedStateHandle: SavedStateHandle, nodeRepository: NodeRepository, radioConfigRepository: RadioConfigRepository, serviceRepository: ServiceRepository, radioController: RadioController, nodeManagementActions: NodeManagementActions, getFilteredNodesUseCase: GetFilteredNodesUseCase, nodeFilterPreferences: NodeFilterPreferences, )</ID>
<ID>LongParameterList:AndroidRadioConfigViewModel.kt$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, )</ID>
<ID>LongParameterList:AndroidSettingsViewModel.kt$AndroidSettingsViewModel$( private val app: Application, radioConfigRepository: RadioConfigRepository, radioController: RadioController, nodeRepository: NodeRepository, uiPrefs: UiPrefs, buildConfigProvider: BuildConfigProvider, databaseManager: DatabaseManager, meshLogPrefs: MeshLogPrefs, setThemeUseCase: SetThemeUseCase, setAppIntroCompletedUseCase: SetAppIntroCompletedUseCase, setProvideLocationUseCase: SetProvideLocationUseCase, setDatabaseCacheLimitUseCase: SetDatabaseCacheLimitUseCase, setMeshLogSettingsUseCase: SetMeshLogSettingsUseCase, meshLocationUseCase: MeshLocationUseCase, exportDataUseCase: ExportDataUseCase, isOtaCapableUseCase: IsOtaCapableUseCase, )</ID>
<ID>MagicNumber:AndroidMetricsViewModel.kt$AndroidMetricsViewModel$1000L</ID>
<ID>MagicNumber:AndroidMetricsViewModel.kt$AndroidMetricsViewModel$1e-5</ID>
<ID>MagicNumber:AndroidMetricsViewModel.kt$AndroidMetricsViewModel$1e-7</ID>
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$21972</ID>
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$32809</ID>
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$6790</ID>
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$9114</ID>
<ID>MagicNumber:SerialConnectionImpl.kt$SerialConnectionImpl$115200</ID>
<ID>MagicNumber:SerialConnectionImpl.kt$SerialConnectionImpl$200</ID>
<ID>MagicNumber:StreamInterface.kt$StreamInterface$0xff</ID>
<ID>MagicNumber:StreamInterface.kt$StreamInterface$3</ID>
<ID>MagicNumber:StreamInterface.kt$StreamInterface$4</ID>
<ID>MagicNumber:StreamInterface.kt$StreamInterface$8</ID>
<ID>MagicNumber:TCPInterface.kt$TCPInterface$1000</ID>
<ID>SwallowedException:NsdManager.kt$ex: IllegalArgumentException</ID>
<ID>SwallowedException:TCPInterface.kt$TCPInterface$ex: SocketTimeoutException</ID>
<ID>TooGenericExceptionCaught:AndroidRadioConfigViewModel.kt$AndroidRadioConfigViewModel$ex: Exception</ID>
<ID>TooGenericExceptionCaught:NordicBleInterface.kt$NordicBleInterface$e: Exception</ID>
<ID>TooGenericExceptionCaught:TCPInterface.kt$TCPInterface$ex: Throwable</ID>
<ID>TooManyFunctions:NordicBleInterface.kt$NordicBleInterface : RadioTransport</ID>
</CurrentIssues>
</SmellBaseline>
5 changes: 3 additions & 2 deletions app/src/main/kotlin/org/meshtastic/app/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import org.meshtastic.app.intro.AnalyticsIntro
import org.meshtastic.app.map.getMapViewProvider
import org.meshtastic.app.model.UIViewModel
import org.meshtastic.app.node.component.InlineMap
import org.meshtastic.app.node.metrics.getTracerouteMapOverlayInsets
import org.meshtastic.app.ui.MainScreen
import org.meshtastic.core.barcode.rememberBarcodeScanner
import org.meshtastic.core.common.util.toMeshtasticUri
import org.meshtastic.core.model.util.dispatchMeshtasticUri
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
import org.meshtastic.core.nfc.NfcScannerEffect
Expand All @@ -70,6 +70,7 @@ import org.meshtastic.core.ui.util.LocalMapViewProvider
import org.meshtastic.core.ui.util.LocalNfcScannerProvider
import org.meshtastic.core.ui.util.LocalTracerouteMapOverlayInsetsProvider
import org.meshtastic.core.ui.util.showToast
import org.meshtastic.core.ui.viewmodel.UIViewModel
import org.meshtastic.feature.intro.AppIntroductionScreen
import org.meshtastic.feature.intro.IntroViewModel

Expand Down Expand Up @@ -206,7 +207,7 @@ class MainActivity : ComponentActivity() {
private fun handleMeshtasticUri(uri: Uri) {
Logger.d { "Handling Meshtastic URI: $uri" }
if (uri.toString().startsWith(DEEP_LINK_BASE_URI)) {
model.handleNavigationDeepLink(uri)
model.handleNavigationDeepLink(uri.toMeshtasticUri())
return
}

Expand Down
87 changes: 0 additions & 87 deletions app/src/main/kotlin/org/meshtastic/app/model/UIViewModel.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import org.koin.compose.viewmodel.koinViewModel
import org.meshtastic.app.settings.AndroidRadioConfigViewModel
import org.meshtastic.app.ui.sharing.ChannelScreen
import org.meshtastic.core.navigation.ChannelsRoutes
import org.meshtastic.feature.settings.radio.RadioConfigViewModel

/** Navigation graph for for the top level ChannelScreen - [ChannelsRoutes.Channels]. */
fun EntryProviderScope<NavKey>.channelsGraph(backStack: NavBackStack<NavKey>) {
entry<ChannelsRoutes.ChannelsGraph> {
ChannelScreen(
radioConfigViewModel = koinViewModel<AndroidRadioConfigViewModel>(),
radioConfigViewModel = koinViewModel<RadioConfigViewModel>(),
onNavigate = { route -> backStack.add(route) },
onNavigateUp = { backStack.removeLastOrNull() },
)
}

entry<ChannelsRoutes.Channels> {
ChannelScreen(
radioConfigViewModel = koinViewModel<AndroidRadioConfigViewModel>(),
radioConfigViewModel = koinViewModel<RadioConfigViewModel>(),
onNavigate = { route -> backStack.add(route) },
onNavigateUp = { backStack.removeLastOrNull() },
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import org.koin.compose.viewmodel.koinViewModel
import org.meshtastic.app.settings.AndroidRadioConfigViewModel
import org.meshtastic.core.navigation.ConnectionsRoutes
import org.meshtastic.core.navigation.NodesRoutes
import org.meshtastic.feature.connections.AndroidScannerViewModel
import org.meshtastic.feature.connections.ui.ConnectionsScreen
import org.meshtastic.feature.settings.radio.RadioConfigViewModel

/** Navigation graph for for the top level ConnectionsScreen - [ConnectionsRoutes.Connections]. */
fun EntryProviderScope<NavKey>.connectionsGraph(backStack: NavBackStack<NavKey>) {
entry<ConnectionsRoutes.ConnectionsGraph> {
ConnectionsScreen(
scanModel = koinViewModel<AndroidScannerViewModel>(),
radioConfigViewModel = koinViewModel<AndroidRadioConfigViewModel>(),
radioConfigViewModel = koinViewModel<RadioConfigViewModel>(),
onClickNodeChip = {
// Navigation 3 ignores back stack behavior options; we handle this by popping if necessary.
backStack.add(NodesRoutes.NodeDetailGraph(it))
Expand All @@ -44,7 +44,7 @@ fun EntryProviderScope<NavKey>.connectionsGraph(backStack: NavBackStack<NavKey>)
entry<ConnectionsRoutes.Connections> {
ConnectionsScreen(
scanModel = koinViewModel<AndroidScannerViewModel>(),
radioConfigViewModel = koinViewModel<AndroidRadioConfigViewModel>(),
radioConfigViewModel = koinViewModel<RadioConfigViewModel>(),
onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) },
onNavigateToNodeDetails = { backStack.add(NodesRoutes.NodeDetailGraph(it)) },
onConfigNavigate = { route -> backStack.add(route) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import kotlinx.coroutines.flow.Flow
import org.koin.compose.viewmodel.koinViewModel
import org.meshtastic.app.model.UIViewModel
import org.meshtastic.core.navigation.ContactsRoutes
import org.meshtastic.core.ui.component.ScrollToTopEvent
import org.meshtastic.core.ui.viewmodel.UIViewModel
import org.meshtastic.feature.messaging.MessageViewModel
import org.meshtastic.feature.messaging.QuickChatScreen
import org.meshtastic.feature.messaging.QuickChatViewModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import kotlinx.coroutines.flow.Flow
import org.jetbrains.compose.resources.StringResource
import org.koin.compose.viewmodel.koinViewModel
import org.meshtastic.app.map.node.NodeMapScreen
import org.meshtastic.app.node.AndroidMetricsViewModel
import org.meshtastic.app.ui.node.AdaptiveNodeListScreen
import org.meshtastic.core.navigation.ContactsRoutes
import org.meshtastic.core.navigation.NodeDetailRoutes
Expand Down Expand Up @@ -116,7 +115,7 @@ fun EntryProviderScope<NavKey>.nodeDetailGraph(
}

entry<NodeDetailRoutes.TracerouteLog> { args ->
val metricsViewModel = koinViewModel<AndroidMetricsViewModel>()
val metricsViewModel = koinViewModel<MetricsViewModel>()
metricsViewModel.setNodeId(args.destNum)

TracerouteLogScreen(
Expand All @@ -135,7 +134,7 @@ fun EntryProviderScope<NavKey>.nodeDetailGraph(
}

entry<NodeDetailRoutes.TracerouteMap> { args ->
val metricsViewModel = koinViewModel<AndroidMetricsViewModel>()
val metricsViewModel = koinViewModel<MetricsViewModel>()
metricsViewModel.setNodeId(args.destNum)

TracerouteMapScreen(
Expand Down Expand Up @@ -177,7 +176,7 @@ private inline fun <reified R : Route> EntryProviderScope<NavKey>.addNodeDetailS
crossinline getDestNum: (R) -> Int,
) {
entry<R> { args ->
val metricsViewModel = koinViewModel<AndroidMetricsViewModel>()
val metricsViewModel = koinViewModel<MetricsViewModel>()
val destNum = getDestNum(args)
metricsViewModel.setNodeId(destNum)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ import androidx.navigation3.runtime.EntryProviderScope
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.core.navigation.NodesRoutes
import org.meshtastic.core.navigation.Route
import org.meshtastic.core.navigation.SettingsRoutes
Expand All @@ -37,13 +34,16 @@ import org.meshtastic.feature.settings.AdministrationScreen
import org.meshtastic.feature.settings.DeviceConfigurationScreen
import org.meshtastic.feature.settings.ModuleConfigurationScreen
import org.meshtastic.feature.settings.SettingsScreen
import org.meshtastic.feature.settings.SettingsViewModel
import org.meshtastic.feature.settings.debugging.DebugScreen
import org.meshtastic.feature.settings.debugging.DebugViewModel
import org.meshtastic.feature.settings.filter.FilterSettingsScreen
import org.meshtastic.feature.settings.filter.FilterSettingsViewModel
import org.meshtastic.feature.settings.navigation.ConfigRoute
import org.meshtastic.feature.settings.navigation.ModuleRoute
import org.meshtastic.feature.settings.radio.CleanNodeDatabaseScreen
import org.meshtastic.feature.settings.radio.CleanNodeDatabaseViewModel
import org.meshtastic.feature.settings.radio.RadioConfigViewModel
import org.meshtastic.feature.settings.radio.channel.ChannelConfigScreen
import org.meshtastic.feature.settings.radio.component.AmbientLightingConfigScreen
import org.meshtastic.feature.settings.radio.component.AudioConfigScreen
Expand Down Expand Up @@ -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 }
Expand All @@ -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)) },
) {
Expand All @@ -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)) },
) {
Expand All @@ -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),
Expand Down Expand Up @@ -189,7 +189,7 @@ fun EntryProviderScope<NavKey>.settingsGraph(backStack: NavBackStack<NavKey>) {
}

entry<SettingsRoutes.DebugPanel> {
val viewModel: AndroidDebugViewModel = koinViewModel()
val viewModel: DebugViewModel = koinViewModel()
DebugScreen(viewModel = viewModel, onNavigateUp = { backStack.removeLastOrNull() })
}

Expand All @@ -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)) }
}
Loading
Loading