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 { 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 { androidTarget {
compilations.all { compilations.all {
compileTaskProvider { compileTaskProvider {
@@ -58,6 +66,9 @@ kotlin {
implementation(libs.multiplatformSettings) implementation(libs.multiplatformSettings)
implementation(libs.koin.core) implementation(libs.koin.core)
implementation(libs.koin.compose) 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 { commonTest.dependencies {
@@ -89,11 +100,11 @@ kotlin {
android { android {
namespace = "dev.meloda.overseerr" namespace = "dev.meloda.overseerr"
compileSdk = 35 compileSdk = 34
defaultConfig { defaultConfig {
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 34
applicationId = "dev.meloda.overseerr.androidApp" applicationId = "dev.meloda.overseerr.androidApp"
versionCode = 1 versionCode = 1
@@ -2,9 +2,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application <application
android:name=".common.AppGlobal"
android:icon="@android:drawable/ic_menu_compass" android:icon="@android:drawable/ic_menu_compass"
android:label="Overseerr" android:label="Overseerr"
android:theme="@android:style/Theme.Material.NoActionBar"> android:theme="@android:style/Theme.Material.NoActionBar">
<activity <activity
android:name=".AppActivity" android:name=".AppActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
@@ -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 package dev.meloda.overseerr
import androidx.compose.animation.core.*
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.* 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.Alignment
import androidx.compose.ui.Modifier 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 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.AppTheme
import dev.meloda.overseerr.theme.LocalThemeIsDark import org.koin.compose.KoinContext
import kotlinx.coroutines.isActive import org.koin.compose.koinInject
import org.jetbrains.compose.resources.Font
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.resources.vectorResource
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
internal fun App() = AppTheme { internal fun App() = KoinContext {
Column( val platform: Platform = koinInject()
modifier = Modifier
.fillMaxSize() AppTheme {
.windowInsetsPadding(WindowInsets.safeDrawing) var loginValue by rememberSaveable {
.padding(16.dp), mutableStateOf("")
horizontalAlignment = Alignment.CenterHorizontally }
) { var passwordValue by rememberSaveable {
mutableStateOf("")
}
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = "Log in")
}
)
Text( Text(
text = stringResource(Res.string.cyclone), text = "Current platform: ${platform.name}",
fontFamily = FontFamily(Font(Res.font.IndieFlower_Regular)),
style = MaterialTheme.typography.displayLarge style = MaterialTheme.typography.displayLarge
) )
var isRotating by remember { mutableStateOf(false) }
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)
} }
} ) { padding ->
} Column(
modifier = Modifier.fillMaxSize()
Image( .padding(padding),
modifier = Modifier horizontalAlignment = Alignment.CenterHorizontally
.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)) 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")
}
}
} }
} }
} }
@@ -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 androidx.compose.ui.window.ComposeUIViewController
import dev.meloda.overseerr.App import dev.meloda.overseerr.App
import dev.meloda.overseerr.di.appModule
import org.koin.core.context.startKoin
import platform.UIKit.UIViewController 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"
}
+7 -1
View File
@@ -2,10 +2,16 @@ import androidx.compose.ui.unit.dp
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 androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.window.rememberWindowState
import java.awt.Dimension
import dev.meloda.overseerr.App import dev.meloda.overseerr.App
import dev.meloda.overseerr.di.appModule
import org.koin.core.context.startKoin
import java.awt.Dimension
fun main() = application { fun main() = application {
startKoin {
modules(appModule)
}
Window( Window(
title = "Overseerr", title = "Overseerr",
state = rememberWindowState(width = 800.dp, height = 600.dp), state = rememberWindowState(width = 800.dp, height = 600.dp),
+1
View File
@@ -9,6 +9,7 @@ org.gradle.parallel=true
kotlin.code.style=official kotlin.code.style=official
kotlin.js.compiler=ir kotlin.js.compiler=ir
kotlin.daemon.jvmargs=-Xmx4G kotlin.daemon.jvmargs=-Xmx4G
kotlin.native.ignoreDisabledTargets=true
#Android #Android
android.useAndroidX=true android.useAndroidX=true
+3 -1
View File
@@ -14,6 +14,9 @@ pluginManagement {
mavenCentral() mavenCentral()
} }
} }
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}
dependencyResolutionManagement { dependencyResolutionManagement {
repositories { repositories {
@@ -31,4 +34,3 @@ dependencyResolutionManagement {
} }
} }
include(":composeApp") include(":composeApp")