refactor: composeApp -> ui
103
ui/build.gradle.kts
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
alias(libs.plugins.androidApplication)
|
||||
alias(libs.plugins.composeMultiplatform)
|
||||
alias(libs.plugins.composeCompiler)
|
||||
alias(libs.plugins.composeHotReload)
|
||||
alias(libs.plugins.metro)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
androidTarget {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
androidMain.dependencies {
|
||||
implementation(libs.compose.uiToolingPreview)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
}
|
||||
commonMain.dependencies {
|
||||
implementation(project(":core"))
|
||||
implementation(libs.compose.runtime)
|
||||
implementation(libs.compose.foundation)
|
||||
implementation(libs.compose.material3)
|
||||
implementation(libs.compose.ui)
|
||||
implementation(libs.compose.components.resources)
|
||||
implementation(libs.compose.uiToolingPreview)
|
||||
implementation(libs.androidx.lifecycle.viewmodelCompose)
|
||||
implementation(libs.androidx.lifecycle.runtimeCompose)
|
||||
|
||||
implementation(libs.androidx.lifecycle.viewmodel.nav3)
|
||||
implementation(libs.androidx.nav3.ui)
|
||||
implementation(libs.compose.material3.adaptive)
|
||||
implementation(libs.compose.material3.adaptive.nav3)
|
||||
|
||||
implementation(libs.metrox.viewmodel.compose)
|
||||
|
||||
implementation(libs.kermit)
|
||||
}
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
jvmMain.dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(libs.kotlinx.coroutinesSwing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "moe.lava.neon"
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "moe.lava.neon"
|
||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
debugImplementation(libs.compose.uiTooling)
|
||||
}
|
||||
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "moe.lava.neon.MainKt"
|
||||
|
||||
nativeDistributions {
|
||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||
packageName = "moe.lava.neon"
|
||||
packageVersion = "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compose.resources {
|
||||
packageOfResClass = "moe.lava.neon.resources"
|
||||
}
|
||||
22
ui/src/androidMain/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
||||
<activity
|
||||
android:exported="true"
|
||||
android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
31
ui/src/androidMain/kotlin/moe/lava/neon/MainActivity.kt
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package moe.lava.neon
|
||||
|
||||
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.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
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
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContent {
|
||||
App()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun AppAndroidPreview() {
|
||||
App()
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
170
ui/src/androidMain/res/drawable/ic_launcher_background.xml
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
5
ui/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
BIN
ui/src/androidMain/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
ui/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
ui/src/androidMain/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
ui/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
ui/src/androidMain/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
ui/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
ui/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
ui/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
ui/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
ui/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
3
ui/src/androidMain/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">Neon</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="450dp"
|
||||
android:height="450dp"
|
||||
android:viewportWidth="64"
|
||||
android:viewportHeight="64">
|
||||
<path
|
||||
android:pathData="M56.25,18V46L32,60 7.75,46V18L32,4Z"
|
||||
android:fillColor="#6075f2"/>
|
||||
<path
|
||||
android:pathData="m41.5,26.5v11L32,43V60L56.25,46V18Z"
|
||||
android:fillColor="#6b57ff"/>
|
||||
<path
|
||||
android:pathData="m32,43 l-9.5,-5.5v-11L7.75,18V46L32,60Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:centerX="23.131"
|
||||
android:centerY="18.441"
|
||||
android:gradientRadius="42.132"
|
||||
android:type="radial">
|
||||
<item android:offset="0" android:color="#FF5383EC"/>
|
||||
<item android:offset="0.867" android:color="#FF7F52FF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M22.5,26.5 L32,21 41.5,26.5 56.25,18 32,4 7.75,18Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="44.172"
|
||||
android:startY="4.377"
|
||||
android:endX="17.973"
|
||||
android:endY="34.035"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF33C3FF"/>
|
||||
<item android:offset="0.878" android:color="#FF5383EC"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m32,21 l9.526,5.5v11L32,43 22.474,37.5v-11z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
72
ui/src/commonMain/kotlin/moe/lava/neon/ui/App.kt
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package moe.lava.neon.ui
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
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
|
||||
import androidx.navigation3.runtime.rememberNavBackStack
|
||||
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
||||
import androidx.navigation3.ui.NavDisplay
|
||||
import androidx.savedstate.serialization.SavedStateConfiguration
|
||||
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.core.di.AppGraph
|
||||
import moe.lava.neon.ui.di.AppUiGraph
|
||||
import moe.lava.neon.ui.screens.Login
|
||||
import moe.lava.neon.ui.screens.Sample
|
||||
|
||||
private object Route {
|
||||
@Serializable
|
||||
data object Login : NavKey
|
||||
|
||||
@Serializable
|
||||
data object Sample : NavKey
|
||||
}
|
||||
|
||||
private val config = SavedStateConfiguration {
|
||||
serializersModule = SerializersModule {
|
||||
polymorphic(NavKey::class) {
|
||||
subclass(Route.Login::class, Route.Login.serializer())
|
||||
subclass(Route.Sample::class, Route.Sample.serializer())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun App() {
|
||||
val uiGraph = createGraph<AppUiGraph>()
|
||||
val graph = uiGraph.core
|
||||
CompositionLocalProvider(LocalMetroViewModelFactory provides uiGraph.metroViewModelFactory) {
|
||||
MaterialTheme {
|
||||
val init = if (graph.auth.token != null) Route.Sample else Route.Login
|
||||
val backStack = rememberNavBackStack(config, init)
|
||||
NavDisplay(
|
||||
backStack = backStack,
|
||||
entryDecorators = listOf(
|
||||
rememberSaveableStateHolderNavEntryDecorator(),
|
||||
rememberViewModelStoreNavEntryDecorator(),
|
||||
),
|
||||
onBack = { backStack.removeLastOrNull() },
|
||||
entryProvider = entryProvider {
|
||||
entry<Route.Login> {
|
||||
Login(
|
||||
onSuccess = {
|
||||
backStack.clear()
|
||||
backStack.add(Route.Sample)
|
||||
}
|
||||
)
|
||||
}
|
||||
entry<Route.Sample> { key ->
|
||||
Sample()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
11
ui/src/commonMain/kotlin/moe/lava/neon/ui/Greeting.kt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package moe.lava.neon.ui
|
||||
|
||||
import moe.lava.neon.core.getPlatform
|
||||
|
||||
class Greeting {
|
||||
private val platform = getPlatform()
|
||||
|
||||
fun greet(): String {
|
||||
return "Hello, ${platform.name}!"
|
||||
}
|
||||
}
|
||||
29
ui/src/commonMain/kotlin/moe/lava/neon/ui/di/AppUiGraph.kt
Normal file
|
|
@ -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()
|
||||
81
ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Login.kt
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package moe.lava.neon.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.safeContentPadding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
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
|
||||
fun Login(
|
||||
onSuccess: () -> Unit,
|
||||
) {
|
||||
val viewModel: LoginViewModel = metroViewModel()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
// .background(MaterialTheme.colorScheme.primaryContainer)
|
||||
.safeContentPadding()
|
||||
.fillMaxSize()
|
||||
) {
|
||||
Text("Login!")
|
||||
|
||||
var token by rememberSaveable { mutableStateOf("") }
|
||||
OutlinedTextField(
|
||||
value = token,
|
||||
onValueChange = { token = it },
|
||||
label = { Text("Enter token") },
|
||||
)
|
||||
Button(onClick = {
|
||||
scope.launch {
|
||||
val res = viewModel.login(token)
|
||||
when (res) {
|
||||
LoginViewModel.LoginResult.Failed -> {}
|
||||
LoginViewModel.LoginResult.Success -> onSuccess()
|
||||
}
|
||||
}
|
||||
}) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
69
ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Sample.kt
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package moe.lava.neon.ui.screens
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.safeContentPadding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
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.compose_multiplatform
|
||||
import moe.lava.neon.ui.Greeting
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
|
||||
@Composable
|
||||
fun Sample() {
|
||||
val viewModel: SampleViewModel = metroViewModel()
|
||||
var showContent by remember { mutableStateOf(false) }
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||
.safeContentPadding()
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Button(onClick = { showContent = !showContent }) {
|
||||
Text("Click me!")
|
||||
}
|
||||
AnimatedVisibility(showContent) {
|
||||
val greeting = remember { Greeting().greet() }
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Image(painterResource(Res.drawable.compose_multiplatform), null)
|
||||
Text("Compose: $greeting")
|
||||
Text("Passed token: ${viewModel.token}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject
|
||||
@ViewModelKey(SampleViewModel::class)
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
class SampleViewModel(
|
||||
private val auth: AuthRepository
|
||||
) : ViewModel() {
|
||||
val token get() = auth.token
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package moe.lava.neon
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ComposeAppCommonTest {
|
||||
|
||||
@Test
|
||||
fun example() {
|
||||
assertEquals(3, 1 + 2)
|
||||
}
|
||||
}
|
||||
14
ui/src/jvmMain/kotlin/moe/lava/neon/main.kt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package moe.lava.neon
|
||||
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import moe.lava.neon.ui.App
|
||||
|
||||
fun main() = application {
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
title = "Neon",
|
||||
) {
|
||||
App()
|
||||
}
|
||||
}
|
||||