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.
This commit is contained in:
Cilly Leang 2026-02-01 00:50:57 +11:00
parent 53abaccd21
commit 2725342c3f
Signed by: cilly
GPG key ID: 6500251E087653C9
23 changed files with 165 additions and 199 deletions

View file

@ -6,8 +6,8 @@ plugins {
alias(libs.plugins.composeHotReload) apply false alias(libs.plugins.composeHotReload) apply false
alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.composeCompiler) apply false
alias(libs.plugins.koinCompiler) apply false
alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.kotlinMultiplatform) apply false
alias(libs.plugins.kotlinSerialization) apply false alias(libs.plugins.kotlinSerialization) apply false
alias(libs.plugins.metro) apply false
alias(libs.plugins.sqldelight) apply false alias(libs.plugins.sqldelight) apply false
} }

View file

@ -2,9 +2,9 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins { plugins {
alias(libs.plugins.androidLibrary) alias(libs.plugins.androidLibrary)
alias(libs.plugins.koinCompiler)
alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization) alias(libs.plugins.kotlinSerialization)
alias(libs.plugins.metro)
alias(libs.plugins.sqldelight) alias(libs.plugins.sqldelight)
} }
@ -23,6 +23,9 @@ kotlin {
implementation(libs.ktor.client.websockets) implementation(libs.ktor.client.websockets)
implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.ktor.serialization.kotlinx.json)
implementation(project.dependencies.platform(libs.koin.bom))
implementation(libs.koin.core)
implementation(libs.kermit) implementation(libs.kermit)
implementation(libs.settings) implementation(libs.settings)
} }

View file

