refactor(frontend, build): update PingViewModel initialization, resolve view model via Koin, and clean yarn dependencies

Injected `PingViewModel` via Koin to align with dependency injection best practices. Suppressed Gradle deprecation warnings and added the `frontend.core.sync` dependency. Cleaned up outdated packages in `yarn.lock`.
This commit is contained in:
2026-01-12 19:47:59 +01:00
parent 32e43b8fb0
commit 9e12018208
23 changed files with 438 additions and 1287 deletions
@@ -1,4 +1,5 @@
@file:OptIn(ExperimentalKotlinGradlePluginApi::class)
@file:Suppress("DEPRECATION")
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
@@ -78,6 +79,7 @@ kotlin {
implementation(projects.frontend.core.designSystem)
implementation(projects.frontend.core.navigation)
implementation(projects.frontend.core.network)
implementation(projects.frontend.core.sync)
implementation(project(":frontend:core:local-db"))
implementation(projects.frontend.features.authFeature)
implementation(projects.frontend.features.pingFeature)
@@ -143,10 +145,12 @@ val copySqliteWorkerJs by tasks.registering(Copy::class) {
from(localDb.layout.buildDirectory.file("processedResources/js/main/sqlite.worker.js"))
// Root build directory where Kotlin JS packages are assembled.
into(rootProject.layout.buildDirectory.dir("js/packages/${rootProject.name}-frontend-shells-meldestelle-portal/kotlin"))
// Use a concrete path (instead of a Provider) so the Copy task always materializes the directory.
into(rootProject.layout.buildDirectory.asFile.get().resolve("js/packages/${rootProject.name}-frontend-shells-meldestelle-portal/kotlin"))
}
tasks.matching { it.name == "jsBrowserProductionWebpack" }.configureEach {
// Ensure the worker is present for the production bundle.
tasks.named("jsBrowserProductionWebpack") {
dependsOn(copySqliteWorkerJs)
}
@@ -163,6 +167,14 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
}
}
// ---------------------------------------------------------------------------
// Kotlin/JS source maps
// ---------------------------------------------------------------------------
// Production source maps must remain enabled for browser debugging.
// The remaining Kotlin/Gradle message
// `Cannot rewrite paths in JavaScript source maps: Too many sources or format is not supported`
// is treated as an external Kotlin/JS toolchain limitation and is documented separately.
// Configure a duplicate handling strategy for distribution tasks
tasks.withType<Tar> {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
@@ -8,7 +8,7 @@ import androidx.compose.runtime.collectAsState
import at.mocode.clients.shared.navigation.AppScreen
import at.mocode.clients.authfeature.AuthTokenManager
import at.mocode.clients.pingfeature.PingScreen
import at.mocode.clients.pingfeature.PingViewModel
import at.mocode.ping.feature.presentation.PingViewModel
import at.mocode.shared.core.AppConstants
import androidx.compose.material3.OutlinedTextField
import androidx.compose.ui.text.input.PasswordVisualTransformation
@@ -34,7 +34,8 @@ fun MainApp() {
// Resolve AuthTokenManager from Koin
val authTokenManager = koinInject<AuthTokenManager>()
val authApiClient = koinInject<AuthApiClient>()
val pingViewModel = remember { PingViewModel() }
// Delta-Sync blueprint: resolve the Ping feature view model via Koin.
val pingViewModel: PingViewModel = koinViewModel()
val scope = rememberCoroutineScope()
// Handle PKCE callback on an app load (web)
@@ -7,11 +7,20 @@ import at.mocode.frontend.core.network.networkModule
import at.mocode.clients.authfeature.di.authFeatureModule
import at.mocode.frontend.core.localdb.localDbModule
import at.mocode.frontend.core.localdb.DatabaseProvider
import at.mocode.frontend.core.localdb.AppDatabase
import at.mocode.frontend.core.sync.di.syncModule
import at.mocode.ping.feature.di.pingFeatureModule
import navigation.navigationModule
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import org.koin.core.context.GlobalContext
import org.koin.core.context.GlobalContext.get
import org.koin.core.qualifier.named
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.core.Koin
import org.koin.core.context.loadKoinModules
import org.koin.dsl.module
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
@@ -21,7 +30,7 @@ fun main() {
console.log("[WebApp] main() entered")
// Initialize DI (Koin) with shared modules + network + local DB modules
try {
initKoin { modules(networkModule, localDbModule, authFeatureModule, navigationModule) }
initKoin { modules(networkModule, localDbModule, syncModule, pingFeatureModule, authFeatureModule, navigationModule) }
console.log("[WebApp] Koin initialized with networkModule + localDbModule + authFeatureModule + navigationModule")
} catch (e: dynamic) {
console.warn("[WebApp] Koin initialization warning:", e)
@@ -47,6 +56,15 @@ fun main() {
MainScope().launch {
try {
val db = provider.createDatabase()
// Register the created DB instance into Koin so feature repositories can use it.
// This is the central place where we bridge the async DB creation into the DI graph.
// Inject the created DB instance into Koin.
// We register a one-off module that provides this concrete instance.
loadKoinModules(
module {
single<AppDatabase> { db }
}
)
console.log("[WebApp] Local DB created:", jsTypeOf(db))
} catch (e: dynamic) {
console.warn("[WebApp] Local DB smoke failed:", e?.message ?: e)
@@ -5,16 +5,37 @@ import androidx.compose.ui.unit.dp
import at.mocode.shared.di.initKoin
import at.mocode.frontend.core.network.networkModule
import at.mocode.clients.authfeature.di.authFeatureModule
import at.mocode.frontend.core.sync.di.syncModule
import at.mocode.ping.feature.di.pingFeatureModule
import at.mocode.frontend.core.localdb.AppDatabase
import at.mocode.frontend.core.localdb.DatabaseProvider
import navigation.navigationModule
import kotlinx.coroutines.runBlocking
import org.koin.core.context.loadKoinModules
import org.koin.dsl.module
fun main() = application {
// Initialize DI (Koin) with shared modules + network module
try {
initKoin { modules(networkModule, authFeatureModule, navigationModule) }
initKoin { modules(networkModule, syncModule, pingFeatureModule, authFeatureModule, navigationModule) }
println("[DesktopApp] Koin initialized with networkModule + authFeatureModule + navigationModule")
} catch (e: Exception) {
println("[DesktopApp] Koin initialization warning: ${e.message}")
}
// Create the local DB once and register it into Koin so feature repositories can resolve it.
try {
val provider = org.koin.core.context.GlobalContext.get().get<DatabaseProvider>()
val db = runBlocking { provider.createDatabase() }
loadKoinModules(
module {
single<AppDatabase> { db }
}
)
println("[DesktopApp] Local DB created and registered in Koin")
} catch (e: Exception) {
println("[DesktopApp] Local DB init warning: ${e.message}")
}
Window(
onCloseRequest = ::exitApplication,
title = "Meldestelle - Desktop Development",
@@ -6,18 +6,47 @@ import org.w3c.dom.HTMLElement
import at.mocode.shared.di.initKoin
import at.mocode.frontend.core.network.networkModule
import at.mocode.clients.authfeature.di.authFeatureModule
import at.mocode.frontend.core.sync.di.syncModule
import at.mocode.ping.feature.di.pingFeatureModule
import at.mocode.frontend.core.localdb.AppDatabase
import at.mocode.frontend.core.localdb.DatabaseProvider
import navigation.navigationModule
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import org.koin.core.context.GlobalContext
import org.koin.core.context.loadKoinModules
import org.koin.dsl.module
@OptIn(ExperimentalComposeUiApi::class)
fun main() {
// Initialize DI
try {
initKoin { modules(networkModule, authFeatureModule, navigationModule) }
initKoin { modules(networkModule, syncModule, pingFeatureModule, authFeatureModule, navigationModule) }
println("[WasmApp] Koin initialized (with navigationModule)")
} catch (e: Exception) {
println("[WasmApp] Koin init failed: ${e.message}")
}
// Create the local DB asynchronously and register it into Koin.
try {
val provider = GlobalContext.get().get<DatabaseProvider>()
MainScope().launch {
try {
val db = provider.createDatabase()
loadKoinModules(
module {
single<AppDatabase> { db }
}
)
println("[WasmApp] Local DB created and registered in Koin")
} catch (e: dynamic) {
println("[WasmApp] Local DB init warning: ${e?.message ?: e}")
}
}
} catch (e: Exception) {
println("[WasmApp] Local DB init warning: ${e.message}")
}
val root = document.getElementById("ComposeTarget") as HTMLElement
ComposeViewport(root) {
MainApp()
@@ -0,0 +1,29 @@
// Suppress a known, external webpack warning coming from `@sqlite.org/sqlite-wasm`.
//
// Webpack warning:
// "Critical dependency: the request of a dependency is an expression"
//
// Root cause:
// `@sqlite.org/sqlite-wasm/sqlite-wasm/jswasm/sqlite3.mjs` uses a dynamic Worker URL:
// `new Worker(new URL(options.proxyUri, import.meta.url))`
// which webpack cannot statically analyze.
//
// We keep this suppression максимально spezifisch:
// - match only this warning message
// - and only if it originates from the sqlite-wasm package path.
(function (config) {
config.ignoreWarnings = config.ignoreWarnings || []
// Webpack passes warning objects with `message` and `module.resource`.
config.ignoreWarnings.push((warning) => {
const message = String(warning && warning.message ? warning.message : warning)
if (!message.includes('Critical dependency: the request of a dependency is an expression')) return false
const resource = warning && warning.module && warning.module.resource
? String(warning.module.resource)
: ''
return resource.includes('node_modules/@sqlite.org/sqlite-wasm/')
})
})(config)