Simple login screen & Koin DI integration

This commit is contained in:
2024-08-05 01:43:36 +03:00
parent ddca42e38e
commit bf6b55aab9
16 changed files with 170 additions and 92 deletions
+13 -2
View File
@@ -14,6 +14,14 @@ plugins {
}
kotlin {
// export correct artifact to use all classes of library directly from Swift
targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget::class.java).all {
binaries.withType(org.jetbrains.kotlin.gradle.plugin.mpp.Framework::class.java).all {
export("dev.icerock.moko:mvvm-core:0.16.1")
export("dev.icerock.moko:mvvm-state:0.16.1")
}
}
androidTarget {
compilations.all {
compileTaskProvider {
@@ -58,6 +66,9 @@ kotlin {
implementation(libs.multiplatformSettings)
implementation(libs.koin.core)
implementation(libs.koin.compose)
api("dev.icerock.moko:mvvm-compose:0.16.1")
api("dev.icerock.moko:mvvm-flow-compose:0.16.1")
}
commonTest.dependencies {
@@ -89,11 +100,11 @@ kotlin {
android {
namespace = "dev.meloda.overseerr"
compileSdk = 35
compileSdk = 34
defaultConfig {
minSdk = 26
targetSdk = 35
targetSdk = 34
applicationId = "dev.meloda.overseerr.androidApp"
versionCode = 1
@@ -2,9 +2,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".common.AppGlobal"
android:icon="@android:drawable/ic_menu_compass"
android:label="Overseerr"
android:theme="@android:style/Theme.Material.NoActionBar">
<activity
android:name=".AppActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
@@ -12,10 +14,10 @@
android:windowSoftInputMode="adjustPan"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
</manifest>
@@ -0,0 +1,16 @@
package dev.meloda.overseerr.common
import android.app.Application
import dev.meloda.overseerr.di.appModule
import org.koin.core.context.startKoin
class AppGlobal : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
modules(appModule)
}
}
}
@@ -0,0 +1,6 @@
package dev.meloda.overseerr.model
actual class Platform actual constructor() {
actual val name: String
get() = "Android"
}
@@ -1,100 +1,78 @@
package dev.meloda.overseerr
import androidx.compose.animation.core.*
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import overseerr.composeapp.generated.resources.*
import dev.meloda.overseerr.model.Platform
import dev.meloda.overseerr.theme.AppTheme
import dev.meloda.overseerr.theme.LocalThemeIsDark
import kotlinx.coroutines.isActive
import org.jetbrains.compose.resources.Font
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.resources.vectorResource
import org.koin.compose.KoinContext
import org.koin.compose.koinInject
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun App() = AppTheme {
Column(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.safeDrawing)
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(Res.string.cyclone),
fontFamily = FontFamily(Font(Res.font.IndieFlower_Regular)),
style = MaterialTheme.typography.displayLarge
)
internal fun App() = KoinContext {
val platform: Platform = koinInject()
var isRotating by remember { mutableStateOf(false) }
AppTheme {
var loginValue by rememberSaveable {
mutableStateOf("")
}
var passwordValue by rememberSaveable {
mutableStateOf("")
}
val rotate = remember { Animatable(0f) }
val target = 360f
if (isRotating) {
LaunchedEffect(Unit) {
while (isActive) {
val remaining = (target - rotate.value) / target
rotate.animateTo(target, animationSpec = tween((1_000 * remaining).toInt(), easing = LinearEasing))
rotate.snapTo(0f)
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = "Log in")
}
)
Text(
text = "Current platform: ${platform.name}",
style = MaterialTheme.typography.displayLarge
)
}
) { padding ->
Column(
modifier = Modifier.fillMaxSize()
.padding(padding),
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
modifier = Modifier.fillMaxWidth(0.9f),
value = loginValue,
onValueChange = { newText -> loginValue = newText },
placeholder = { Text(text = "Login") }
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
modifier = Modifier.fillMaxWidth(0.9f),
value = passwordValue,
onValueChange = { newText -> passwordValue = newText },
placeholder = { Text(text = "Password") }
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
}
) {
Text(text = "Authorize")
}
}
}
Image(
modifier = Modifier
.size(250.dp)
.padding(16.dp)
.run { rotate(rotate.value) },
imageVector = vectorResource(Res.drawable.ic_cyclone),
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
contentDescription = null
)
ElevatedButton(
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 4.dp)
.widthIn(min = 200.dp),
onClick = { isRotating = !isRotating },
content = {
Icon(vectorResource(Res.drawable.ic_rotate_right), contentDescription = null)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(
stringResource(if (isRotating) Res.string.stop else Res.string.run)
)
}
)
var isDark by LocalThemeIsDark.current
val icon = remember(isDark) {
if (isDark) Res.drawable.ic_light_mode
else Res.drawable.ic_dark_mode
}
ElevatedButton(
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp).widthIn(min = 200.dp),
onClick = { isDark = !isDark },
content = {
Icon(vectorResource(icon), contentDescription = null)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(stringResource(Res.string.theme))
}
)
val uriHandler = LocalUriHandler.current
TextButton(
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp).widthIn(min = 200.dp),
onClick = { uriHandler.openUri("https://github.com/terrakok") },
) {
Text(stringResource(Res.string.open_github))
}
}
}
@@ -0,0 +1,9 @@
package dev.meloda.overseerr.di
import dev.meloda.overseerr.model.Platform
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
val appModule = module {
singleOf(::Platform)
}
@@ -0,0 +1,9 @@
package dev.meloda.overseerr.login
import dev.icerock.moko.mvvm.viewmodel.ViewModel
class LoginViewModel : ViewModel() {
}
@@ -0,0 +1,4 @@
package dev.meloda.overseerr.login.model
class LoginScreenState {
}
@@ -0,0 +1,10 @@
package dev.meloda.overseerr.login.presentation
import androidx.compose.runtime.Composable
import dev.icerock.moko.mvvm.compose.getViewModel
import dev.meloda.overseerr.login.LoginViewModel
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
}
@@ -0,0 +1,5 @@
package dev.meloda.overseerr.model
expect class Platform() {
val name: String
}
@@ -0,0 +1,6 @@
package dev.meloda.overseerr.model
actual class Platform actual constructor() {
actual val name: String
get() = "iOS"
}
+8 -1
View File
@@ -1,5 +1,12 @@
import androidx.compose.ui.window.ComposeUIViewController
import dev.meloda.overseerr.App
import dev.meloda.overseerr.di.appModule
import org.koin.core.context.startKoin
import platform.UIKit.UIViewController
fun MainViewController(): UIViewController = ComposeUIViewController { App() }
fun MainViewController(): UIViewController = ComposeUIViewController {
startKoin {
modules(appModule)
}
App()
}
@@ -0,0 +1,6 @@
package dev.meloda.overseerr.model
actual class Platform actual constructor() {
actual val name: String
get() = "JVM"
}
+8 -2
View File
@@ -2,10 +2,16 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import java.awt.Dimension
import dev.meloda.overseerr.App
import dev.meloda.overseerr.di.appModule
import org.koin.core.context.startKoin
import java.awt.Dimension
fun main() = application {
startKoin {
modules(appModule)
}
Window(
title = "Overseerr",
state = rememberWindowState(width = 800.dp, height = 600.dp),
@@ -14,4 +20,4 @@ fun main() = application {
window.minimumSize = Dimension(350, 600)
App()
}
}
}
+1
View File
@@ -9,6 +9,7 @@ org.gradle.parallel=true
kotlin.code.style=official
kotlin.js.compiler=ir
kotlin.daemon.jvmargs=-Xmx4G
kotlin.native.ignoreDisabledTargets=true
#Android
android.useAndroidX=true
+3 -1
View File
@@ -14,6 +14,9 @@ pluginManagement {
mavenCentral()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}
dependencyResolutionManagement {
repositories {
@@ -31,4 +34,3 @@ dependencyResolutionManagement {
}
}
include(":composeApp")