@ -2,12 +2,7 @@ package moe.lava.neon.core
import com.russhwolf.settings.Settings import com.russhwolf.settings.Settings
import com.russhwolf.settings.nullableString 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 { class AppSettings {
private val settings = Settings() private val settings = Settings()

View file

@ -1,9 +1,6 @@
package moe.lava.neon.core.api package moe.lava.neon.core.api
import co.touchlab.kermit.Logger 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.HttpClient
import io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.plugins.HttpSend 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.CaptchaRequest
import moe.lava.neon.core.api.captcha.CaptchaResponse import moe.lava.neon.core.api.captcha.CaptchaResponse
@SingleIn(AppScope::class)
@Inject
class ApiClient { class ApiClient {
private val logger = Logger.withTag("neon.core.api/client") private val logger = Logger.withTag("neon.core.api/client")

View file

@ -1,21 +1,19 @@
package moe.lava.neon.core.api.gateway package moe.lava.neon.core.api.gateway
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import dev.zacsweers.metro.Inject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.ExperimentalSerializationApi 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 moe.lava.neon.core.repository.AuthRepository
import kotlin.math.pow import kotlin.math.pow
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@Inject
class GatewayHandler( class GatewayHandler(
private val auth: AuthRepository, private val auth: AuthRepository,
private val handlers: EventHandlerGraph, private val eventHandlers: EventHandlers,
) { ) {
private val logger = Logger.withTag("neon.core.api.gateway/handler") private val logger = Logger.withTag("neon.core.api.gateway/handler")
private val scope = CoroutineScope(Dispatchers.IO) private val scope = CoroutineScope(Dispatchers.IO)
@ -35,7 +33,7 @@ class GatewayHandler(
session = GatewaySession.start( session = GatewaySession.start(
token = token, token = token,
eventHandlers = handlers, eventHandlers = eventHandlers,
resumeProps = resumeProps, resumeProps = resumeProps,
onSuccess = { onSuccess = {
logger.d { "Successful session start" } logger.d { "Successful session start" }

View file

@ -24,7 +24,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import moe.lava.neon.core.api.ApiConstants import moe.lava.neon.core.api.ApiConstants
import moe.lava.neon.core.api.ApiConstants.json 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.random.Random
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
@ -33,7 +33,7 @@ private val logger = Logger.withTag("neon.core.api.gateway/session")
class GatewaySession private constructor( class GatewaySession private constructor(
private var ws: DefaultClientWebSocketSession, private var ws: DefaultClientWebSocketSession,
private val token: String, private val token: String,
private val handlers: EventHandlerGraph, private val handlers: EventHandlers,
private val scope: CoroutineScope, private val scope: CoroutineScope,
private var resumeProps: ResumeProperties?, private var resumeProps: ResumeProperties?,
private val onDestroy: (GatewayCloseReason, ResumeProperties?) -> Unit, private val onDestroy: (GatewayCloseReason, ResumeProperties?) -> Unit,
@ -46,7 +46,7 @@ class GatewaySession private constructor(
companion object { companion object {
suspend fun start( suspend fun start(
token: String, token: String,
eventHandlers: EventHandlerGraph, eventHandlers: EventHandlers,
client: HttpClient = HttpClient { client: HttpClient = HttpClient {
install(HttpCookies) install(HttpCookies)
install(WebSockets) install(WebSockets)

View file

@ -3,3 +3,7 @@ package moe.lava.neon.core.api.gateway.handlers
import moe.lava.neon.core.api.gateway.Event import moe.lava.neon.core.api.gateway.Event
sealed interface Handler<T: Event.Incoming> sealed interface Handler<T: Event.Incoming>
class EventHandlers(
val ready: ReadyHandler
)

View file

@ -1,13 +1,11 @@
package moe.lava.neon.core.api.gateway.handlers package moe.lava.neon.core.api.gateway.handlers
import co.touchlab.kermit.Logger 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.Event
import moe.lava.neon.core.api.gateway.ResumeProperties import moe.lava.neon.core.api.gateway.ResumeProperties
private val logger = Logger.withTag("neon.core.api.events/ready") private val logger = Logger.withTag("neon.core.api.events/ready")
@Inject
class ReadyHandler : Handler<Event.Ready> { class ReadyHandler : Handler<Event.Ready> {
fun handle(event: Event.Ready, updateResumeProps: (ResumeProperties) -> Unit) { fun handle(event: Event.Ready, updateResumeProps: (ResumeProperties) -> Unit) {
logger.i { "Received payload $event" } logger.i { "Received payload $event" }

View file

@ -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
}

View file

@ -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<ApiClient>()
single<AppSettings>()
single<AuthRepository>()
single<UserRepository>()
single<GatewayHandler>()
single<ReadyHandler>()
single<EventHandlers>()
}

View file

@ -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
}

View file

@ -1,9 +1,6 @@
package moe.lava.neon.core.repository package moe.lava.neon.core.repository
import co.touchlab.kermit.Logger 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.call.body
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.header import io.ktor.client.request.header
@ -46,8 +43,6 @@ sealed class AuthResponse {
// data class MFARequested() : AuthResponse() // data class MFARequested() : AuthResponse()
} }
@Inject
@SingleIn(AppScope::class)
class AuthRepository( class AuthRepository(
private val settings: AppSettings, private val settings: AppSettings,
private val api: ApiClient, private val api: ApiClient,

View file

@ -1,10 +1,4 @@
package moe.lava.neon.core.repository 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 { class UserRepository {
} }

View file

@ -19,12 +19,13 @@ desugar = "2.1.5"
hcaptcha = "4.4.0" hcaptcha = "4.4.0"
junit = "4.13.2" junit = "4.13.2"
kermit = "2.0.8" kermit = "2.0.8"
koin-bom = "4.2.0-RC1"
koin-plugin = "0.3.0"
kotlin = "2.3.0" kotlin = "2.3.0"
kotlinx-coroutines = "1.10.2" kotlinx-coroutines = "1.10.2"
ktor = "3.4.0" ktor = "3.4.0"
material3 = "1.11.0-alpha02" material3 = "1.11.0-alpha02"
material3-adaptive = "1.3.0-alpha04" material3-adaptive = "1.3.0-alpha04"
metro = "0.10.2"
settings = "1.3.0" settings = "1.3.0"
sqldelight = "2.2.1" 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" } hcaptcha-compose = { module = "com.github.hCaptcha.hcaptcha-android-sdk:compose-sdk", version.ref = "hcaptcha" }
junit = { module = "junit:junit", version.ref = "junit" } junit = { module = "junit:junit", version.ref = "junit" }
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } 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 = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", 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" } 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-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", 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" } 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" } settings = { module = "com.russhwolf:multiplatform-settings-no-arg", version.ref = "settings" }
[plugins] [plugins]
@ -71,7 +77,7 @@ androidLibrary = { id = "com.android.library", version.ref = "agp" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "compose-hot-reload" } composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "compose-hot-reload" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } 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" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", 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" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }

View file

@ -8,7 +8,7 @@ plugins {
alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler) alias(libs.plugins.composeCompiler)
alias(libs.plugins.composeHotReload) alias(libs.plugins.composeHotReload)
alias(libs.plugins.metro) alias(libs.plugins.koinCompiler)
} }
kotlin { kotlin {
@ -54,8 +54,10 @@ kotlin {
implementation(libs.kermit) 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 { commonTest.dependencies {
implementation(libs.kotlin.test) implementation(libs.kotlin.test)

View file

@ -4,23 +4,23 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge 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.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() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge() enableEdgeToEdge()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
initKoin {
androidContext(this@MainActivity)
androidLogger()
}
setContent { setContent {
App() App()
} }
} }
} }
@Preview
@Composable
fun AppAndroidPreview() {
App()
}

View file

@ -5,7 +5,6 @@ import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.MotionScheme import androidx.compose.material3.MotionScheme
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.entryProvider
@ -14,12 +13,10 @@ import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay import androidx.navigation3.ui.NavDisplay
import androidx.savedstate.serialization.SavedStateConfiguration import androidx.savedstate.serialization.SavedStateConfiguration
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import dev.zacsweers.metro.createGraph
import dev.zacsweers.metrox.viewmodel.LocalMetroViewModelFactory
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic 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.Login
import moe.lava.neon.ui.screens.Sample import moe.lava.neon.ui.screens.Sample
import moe.lava.neon.ui.screens.chat.Chat 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.screens.navigator.NavigatorPreviewProvider
import moe.lava.neon.ui.util.ThreePaneSceneStrategy import moe.lava.neon.ui.util.ThreePaneSceneStrategy
import moe.lava.neon.ui.util.rememberThreePaneSceneStrategy import moe.lava.neon.ui.util.rememberThreePaneSceneStrategy
import org.koin.compose.koinInject
import kotlin.system.exitProcess import kotlin.system.exitProcess
object Route { object Route {
@ -68,81 +66,78 @@ fun App() {
exitProcess(1) exitProcess(1)
} }
val uiGraph = createGraph<AppUiGraph>() val auth: AuthRepository = koinInject()
val graph = uiGraph.core CaptchaBinder(koinInject())
CaptchaBinder(graph.api) MaterialExpressiveTheme(
CompositionLocalProvider(LocalMetroViewModelFactory provides uiGraph.metroViewModelFactory) { colorScheme = getColorScheme(),
MaterialExpressiveTheme( motionScheme = MotionScheme.expressive(),
colorScheme = getColorScheme(), ) {
motionScheme = MotionScheme.expressive(), val init = if (auth.token != null) Route.Sample else Route.Login
) { val backStack = rememberNavBackStack(config, Route.Sample)
val init = if (graph.auth.token != null) Route.Sample else Route.Login val threePaneStrategy = rememberThreePaneSceneStrategy<NavKey>()
val backStack = rememberNavBackStack(config, Route.Sample) NavDisplay(
val threePaneStrategy = rememberThreePaneSceneStrategy<NavKey>() backStack = backStack,
NavDisplay( entryDecorators = listOf(
backStack = backStack, rememberSaveableStateHolderNavEntryDecorator(),
entryDecorators = listOf( rememberViewModelStoreNavEntryDecorator(),
rememberSaveableStateHolderNavEntryDecorator(), ),
rememberViewModelStoreNavEntryDecorator(), onBack = { backStack.removeLastOrNull() },
), sceneStrategy = threePaneStrategy,
onBack = { backStack.removeLastOrNull() }, entryProvider = entryProvider {
sceneStrategy = threePaneStrategy, entry<Route.Login> {
entryProvider = entryProvider { Login(
entry<Route.Login> { onSuccess = {
Login( backStack.clear()
onSuccess = { backStack.add(Route.Sample)
backStack.clear()
backStack.add(Route.Sample)
}
)
}
entry<Route.Sample> {
Sample(
navTest = {
backStack.add(Route.Navigator(it))
backStack.add(Route.Chat)
backStack.add(Route.MembersList)
},
onRequestLogout = {
backStack.clear()
backStack.add(Route.Login)
}
)
}
entry<Route.Navigator>(
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<Route.Sample> {
Sample(
navTest = {
backStack.add(Route.Navigator(it))
backStack.add(Route.Chat)
backStack.add(Route.MembersList)
},
onRequestLogout = {
backStack.clear()
backStack.add(Route.Login)
}
)
}
entry<Route.Chat>( entry<Route.Navigator>(
metadata = ThreePaneSceneStrategy.detailPane() metadata = ThreePaneSceneStrategy.listPane()
) { ) { key ->
Chat( if (key.left) {
onOpenMembers = { backStack.add(Route.MembersList) } Navigator(
NavigatorPreviewProvider.base2.copy(
guildNavPosition = NavigatorModel.GuildNavPosition.LeftSidebar
)
)
} else {
Navigator(
NavigatorPreviewProvider.base2.copy(
guildNavPosition = NavigatorModel.GuildNavPosition.BottomSheet
)
) )
}
entry<Route.MembersList>(
metadata = ThreePaneSceneStrategy.extraPane()
) {
MembersList()
} }
} }
)
} entry<Route.Chat>(
metadata = ThreePaneSceneStrategy.detailPane()
) {
Chat(
onOpenMembers = { backStack.add(Route.MembersList) }
)
}
entry<Route.MembersList>(
metadata = ThreePaneSceneStrategy.extraPane()
) {
MembersList()
}
}
)
} }
} }

View file

@ -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<KClass<out ViewModel>, Provider<ViewModel>>,
override val assistedFactoryProviders: Map<KClass<out ViewModel>, Provider<ViewModelAssistedFactory>>,
override val manualAssistedFactoryProviders: Map<KClass<out ManualViewModelAssistedFactory>, Provider<ManualViewModelAssistedFactory>>,
) : MetroViewModelFactory()

View file

@ -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)
}
}

View file

@ -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<LoginViewModel>()
viewModel<SampleViewModel>()
}

View file

@ -23,11 +23,6 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import co.touchlab.kermit.Logger 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 kotlinx.coroutines.launch
import moe.lava.neon.core.repository.AuthRepository import moe.lava.neon.core.repository.AuthRepository
import moe.lava.neon.core.repository.AuthResponse 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
import moe.lava.neon.resources.visibility_off import moe.lava.neon.resources.visibility_off
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import org.koin.compose.viewmodel.koinViewModel
@Composable @Composable
fun Login( fun Login(
onSuccess: () -> Unit, onSuccess: () -> Unit,
) { ) {
val viewModel: LoginViewModel = metroViewModel() val viewModel: LoginViewModel = koinViewModel()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
Column( Column(
@ -113,9 +109,6 @@ fun Login(
} }
} }
@Inject
@ViewModelKey(LoginViewModel::class)
@ContributesIntoMap(AppScope::class)
class LoginViewModel( class LoginViewModel(
private val auth: AuthRepository private val auth: AuthRepository
) : ViewModel() { ) : ViewModel() {

View file

@ -20,11 +20,6 @@ import androidx.compose.ui.Modifier
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger 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 kotlinx.coroutines.launch
import moe.lava.neon.core.api.gateway.GatewayHandler import moe.lava.neon.core.api.gateway.GatewayHandler
import moe.lava.neon.core.repository.AuthRepository 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.resources.compose_multiplatform
import moe.lava.neon.ui.Greeting import moe.lava.neon.ui.Greeting
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import org.koin.compose.viewmodel.koinViewModel
@Composable @Composable
fun Sample( fun Sample(
navTest: (Boolean) -> Unit, navTest: (Boolean) -> Unit,
onRequestLogout: () -> Unit, onRequestLogout: () -> Unit,
) { ) {
val viewModel: SampleViewModel = metroViewModel() val viewModel: SampleViewModel = koinViewModel()
var showContent by remember { mutableStateOf(false) } var showContent by remember { mutableStateOf(false) }
Column( Column(
modifier = Modifier modifier = Modifier
@ -86,9 +82,6 @@ fun Sample(
} }
} }
@Inject
@ViewModelKey(SampleViewModel::class)
@ContributesIntoMap(AppScope::class)
class SampleViewModel( class SampleViewModel(
private val auth: AuthRepository, private val auth: AuthRepository,
private val gateway: GatewayHandler, private val gateway: GatewayHandler,

View file

@ -6,12 +6,16 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.window.Window import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application import androidx.compose.ui.window.application
import moe.lava.neon.ui.App 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 // The UI is designed with touchscreens in mind; on desktop elements may look gigantic
// So scale them down a bit // So scale them down a bit
const val scaleFactor = 0.75f const val scaleFactor = 0.75f
fun main() = application { fun main() = application {
initKoin {
printLogger()
}
Window( Window(
onCloseRequest = ::exitApplication, onCloseRequest = ::exitApplication,
title = "Neon", title = "Neon",