From 2725342c3ffd0981c11e6968944a5022013329c1 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Sun, 1 Feb 2026 00:50:57 +1100 Subject: [PATCH] refactor: switch from metro to koin Honestly metro looks too overcomplicated and I still don't know how to use it properly. Switching to koin for now as I'm more comfortable with it. --- build.gradle.kts | 2 +- core/build.gradle.kts | 5 +- .../kotlin/moe/lava/neon/core/AppSettings.kt | 5 - .../moe/lava/neon/core/api/ApiClient.kt | 5 - .../neon/core/api/gateway/GatewayHandler.kt | 8 +- .../neon/core/api/gateway/GatewaySession.kt | 6 +- .../handlers/{Handler.kt => EventHandlers.kt} | 4 + .../core/api/gateway/handlers/ReadyHandler.kt | 2 - .../kotlin/moe/lava/neon/core/di/AppGraph.kt | 18 --- .../moe/lava/neon/core/di/CoreModule.kt | 24 +++ .../lava/neon/core/di/EventHandlerGraph.kt | 12 -- .../neon/core/repository/AuthRepository.kt | 5 - .../neon/core/repository/UserRepository.kt | 6 - gradle/libs.versions.toml | 12 +- ui/build.gradle.kts | 8 +- .../kotlin/moe/lava/neon/MainActivity.kt | 16 +- .../commonMain/kotlin/moe/lava/neon/ui/App.kt | 145 +++++++++--------- .../kotlin/moe/lava/neon/ui/di/AppUiGraph.kt | 29 ---- .../kotlin/moe/lava/neon/ui/di/KoinInit.kt | 13 ++ .../kotlin/moe/lava/neon/ui/di/UiModule.kt | 13 ++ .../kotlin/moe/lava/neon/ui/screens/Login.kt | 11 +- .../kotlin/moe/lava/neon/ui/screens/Sample.kt | 11 +- ui/src/jvmMain/kotlin/moe/lava/neon/main.kt | 4 + 23 files changed, 165 insertions(+), 199 deletions(-) rename core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/handlers/{Handler.kt => EventHandlers.kt} (72%) delete mode 100644 core/src/commonMain/kotlin/moe/lava/neon/core/di/AppGraph.kt create mode 100644 core/src/commonMain/kotlin/moe/lava/neon/core/di/CoreModule.kt delete mode 100644 core/src/commonMain/kotlin/moe/lava/neon/core/di/EventHandlerGraph.kt delete mode 100644 ui/src/commonMain/kotlin/moe/lava/neon/ui/di/AppUiGraph.kt create mode 100644 ui/src/commonMain/kotlin/moe/lava/neon/ui/di/KoinInit.kt create mode 100644 ui/src/commonMain/kotlin/moe/lava/neon/ui/di/UiModule.kt diff --git a/build.gradle.kts b/build.gradle.kts index 8c3efbb..342cac8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,8 +6,8 @@ plugins { alias(libs.plugins.composeHotReload) apply false alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.composeCompiler) apply false + alias(libs.plugins.koinCompiler) apply false alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.kotlinSerialization) apply false - alias(libs.plugins.metro) apply false alias(libs.plugins.sqldelight) apply false } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 78e5ec2..ed7c496 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -2,9 +2,9 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.androidLibrary) + alias(libs.plugins.koinCompiler) alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinSerialization) - alias(libs.plugins.metro) alias(libs.plugins.sqldelight) } @@ -23,6 +23,9 @@ kotlin { implementation(libs.ktor.client.websockets) implementation(libs.ktor.serialization.kotlinx.json) + implementation(project.dependencies.platform(libs.koin.bom)) + implementation(libs.koin.core) + implementation(libs.kermit) implementation(libs.settings) } diff --git a/core/src/commonMain/kotlin/moe/lava/neon/core/AppSettings.kt b/core/src/commonMain/kotlin/moe/lava/neon/core/AppSettings.kt index 5d5040b..2ca64da 100644 --- a/core/src/commonMain/kotlin/moe/lava/neon/core/AppSettings.kt +++ b/core/src/commonMain/kotlin/moe/lava/neon/core/AppSettings.kt @@ -2,12 +2,7 @@ package moe.lava.neon.core import com.russhwolf.settings.Settings import com.russhwolf.settings.nullableString -import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.Inject -import dev.zacsweers.metro.SingleIn -@SingleIn(AppScope::class) -@Inject class AppSettings { private val settings = Settings() diff --git a/core/src/commonMain/kotlin/moe/lava/neon/core/api/ApiClient.kt b/core/src/commonMain/kotlin/moe/lava/neon/core/api/ApiClient.kt index d34dbf3..727b4e2 100644 --- a/core/src/commonMain/kotlin/moe/lava/neon/core/api/ApiClient.kt +++ b/core/src/commonMain/kotlin/moe/lava/neon/core/api/ApiClient.kt @@ -1,9 +1,6 @@ package moe.lava.neon.core.api import co.touchlab.kermit.Logger -import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.Inject -import dev.zacsweers.metro.SingleIn import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.plugins.HttpSend @@ -23,8 +20,6 @@ import kotlinx.serialization.json.JsonNamingStrategy import moe.lava.neon.core.api.captcha.CaptchaRequest import moe.lava.neon.core.api.captcha.CaptchaResponse -@SingleIn(AppScope::class) -@Inject class ApiClient { private val logger = Logger.withTag("neon.core.api/client") diff --git a/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/GatewayHandler.kt b/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/GatewayHandler.kt index 40dc00e..9126635 100644 --- a/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/GatewayHandler.kt +++ b/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/GatewayHandler.kt @@ -1,21 +1,19 @@ package moe.lava.neon.core.api.gateway import co.touchlab.kermit.Logger -import dev.zacsweers.metro.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.serialization.ExperimentalSerializationApi -import moe.lava.neon.core.di.EventHandlerGraph +import moe.lava.neon.core.api.gateway.handlers.EventHandlers import moe.lava.neon.core.repository.AuthRepository import kotlin.math.pow import kotlin.time.Duration.Companion.seconds -@Inject class GatewayHandler( private val auth: AuthRepository, - private val handlers: EventHandlerGraph, + private val eventHandlers: EventHandlers, ) { private val logger = Logger.withTag("neon.core.api.gateway/handler") private val scope = CoroutineScope(Dispatchers.IO) @@ -35,7 +33,7 @@ class GatewayHandler( session = GatewaySession.start( token = token, - eventHandlers = handlers, + eventHandlers = eventHandlers, resumeProps = resumeProps, onSuccess = { logger.d { "Successful session start" } diff --git a/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/GatewaySession.kt b/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/GatewaySession.kt index 1ac638b..c2fc7b7 100644 --- a/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/GatewaySession.kt +++ b/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/GatewaySession.kt @@ -24,7 +24,7 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import moe.lava.neon.core.api.ApiConstants import moe.lava.neon.core.api.ApiConstants.json -import moe.lava.neon.core.di.EventHandlerGraph +import moe.lava.neon.core.api.gateway.handlers.EventHandlers import kotlin.random.Random import kotlin.time.Duration.Companion.milliseconds @@ -33,7 +33,7 @@ private val logger = Logger.withTag("neon.core.api.gateway/session") class GatewaySession private constructor( private var ws: DefaultClientWebSocketSession, private val token: String, - private val handlers: EventHandlerGraph, + private val handlers: EventHandlers, private val scope: CoroutineScope, private var resumeProps: ResumeProperties?, private val onDestroy: (GatewayCloseReason, ResumeProperties?) -> Unit, @@ -46,7 +46,7 @@ class GatewaySession private constructor( companion object { suspend fun start( token: String, - eventHandlers: EventHandlerGraph, + eventHandlers: EventHandlers, client: HttpClient = HttpClient { install(HttpCookies) install(WebSockets) diff --git a/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/handlers/Handler.kt b/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/handlers/EventHandlers.kt similarity index 72% rename from core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/handlers/Handler.kt rename to core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/handlers/EventHandlers.kt index 5f6a6ac..87504bf 100644 --- a/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/handlers/Handler.kt +++ b/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/handlers/EventHandlers.kt @@ -3,3 +3,7 @@ package moe.lava.neon.core.api.gateway.handlers import moe.lava.neon.core.api.gateway.Event sealed interface Handler + +class EventHandlers( + val ready: ReadyHandler +) diff --git a/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/handlers/ReadyHandler.kt b/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/handlers/ReadyHandler.kt index f9b5afc..e3b2ef9 100644 --- a/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/handlers/ReadyHandler.kt +++ b/core/src/commonMain/kotlin/moe/lava/neon/core/api/gateway/handlers/ReadyHandler.kt @@ -1,13 +1,11 @@ package moe.lava.neon.core.api.gateway.handlers import co.touchlab.kermit.Logger -import dev.zacsweers.metro.Inject import moe.lava.neon.core.api.gateway.Event import moe.lava.neon.core.api.gateway.ResumeProperties private val logger = Logger.withTag("neon.core.api.events/ready") -@Inject class ReadyHandler : Handler { fun handle(event: Event.Ready, updateResumeProps: (ResumeProperties) -> Unit) { logger.i { "Received payload $event" } diff --git a/core/src/commonMain/kotlin/moe/lava/neon/core/di/AppGraph.kt b/core/src/commonMain/kotlin/moe/lava/neon/core/di/AppGraph.kt deleted file mode 100644 index 1b38b5a..0000000 --- a/core/src/commonMain/kotlin/moe/lava/neon/core/di/AppGraph.kt +++ /dev/null @@ -1,18 +0,0 @@ -package moe.lava.neon.core.di - -import dev.zacsweers.metro.GraphExtension -import moe.lava.neon.core.AppSettings -import moe.lava.neon.core.api.ApiClient -import moe.lava.neon.core.repository.AuthRepository -import moe.lava.neon.core.repository.UserRepository - -@GraphExtension -interface AppGraph { - val api: ApiClient - val settings: AppSettings - - val auth: AuthRepository - val users: UserRepository - - val gatewayHandlers: EventHandlerGraph -} diff --git a/core/src/commonMain/kotlin/moe/lava/neon/core/di/CoreModule.kt b/core/src/commonMain/kotlin/moe/lava/neon/core/di/CoreModule.kt new file mode 100644 index 0000000..c16a33e --- /dev/null +++ b/core/src/commonMain/kotlin/moe/lava/neon/core/di/CoreModule.kt @@ -0,0 +1,24 @@ +package moe.lava.neon.core.di + +import moe.lava.neon.core.AppSettings +import moe.lava.neon.core.api.ApiClient +import moe.lava.neon.core.api.gateway.GatewayHandler +import moe.lava.neon.core.api.gateway.handlers.EventHandlers +import moe.lava.neon.core.api.gateway.handlers.ReadyHandler +import moe.lava.neon.core.repository.AuthRepository +import moe.lava.neon.core.repository.UserRepository +import org.koin.dsl.module +import org.koin.plugin.module.dsl.single + +val coreModule = module { + single() + single() + + single() + single() + + single() + + single() + single() +} diff --git a/core/src/commonMain/kotlin/moe/lava/neon/core/di/EventHandlerGraph.kt b/core/src/commonMain/kotlin/moe/lava/neon/core/di/EventHandlerGraph.kt deleted file mode 100644 index dfa03dd..0000000 --- a/core/src/commonMain/kotlin/moe/lava/neon/core/di/EventHandlerGraph.kt +++ /dev/null @@ -1,12 +0,0 @@ -package moe.lava.neon.core.di - -import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.ContributesTo -import dev.zacsweers.metro.GraphExtension -import moe.lava.neon.core.api.gateway.handlers.ReadyHandler - -@GraphExtension -@ContributesTo(AppScope::class) -interface EventHandlerGraph { - val ready: ReadyHandler -} diff --git a/core/src/commonMain/kotlin/moe/lava/neon/core/repository/AuthRepository.kt b/core/src/commonMain/kotlin/moe/lava/neon/core/repository/AuthRepository.kt index 93bbae6..9375cd2 100644 --- a/core/src/commonMain/kotlin/moe/lava/neon/core/repository/AuthRepository.kt +++ b/core/src/commonMain/kotlin/moe/lava/neon/core/repository/AuthRepository.kt @@ -1,9 +1,6 @@ package moe.lava.neon.core.repository import co.touchlab.kermit.Logger -import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.Inject -import dev.zacsweers.metro.SingleIn import io.ktor.client.call.body import io.ktor.client.request.get import io.ktor.client.request.header @@ -46,8 +43,6 @@ sealed class AuthResponse { // data class MFARequested() : AuthResponse() } -@Inject -@SingleIn(AppScope::class) class AuthRepository( private val settings: AppSettings, private val api: ApiClient, diff --git a/core/src/commonMain/kotlin/moe/lava/neon/core/repository/UserRepository.kt b/core/src/commonMain/kotlin/moe/lava/neon/core/repository/UserRepository.kt index 53ff0c6..1392010 100644 --- a/core/src/commonMain/kotlin/moe/lava/neon/core/repository/UserRepository.kt +++ b/core/src/commonMain/kotlin/moe/lava/neon/core/repository/UserRepository.kt @@ -1,10 +1,4 @@ package moe.lava.neon.core.repository -import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.Inject -import dev.zacsweers.metro.SingleIn - -@Inject -@SingleIn(AppScope::class) class UserRepository { } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9e1d141..ef8ff17 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,12 +19,13 @@ desugar = "2.1.5" hcaptcha = "4.4.0" junit = "4.13.2" kermit = "2.0.8" +koin-bom = "4.2.0-RC1" +koin-plugin = "0.3.0" kotlin = "2.3.0" kotlinx-coroutines = "1.10.2" ktor = "3.4.0" material3 = "1.11.0-alpha02" material3-adaptive = "1.3.0-alpha04" -metro = "0.10.2" settings = "1.3.0" sqldelight = "2.2.1" @@ -54,6 +55,12 @@ desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desuga hcaptcha-compose = { module = "com.github.hCaptcha.hcaptcha-android-sdk:compose-sdk", version.ref = "hcaptcha" } junit = { module = "junit:junit", version.ref = "junit" } kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } +koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin-bom" } +koin-compose = { module = "io.insert-koin:koin-compose" } +koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel" } +koin-compose-navigation3 = { module = "io.insert-koin:koin-compose-navigation3" } +koin-core = { module = "io.insert-koin:koin-core" } +koin-test = { module = "io.insert-koin:koin-test" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } @@ -62,7 +69,6 @@ ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } -metrox-viewmodel-compose = { module = "dev.zacsweers.metro:metrox-viewmodel-compose", version.ref = "metro" } settings = { module = "com.russhwolf:multiplatform-settings-no-arg", version.ref = "settings" } [plugins] @@ -71,7 +77,7 @@ androidLibrary = { id = "com.android.library", version.ref = "agp" } composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "compose-hot-reload" } composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } +koinCompiler = { id = "io.insert-koin.compiler.plugin", version.ref = "koin-plugin" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } -metro = { id = "dev.zacsweers.metro", version.ref = "metro" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index ed73203..31edb2d 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -8,7 +8,7 @@ plugins { alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) alias(libs.plugins.composeHotReload) - alias(libs.plugins.metro) + alias(libs.plugins.koinCompiler) } kotlin { @@ -54,8 +54,10 @@ kotlin { implementation(libs.kermit) - implementation(libs.metrox.viewmodel.compose) - + implementation(project.dependencies.platform(libs.koin.bom)) + implementation(libs.koin.compose) + implementation(libs.koin.compose.viewmodel) + implementation(libs.koin.compose.navigation3) } commonTest.dependencies { implementation(libs.kotlin.test) diff --git a/ui/src/androidMain/kotlin/moe/lava/neon/MainActivity.kt b/ui/src/androidMain/kotlin/moe/lava/neon/MainActivity.kt index 45b77df..cc4947f 100644 --- a/ui/src/androidMain/kotlin/moe/lava/neon/MainActivity.kt +++ b/ui/src/androidMain/kotlin/moe/lava/neon/MainActivity.kt @@ -4,23 +4,23 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.Preview import moe.lava.neon.ui.App +import moe.lava.neon.ui.di.initKoin +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) + initKoin { + androidContext(this@MainActivity) + androidLogger() + } + setContent { App() } } } - -@Preview -@Composable -fun AppAndroidPreview() { - App() -} diff --git a/ui/src/commonMain/kotlin/moe/lava/neon/ui/App.kt b/ui/src/commonMain/kotlin/moe/lava/neon/ui/App.kt index a24fac4..c42a2cc 100644 --- a/ui/src/commonMain/kotlin/moe/lava/neon/ui/App.kt +++ b/ui/src/commonMain/kotlin/moe/lava/neon/ui/App.kt @@ -5,7 +5,6 @@ import androidx.compose.material3.MaterialExpressiveTheme import androidx.compose.material3.MotionScheme import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entryProvider @@ -14,12 +13,10 @@ import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.ui.NavDisplay import androidx.savedstate.serialization.SavedStateConfiguration import co.touchlab.kermit.Logger -import dev.zacsweers.metro.createGraph -import dev.zacsweers.metrox.viewmodel.LocalMetroViewModelFactory import kotlinx.serialization.Serializable import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic -import moe.lava.neon.ui.di.AppUiGraph +import moe.lava.neon.core.repository.AuthRepository import moe.lava.neon.ui.screens.Login import moe.lava.neon.ui.screens.Sample import moe.lava.neon.ui.screens.chat.Chat @@ -29,6 +26,7 @@ import moe.lava.neon.ui.screens.navigator.NavigatorModel import moe.lava.neon.ui.screens.navigator.NavigatorPreviewProvider import moe.lava.neon.ui.util.ThreePaneSceneStrategy import moe.lava.neon.ui.util.rememberThreePaneSceneStrategy +import org.koin.compose.koinInject import kotlin.system.exitProcess object Route { @@ -68,81 +66,78 @@ fun App() { exitProcess(1) } - val uiGraph = createGraph() - val graph = uiGraph.core - CaptchaBinder(graph.api) - CompositionLocalProvider(LocalMetroViewModelFactory provides uiGraph.metroViewModelFactory) { - MaterialExpressiveTheme( - colorScheme = getColorScheme(), - motionScheme = MotionScheme.expressive(), - ) { - val init = if (graph.auth.token != null) Route.Sample else Route.Login - val backStack = rememberNavBackStack(config, Route.Sample) - val threePaneStrategy = rememberThreePaneSceneStrategy() - NavDisplay( - backStack = backStack, - entryDecorators = listOf( - rememberSaveableStateHolderNavEntryDecorator(), - rememberViewModelStoreNavEntryDecorator(), - ), - onBack = { backStack.removeLastOrNull() }, - sceneStrategy = threePaneStrategy, - entryProvider = entryProvider { - entry { - Login( - onSuccess = { - backStack.clear() - backStack.add(Route.Sample) - } - ) - } - entry { - Sample( - navTest = { - backStack.add(Route.Navigator(it)) - backStack.add(Route.Chat) - backStack.add(Route.MembersList) - }, - onRequestLogout = { - backStack.clear() - backStack.add(Route.Login) - } - ) - } - - entry( - metadata = ThreePaneSceneStrategy.listPane() - ) { key -> - if (key.left) { - Navigator( - NavigatorPreviewProvider.base2.copy( - guildNavPosition = NavigatorModel.GuildNavPosition.LeftSidebar - ) - ) - } else { - Navigator( - NavigatorPreviewProvider.base2.copy( - guildNavPosition = NavigatorModel.GuildNavPosition.BottomSheet - ) - ) + val auth: AuthRepository = koinInject() + CaptchaBinder(koinInject()) + MaterialExpressiveTheme( + colorScheme = getColorScheme(), + motionScheme = MotionScheme.expressive(), + ) { + val init = if (auth.token != null) Route.Sample else Route.Login + val backStack = rememberNavBackStack(config, Route.Sample) + val threePaneStrategy = rememberThreePaneSceneStrategy() + NavDisplay( + backStack = backStack, + entryDecorators = listOf( + rememberSaveableStateHolderNavEntryDecorator(), + rememberViewModelStoreNavEntryDecorator(), + ), + onBack = { backStack.removeLastOrNull() }, + sceneStrategy = threePaneStrategy, + entryProvider = entryProvider { + entry { + Login( + onSuccess = { + backStack.clear() + backStack.add(Route.Sample) } - } + ) + } + entry { + Sample( + navTest = { + backStack.add(Route.Navigator(it)) + backStack.add(Route.Chat) + backStack.add(Route.MembersList) + }, + onRequestLogout = { + backStack.clear() + backStack.add(Route.Login) + } + ) + } - entry( - metadata = ThreePaneSceneStrategy.detailPane() - ) { - Chat( - onOpenMembers = { backStack.add(Route.MembersList) } + entry( + metadata = ThreePaneSceneStrategy.listPane() + ) { key -> + if (key.left) { + Navigator( + NavigatorPreviewProvider.base2.copy( + guildNavPosition = NavigatorModel.GuildNavPosition.LeftSidebar + ) + ) + } else { + Navigator( + NavigatorPreviewProvider.base2.copy( + guildNavPosition = NavigatorModel.GuildNavPosition.BottomSheet + ) ) - } - - entry( - metadata = ThreePaneSceneStrategy.extraPane() - ) { - MembersList() } } - ) - } + + entry( + metadata = ThreePaneSceneStrategy.detailPane() + ) { + Chat( + onOpenMembers = { backStack.add(Route.MembersList) } + ) + } + + entry( + metadata = ThreePaneSceneStrategy.extraPane() + ) { + MembersList() + } + } + ) } } diff --git a/ui/src/commonMain/kotlin/moe/lava/neon/ui/di/AppUiGraph.kt b/ui/src/commonMain/kotlin/moe/lava/neon/ui/di/AppUiGraph.kt deleted file mode 100644 index 344555a..0000000 --- a/ui/src/commonMain/kotlin/moe/lava/neon/ui/di/AppUiGraph.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.lava.neon.ui.di - -import androidx.lifecycle.ViewModel -import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.DependencyGraph -import dev.zacsweers.metro.Inject -import dev.zacsweers.metro.Provider -import dev.zacsweers.metro.SingleIn -import dev.zacsweers.metrox.viewmodel.ManualViewModelAssistedFactory -import dev.zacsweers.metrox.viewmodel.MetroViewModelFactory -import dev.zacsweers.metrox.viewmodel.ViewModelAssistedFactory -import dev.zacsweers.metrox.viewmodel.ViewModelGraph -import moe.lava.neon.core.di.AppGraph -import kotlin.reflect.KClass - -@DependencyGraph(AppScope::class) -interface AppUiGraph : ViewModelGraph { - val core: AppGraph -} - -@Inject -@ContributesBinding(AppScope::class) -@SingleIn(AppScope::class) -class AppViewModelFactory( - override val viewModelProviders: Map, Provider>, - override val assistedFactoryProviders: Map, Provider>, - override val manualAssistedFactoryProviders: Map, Provider>, -) : MetroViewModelFactory() diff --git a/ui/src/commonMain/kotlin/moe/lava/neon/ui/di/KoinInit.kt b/ui/src/commonMain/kotlin/moe/lava/neon/ui/di/KoinInit.kt new file mode 100644 index 0000000..ca4a856 --- /dev/null +++ b/ui/src/commonMain/kotlin/moe/lava/neon/ui/di/KoinInit.kt @@ -0,0 +1,13 @@ +package moe.lava.neon.ui.di + +import org.koin.core.KoinApplication +import org.koin.core.context.startKoin +import org.koin.dsl.KoinAppDeclaration +import org.koin.dsl.includes + +fun initKoin(config: KoinAppDeclaration? = null): KoinApplication { + return startKoin { + includes(config) + modules(uiModule) + } +} diff --git a/ui/src/commonMain/kotlin/moe/lava/neon/ui/di/UiModule.kt b/ui/src/commonMain/kotlin/moe/lava/neon/ui/di/UiModule.kt new file mode 100644 index 0000000..fd25012 --- /dev/null +++ b/ui/src/commonMain/kotlin/moe/lava/neon/ui/di/UiModule.kt @@ -0,0 +1,13 @@ +package moe.lava.neon.ui.di + +import moe.lava.neon.core.di.coreModule +import moe.lava.neon.ui.screens.LoginViewModel +import moe.lava.neon.ui.screens.SampleViewModel +import org.koin.dsl.module +import org.koin.plugin.module.dsl.viewModel + +val uiModule = module { + includes(coreModule) + viewModel() + viewModel() +} diff --git a/ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Login.kt b/ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Login.kt index 8ef0335..b04a8fe 100644 --- a/ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Login.kt +++ b/ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Login.kt @@ -23,11 +23,6 @@ import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModel import co.touchlab.kermit.Logger -import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.ContributesIntoMap -import dev.zacsweers.metro.Inject -import dev.zacsweers.metrox.viewmodel.ViewModelKey -import dev.zacsweers.metrox.viewmodel.metroViewModel import kotlinx.coroutines.launch import moe.lava.neon.core.repository.AuthRepository import moe.lava.neon.core.repository.AuthResponse @@ -35,12 +30,13 @@ import moe.lava.neon.resources.Res import moe.lava.neon.resources.visibility import moe.lava.neon.resources.visibility_off import org.jetbrains.compose.resources.painterResource +import org.koin.compose.viewmodel.koinViewModel @Composable fun Login( onSuccess: () -> Unit, ) { - val viewModel: LoginViewModel = metroViewModel() + val viewModel: LoginViewModel = koinViewModel() val scope = rememberCoroutineScope() Column( @@ -113,9 +109,6 @@ fun Login( } } -@Inject -@ViewModelKey(LoginViewModel::class) -@ContributesIntoMap(AppScope::class) class LoginViewModel( private val auth: AuthRepository ) : ViewModel() { diff --git a/ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Sample.kt b/ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Sample.kt index 2a5531f..64579ba 100644 --- a/ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Sample.kt +++ b/ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Sample.kt @@ -20,11 +20,6 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import co.touchlab.kermit.Logger -import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.ContributesIntoMap -import dev.zacsweers.metro.Inject -import dev.zacsweers.metrox.viewmodel.ViewModelKey -import dev.zacsweers.metrox.viewmodel.metroViewModel import kotlinx.coroutines.launch import moe.lava.neon.core.api.gateway.GatewayHandler import moe.lava.neon.core.repository.AuthRepository @@ -32,13 +27,14 @@ import moe.lava.neon.resources.Res import moe.lava.neon.resources.compose_multiplatform import moe.lava.neon.ui.Greeting import org.jetbrains.compose.resources.painterResource +import org.koin.compose.viewmodel.koinViewModel @Composable fun Sample( navTest: (Boolean) -> Unit, onRequestLogout: () -> Unit, ) { - val viewModel: SampleViewModel = metroViewModel() + val viewModel: SampleViewModel = koinViewModel() var showContent by remember { mutableStateOf(false) } Column( modifier = Modifier @@ -86,9 +82,6 @@ fun Sample( } } -@Inject -@ViewModelKey(SampleViewModel::class) -@ContributesIntoMap(AppScope::class) class SampleViewModel( private val auth: AuthRepository, private val gateway: GatewayHandler, diff --git a/ui/src/jvmMain/kotlin/moe/lava/neon/main.kt b/ui/src/jvmMain/kotlin/moe/lava/neon/main.kt index 0db2ea1..9c3c679 100644 --- a/ui/src/jvmMain/kotlin/moe/lava/neon/main.kt +++ b/ui/src/jvmMain/kotlin/moe/lava/neon/main.kt @@ -6,12 +6,16 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import moe.lava.neon.ui.App +import moe.lava.neon.ui.di.initKoin // The UI is designed with touchscreens in mind; on desktop elements may look gigantic // So scale them down a bit const val scaleFactor = 0.75f fun main() = application { + initKoin { + printLogger() + } Window( onCloseRequest = ::exitApplication, title = "Neon",