chore(frontend): refactor navigation and DI setup, remove unused shared module

- Replaced `initKoin` with `startKoin` for DI initialization consistency across platforms.
- Introduced `StateNavigationPort` with `StateFlow` to streamline navigation state management.
- Migrated `AppScreen` to sealed class with route mapping for better navigation handling.
- Deleted unused `frontend/shared` module and removed related dependencies from build files.
- Cleaned up legacy navigation and Redux-related code, aligning with MVVM architecture.
This commit is contained in:
2026-02-01 13:46:39 +01:00
parent 77c20bf2ba
commit f78563f8c8
41 changed files with 186 additions and 187 deletions
@@ -63,7 +63,7 @@ kotlin {
sourceSets {
commonMain.dependencies {
// Shared modules
implementation(projects.frontend.shared)
// implementation(projects.frontend.shared) // REMOVED: Shared module deleted
implementation(projects.frontend.core.domain)
implementation(projects.frontend.core.designSystem)
implementation(projects.frontend.core.navigation)
@@ -6,7 +6,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.runtime.collectAsState
import at.mocode.clients.shared.navigation.AppScreen
import at.mocode.frontend.core.navigation.AppScreen
import at.mocode.frontend.core.auth.data.AuthTokenManager
import at.mocode.frontend.core.auth.presentation.LoginScreen
import at.mocode.frontend.core.auth.presentation.LoginViewModel
@@ -14,6 +14,7 @@ import at.mocode.ping.feature.presentation.PingScreen
import at.mocode.ping.feature.presentation.PingViewModel
import at.mocode.frontend.core.designsystem.components.AppFooter
import at.mocode.frontend.core.designsystem.theme.AppTheme
import navigation.StateNavigationPort
import org.koin.compose.koinInject
import org.koin.compose.viewmodel.koinViewModel
@@ -25,7 +26,9 @@ fun MainApp() {
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
var currentScreen by remember { mutableStateOf<AppScreen>(AppScreen.Landing) }
// Resolve NavigationPort to observe state changes
val navigationPort = koinInject<StateNavigationPort>()
val currentScreen by navigationPort.currentScreen.collectAsState()
// Resolve AuthTokenManager from Koin
val authTokenManager = koinInject<AuthTokenManager>()
@@ -36,31 +39,31 @@ fun MainApp() {
when (currentScreen) {
is AppScreen.Landing -> LandingScreen(
onPrimaryCta = { currentScreen = AppScreen.Login },
onSecondary = { currentScreen = AppScreen.Home }
onPrimaryCta = { navigationPort.navigateToScreen(AppScreen.Login) },
onSecondary = { navigationPort.navigateToScreen(AppScreen.Home) }
)
is AppScreen.Home -> WelcomeScreen(
authTokenManager = authTokenManager,
onOpenPing = { currentScreen = AppScreen.Ping },
onOpenLogin = { currentScreen = AppScreen.Login },
onOpenProfile = { currentScreen = AppScreen.Profile }
onOpenPing = { navigationPort.navigateToScreen(AppScreen.Ping) },
onOpenLogin = { navigationPort.navigateToScreen(AppScreen.Login) },
onOpenProfile = { navigationPort.navigateToScreen(AppScreen.Profile) }
)
is AppScreen.Login -> LoginScreen(
viewModel = loginViewModel,
onLoginSuccess = { currentScreen = AppScreen.Profile },
onBack = { currentScreen = AppScreen.Home }
onLoginSuccess = { navigationPort.navigateToScreen(AppScreen.Profile) },
onBack = { navigationPort.navigateToScreen(AppScreen.Home) }
)
is AppScreen.Ping -> PingScreen(
viewModel = pingViewModel,
onBack = { currentScreen = AppScreen.Home } // Navigate back to Home
onBack = { navigationPort.navigateToScreen(AppScreen.Home) } // Navigate back to Home
)
is AppScreen.Profile -> AuthStatusScreen(
authTokenManager = authTokenManager,
onBackToHome = { currentScreen = AppScreen.Home }
onBackToHome = { navigationPort.navigateToScreen(AppScreen.Home) }
)
else -> {}
@@ -2,9 +2,13 @@ package navigation
import at.mocode.frontend.core.auth.data.AuthTokenManager
import at.mocode.frontend.core.domain.models.User
import at.mocode.frontend.core.navigation.AppScreen
import at.mocode.frontend.core.navigation.CurrentUserProvider
import at.mocode.frontend.core.navigation.DeepLinkHandler
import at.mocode.frontend.core.navigation.NavigationPort
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.koin.dsl.module
class ShellCurrentUserProvider(
@@ -23,17 +27,29 @@ class ShellCurrentUserProvider(
}
}
class NoopNavigationPort : NavigationPort {
var lastRoute: String? = null
/**
* A real implementation of NavigationPort that updates a StateFlow.
* This allows the MainApp to observe changes and update the UI.
*/
class StateNavigationPort : NavigationPort {
private val _currentScreen = MutableStateFlow<AppScreen>(AppScreen.Landing)
val currentScreen: StateFlow<AppScreen> = _currentScreen.asStateFlow()
override fun navigateTo(route: String) {
lastRoute = route
// Simple logging; actual routing is handled elsewhere in the shell
println("[NavigationPort] navigateTo $route")
val screen = AppScreen.fromRoute(route)
println("[NavigationPort] navigateTo $route -> $screen")
_currentScreen.value = screen
}
fun navigateToScreen(screen: AppScreen) {
_currentScreen.value = screen
}
}
val navigationModule = module {
single<CurrentUserProvider> { ShellCurrentUserProvider(get()) }
single<NavigationPort> { NoopNavigationPort() }
// Bind as both NavigationPort (for Core) and StateNavigationPort (for Shell)
single { StateNavigationPort() }
single<NavigationPort> { get<StateNavigationPort>() }
single { DeepLinkHandler(get(), get()) }
}
@@ -7,12 +7,12 @@ import at.mocode.frontend.core.localdb.localDbModule
import at.mocode.frontend.core.network.networkModule
import at.mocode.frontend.core.sync.di.syncModule
import at.mocode.ping.feature.di.pingFeatureModule
import at.mocode.shared.di.initKoin
import kotlinx.browser.document
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import navigation.navigationModule
import org.koin.core.context.GlobalContext
import org.koin.core.context.startKoin
import org.koin.core.context.loadKoinModules
import org.koin.dsl.module
import org.w3c.dom.HTMLElement
@@ -23,7 +23,7 @@ fun main() {
// 1. Initialize DI (Koin) with static modules
try {
initKoin { modules(networkModule, localDbModule, syncModule, pingFeatureModule, authModule, navigationModule) }
startKoin { modules(networkModule, localDbModule, syncModule, pingFeatureModule, authModule, navigationModule) }
console.log("[WebApp] Koin initialized with static modules")
} catch (e: dynamic) {
console.warn("[WebApp] Koin initialization warning:", e)
@@ -2,7 +2,6 @@ import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.unit.dp
import at.mocode.shared.di.initKoin
import at.mocode.frontend.core.network.networkModule
import at.mocode.frontend.core.auth.di.authModule
import at.mocode.frontend.core.sync.di.syncModule
@@ -12,6 +11,7 @@ import at.mocode.frontend.core.localdb.DatabaseProvider
import at.mocode.frontend.core.localdb.localDbModule
import navigation.navigationModule
import kotlinx.coroutines.runBlocking
import org.koin.core.context.startKoin
import org.koin.core.context.loadKoinModules
import org.koin.dsl.module
@@ -19,7 +19,7 @@ fun main() = application {
// Initialize DI (Koin) with shared modules + network module
try {
// Updated: Only load the consolidated pingFeatureModule from at.mocode.ping.feature.di
initKoin { modules(networkModule, syncModule, pingFeatureModule, authModule, navigationModule, localDbModule) }
startKoin { modules(networkModule, syncModule, pingFeatureModule, authModule, navigationModule, localDbModule) }
println("[DesktopApp] Koin initialized with networkModule + authModule + navigationModule + pingFeatureModule + localDbModule")
} catch (e: Exception) {
println("[DesktopApp] Koin initialization warning: ${e.message}")