feat: basic repo + di
This commit is contained in:
parent
1b3b465112
commit
4d8872db9c
10 changed files with 185 additions and 22 deletions
|
|
@ -40,6 +40,10 @@ kotlin {
|
||||||
implementation(libs.androidx.nav3.ui)
|
implementation(libs.androidx.nav3.ui)
|
||||||
implementation(libs.compose.material3.adaptive)
|
implementation(libs.compose.material3.adaptive)
|
||||||
implementation(libs.compose.material3.adaptive.nav3)
|
implementation(libs.compose.material3.adaptive.nav3)
|
||||||
|
|
||||||
|
implementation(libs.metrox.viewmodel.compose)
|
||||||
|
|
||||||
|
implementation(libs.kermit)
|
||||||
}
|
}
|
||||||
commonTest.dependencies {
|
commonTest.dependencies {
|
||||||
implementation(libs.kotlin.test)
|
implementation(libs.kotlin.test)
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,12 @@ 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.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.navigation3.ui.defaultPredictivePopTransitionSpec
|
import androidx.navigation3.ui.defaultPredictivePopTransitionSpec
|
||||||
|
import dev.zacsweers.metro.Inject
|
||||||
|
import dev.zacsweers.metrox.viewmodel.LocalMetroViewModelFactory
|
||||||
|
import dev.zacsweers.metrox.viewmodel.MetroViewModelFactory
|
||||||
import moe.lava.neon.ui.App
|
import moe.lava.neon.ui.App
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,21 @@ package moe.lava.neon.ui
|
||||||
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
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.NavKey
|
||||||
import androidx.navigation3.runtime.entryProvider
|
import androidx.navigation3.runtime.entryProvider
|
||||||
import androidx.navigation3.runtime.rememberNavBackStack
|
import androidx.navigation3.runtime.rememberNavBackStack
|
||||||
|
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 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.core.di.AppGraph
|
||||||
|
import moe.lava.neon.ui.di.AppUiGraph
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -18,7 +25,7 @@ private object Route {
|
||||||
data object Login : NavKey
|
data object Login : NavKey
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Sample(val token: String) : NavKey
|
data object Sample : NavKey
|
||||||
}
|
}
|
||||||
|
|
||||||
private val config = SavedStateConfiguration {
|
private val config = SavedStateConfiguration {
|
||||||
|
|
@ -30,25 +37,36 @@ private val config = SavedStateConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun App() {
|
fun App() {
|
||||||
MaterialTheme {
|
val uiGraph = createGraph<AppUiGraph>()
|
||||||
val backStack = rememberNavBackStack(config, Route.Login)
|
val graph = uiGraph.core
|
||||||
NavDisplay(
|
CompositionLocalProvider(LocalMetroViewModelFactory provides uiGraph.metroViewModelFactory) {
|
||||||
backStack = backStack,
|
MaterialTheme {
|
||||||
onBack = { backStack.removeLastOrNull() },
|
val init = if (graph.auth.token != null) Route.Sample else Route.Login
|
||||||
entryProvider = entryProvider {
|
val backStack = rememberNavBackStack(config, init)
|
||||||
entry<Route.Login> {
|
NavDisplay(
|
||||||
Login(
|
backStack = backStack,
|
||||||
onSuccess = { token ->
|
entryDecorators = listOf(
|
||||||
backStack.add(Route.Sample(token))
|
rememberSaveableStateHolderNavEntryDecorator(),
|
||||||
}
|
rememberViewModelStoreNavEntryDecorator(),
|
||||||
)
|
),
|
||||||
|
onBack = { backStack.removeLastOrNull() },
|
||||||
|
entryProvider = entryProvider {
|
||||||
|
entry<Route.Login> {
|
||||||
|
Login(
|
||||||
|
onSuccess = {
|
||||||
|
backStack.clear()
|
||||||
|
backStack.add(Route.Sample)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
entry<Route.Sample> { key ->
|
||||||
|
Sample()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
entry<Route.Sample> { key ->
|
)
|
||||||
Sample(key.token)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
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()
|
||||||
|
|
@ -9,14 +9,28 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import co.touchlab.kermit.Logger
|
||||||
|
import dev.zacsweers.metro.AppScope
|
||||||
|
import dev.zacsweers.metro.ContributesIntoMap
|
||||||
|
import dev.zacsweers.metro.ContributesTo
|
||||||
|
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
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Login(
|
fun Login(
|
||||||
onSuccess: (token: String) -> Unit,
|
onSuccess: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val viewModel: LoginViewModel = metroViewModel()
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
// .background(MaterialTheme.colorScheme.primaryContainer)
|
// .background(MaterialTheme.colorScheme.primaryContainer)
|
||||||
|
|
@ -31,8 +45,37 @@ fun Login(
|
||||||
onValueChange = { token = it },
|
onValueChange = { token = it },
|
||||||
label = { Text("Enter token") },
|
label = { Text("Enter token") },
|
||||||
)
|
)
|
||||||
Button(onClick = { onSuccess(token) }) {
|
Button(onClick = {
|
||||||
|
scope.launch {
|
||||||
|
val res = viewModel.login(token)
|
||||||
|
when (res) {
|
||||||
|
LoginViewModel.LoginResult.Failed -> {}
|
||||||
|
LoginViewModel.LoginResult.Success -> onSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
Text("Submit")
|
Text("Submit")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@ViewModelKey(LoginViewModel::class)
|
||||||
|
@ContributesIntoMap(AppScope::class)
|
||||||
|
class LoginViewModel(
|
||||||
|
private val auth: AuthRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
sealed interface LoginResult {
|
||||||
|
data object Failed : LoginResult
|
||||||
|
data object Success : LoginResult
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun login(token: String): LoginResult {
|
||||||
|
return try {
|
||||||
|
auth.login(token)
|
||||||
|
LoginResult.Success
|
||||||
|
} catch(_: Throwable) {
|
||||||
|
LoginResult.Failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,23 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import co.touchlab.kermit.Logger
|
||||||
|
import dev.zacsweers.metro.AppScope
|
||||||
|
import dev.zacsweers.metro.ContributesIntoMap
|
||||||
|
import dev.zacsweers.metro.ContributesTo
|
||||||
|
import dev.zacsweers.metro.Inject
|
||||||
|
import dev.zacsweers.metrox.viewmodel.ViewModelKey
|
||||||
|
import dev.zacsweers.metrox.viewmodel.metroViewModel
|
||||||
|
import moe.lava.neon.core.repository.AuthRepository
|
||||||
import moe.lava.neon.resources.Res
|
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
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Sample(token: String) {
|
fun Sample() {
|
||||||
|
val viewModel: SampleViewModel = metroViewModel()
|
||||||
var showContent by remember { mutableStateOf(false) }
|
var showContent by remember { mutableStateOf(false) }
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -43,8 +53,17 @@ fun Sample(token: String) {
|
||||||
) {
|
) {
|
||||||
Image(painterResource(Res.drawable.compose_multiplatform), null)
|
Image(painterResource(Res.drawable.compose_multiplatform), null)
|
||||||
Text("Compose: $greeting")
|
Text("Compose: $greeting")
|
||||||
Text("Passed token: $token")
|
Text("Passed token: ${viewModel.token}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@ViewModelKey(SampleViewModel::class)
|
||||||
|
@ContributesIntoMap(AppScope::class)
|
||||||
|
class SampleViewModel(
|
||||||
|
private val auth: AuthRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
val token get() = auth.token
|
||||||
|
}
|
||||||
|
|
|
||||||
14
core/src/commonMain/kotlin/moe/lava/neon/core/di/AppGraph.kt
Normal file
14
core/src/commonMain/kotlin/moe/lava/neon/core/di/AppGraph.kt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package moe.lava.neon.core.di
|
||||||
|
|
||||||
|
import dev.zacsweers.metro.AppScope
|
||||||
|
import dev.zacsweers.metro.DependencyGraph
|
||||||
|
import dev.zacsweers.metro.GraphExtension
|
||||||
|
import dev.zacsweers.metro.SingleIn
|
||||||
|
import moe.lava.neon.core.repository.AuthRepository
|
||||||
|
import moe.lava.neon.core.repository.UserRepository
|
||||||
|
|
||||||
|
@GraphExtension
|
||||||
|
interface AppGraph {
|
||||||
|
val auth: AuthRepository
|
||||||
|
val users: UserRepository
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
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 AuthRepository {
|
||||||
|
var token: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun login(username: String, password: String) {
|
||||||
|
// api.login(username, password)
|
||||||
|
}
|
||||||
|
suspend fun login(token: String) {
|
||||||
|
this.token = token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
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 {
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ androidx-testExt = "1.3.0"
|
||||||
composeHotReload = "1.0.0"
|
composeHotReload = "1.0.0"
|
||||||
composeMultiplatform = "1.10.0"
|
composeMultiplatform = "1.10.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
|
kermit = "2.0.8"
|
||||||
kotlin = "2.3.0"
|
kotlin = "2.3.0"
|
||||||
kotlinx-coroutines = "1.10.2"
|
kotlinx-coroutines = "1.10.2"
|
||||||
material3 = "1.10.0-alpha05"
|
material3 = "1.10.0-alpha05"
|
||||||
|
|
@ -21,6 +22,7 @@ material3-adaptive = "1.3.0-alpha03"
|
||||||
metro = "0.10.0"
|
metro = "0.10.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
|
||||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||||
kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||||
junit = { module = "junit:junit", version.ref = "junit" }
|
junit = { module = "junit:junit", version.ref = "junit" }
|
||||||
|
|
@ -43,6 +45,7 @@ compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "composeMul
|
||||||
compose-components-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "composeMultiplatform" }
|
compose-components-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "composeMultiplatform" }
|
||||||
compose-uiToolingPreview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "composeMultiplatform" }
|
compose-uiToolingPreview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "composeMultiplatform" }
|
||||||
kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
||||||
|
metrox-viewmodel-compose = { module = "dev.zacsweers.metro:metrox-viewmodel-compose", version.ref = "metro" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue