diff --git a/build.gradle.kts b/build.gradle.kts index fa2d427..b682898 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,6 +8,6 @@ plugins { alias(libs.plugins.kotlinJvm) apply false alias(libs.plugins.kotlinMultiplatform) apply false kotlin("plugin.serialization") version "1.9.22" apply false - + } diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index ed86da3..f02160c 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -4,11 +4,17 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig +val lifecycle_version = "2.6.1" +val arch_version = "2.2.0" + plugins { + alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidApplication) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) + + } kotlin { @@ -66,10 +72,19 @@ kotlin { implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) + implementation(libs.androidx.lifecycle.livedata.core) implementation(libs.androidx.lifecycle.viewmodel) implementation(libs.androidx.lifecycle.runtime.compose) + implementation(libs.lifecycle.runtime.compose) + implementation(libs.lifecycle.viewmodel.compose) + implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(projects.shared) - + implementation(project.dependencies.platform(libs.koin.bom)) + implementation(libs.koin.core) + implementation(libs.koin.compose) + implementation(libs.koin.compose.viewmodel) + + } desktopMain.dependencies { implementation(compose.desktop.currentOs) @@ -106,6 +121,7 @@ android { } dependencies { + implementation(libs.androidx.lifecycle.livedata.core.ktx) debugImplementation(compose.uiTooling) } diff --git a/composeApp/src/commonMain/kotlin/org/neutrino/ktans/App.kt b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/App.kt index e1335bc..ac7668f 100644 --- a/composeApp/src/commonMain/kotlin/org/neutrino/ktans/App.kt +++ b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/App.kt @@ -29,76 +29,23 @@ import service.TermService import service.TermServiceImpl import service.SearchType import service.DatabaseFactory +import org.koin.compose.KoinApplication +import org.koin.compose.koinInject +import org.koin.core.parameter.parametersOf +import org.neutrino.ktans.di.appModule +import org.koin.compose.viewmodel.dsl.viewModelOf +import org.neutrino.ktans.view.MainView -@Composable -fun SearchBarTextField() { - val query = remember { mutableStateOf("") } - val termService = TermServiceImpl() - var showResults by remember { mutableStateOf(false) } - var terms: List? = null - - TextField( - value = query.value, - onValueChange = { - query.value = it - var sType = SearchType.START - sType = if (query.value.length > 2) { - SearchType.START - } else { - SearchType.EXACT - } - - val translation = getTranslationForLanguages("an","sl") ?: return@TextField - println(translation) - terms = termService.getTranslationForTerm(it,translation,sType) - showResults = terms != null - println(terms) - - }, - placeholder = { Text("Prelož...") }, - singleLine = true, - leadingIcon = { Icon(Icons.Filled.Search, contentDescription = "Search Icon") }, - trailingIcon = { - if (query.value.isNotEmpty()) { - IconButton(onClick = { query.value = "" }) { - Icon(Icons.Filled.Clear, contentDescription = "Clear Text") - } - } - }, - modifier = Modifier - .fillMaxWidth() - ) - - Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { - if (showResults && terms != null) { - for (term in terms!!) { - Text(term.string1) - Text("${term.string2},") - } - } - } -} @Composable @Preview fun App() { - DatabaseFactory.connectAndMigrate() - DatabaseFactory.connectAll() - - MaterialTheme { - var showContent by remember { mutableStateOf(false) } - Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { - SearchBarTextField() - Button(onClick = { showContent = !showContent }) { - Text("Click me!") - } - AnimatedVisibility(showContent) { - val greeting = remember { Greeting().greet() } - Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { - Image(painterResource(Res.drawable.compose_multiplatform), null) - Text("Compose: $greeting") - } - } + KoinApplication(application = { + modules(appModule()) + }) { + MaterialTheme { + MainView() } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/neutrino/ktans/di/Koin.kt b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/di/Koin.kt new file mode 100644 index 0000000..36ba7a8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/di/Koin.kt @@ -0,0 +1,14 @@ +package org.neutrino.ktans.di + +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import service.TermServiceImpl +import service.DatabaseFactory +import org.koin.compose.viewmodel.dsl.viewModelOf +import org.neutrino.ktans.viewmodel.MainModelView +import org.koin.core.module.dsl.factoryOf + +fun appModule() = module { + singleOf(::TermServiceImpl) + viewModelOf(::MainModelView) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/neutrino/ktans/view/MainView.kt b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/view/MainView.kt new file mode 100644 index 0000000..0544907 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/view/MainView.kt @@ -0,0 +1,114 @@ +package org.neutrino.ktans.view + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.foundation.Image +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.material.TextField +import androidx.compose.material.IconButton +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.filled.Clear +import androidx.compose.runtime.Composable +import kotlinx.coroutines.flow.observeOn +import org.koin.compose.koinInject +import org.neutrino.ktans.viewmodel.MainModelView +import org.neutrino.ktans.viewmodel.Status +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.lifecycle.viewmodel.compose.viewModel + +@Composable +fun RowScope.TableCell( + text: String, + weight: Float +) { + Text( + text = text, + Modifier + .border(1.dp, Color.Black) + .weight(weight) + .padding(8.dp) + ) +} + + +@Composable +fun SearchBarTextField(viewModel: MainModelView) { + val query = remember { mutableStateOf("") } + Column { + TextField( + value = query.value, + onValueChange = { + query.value = it + viewModel.getTerms(query.value) + }, + placeholder = { Text("Prelož...") }, + singleLine = true, + leadingIcon = { Icon(Icons.Filled.Search, contentDescription = "Search Icon") }, + trailingIcon = { + if (query.value.isNotEmpty()) { + IconButton(onClick = { + query.value = "" + viewModel.clearSearch() + }) { + Icon(Icons.Filled.Clear, contentDescription = "Clear Text") + } + } + }, + modifier = Modifier + .fillMaxWidth() + ) + + Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { + val terms by viewModel.terms.collectAsState() + + if (terms != null) { + SelectionContainer { + Column { + Row(Modifier.background(Color.Gray)) { + TableCell(text = "Anglicky", weight = .5f) + TableCell(text = "Slovensky", weight = .5f) + } + val column1Weight = .5f // 50% + val column2Weight = .5f // 50% + // The LazyColumn will be our table. Notice the use of the weights below + LazyColumn(Modifier.fillMaxSize().padding(0.dp)) { + // Here is the header + + items(items = terms!!, itemContent = { t -> + + Row(Modifier.fillMaxWidth()) { + TableCell(text = t.string1, weight = column1Weight) + TableCell(text = t.string2, weight = column2Weight) + } + + + }) + } + } + } + + } + } + } +} + + + +@Composable +fun MainView(viewModel: MainModelView = koinInject(),) { + SearchBarTextField(viewModel) +} + diff --git a/composeApp/src/commonMain/kotlin/org/neutrino/ktans/viewmodel/MainModelView.kt b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/viewmodel/MainModelView.kt new file mode 100644 index 0000000..f48d974 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/viewmodel/MainModelView.kt @@ -0,0 +1,32 @@ +package org.neutrino.ktans.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import models.TermFull +import service.TermService +import service.TermServiceImpl +import service.DatabaseFactory.getTranslationForLanguages +import service.SearchType +import kotlinx.coroutines.Dispatchers +class MainModelView(private val repository: TermServiceImpl): ViewModel() { + val terms : StateFlow?> get() = _terms + private val _terms = MutableStateFlow?>(null) + + fun getTerms(term: String) { + viewModelScope.launch(Dispatchers.IO) { + var sType = SearchType.START + sType = if (term.length > 2) SearchType.START else SearchType.EXACT + val trans = getTranslationForLanguages("an","sl") ?: return@launch + val transTerms = repository.getTranslationForTerm(term,trans,sType) + _terms.value = transTerms + + } + } + fun clearSearch() { + _terms.value = listOf() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/neutrino/ktans/viewmodel/State.kt b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/viewmodel/State.kt new file mode 100644 index 0000000..fa49632 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/viewmodel/State.kt @@ -0,0 +1,32 @@ +package org.neutrino.ktans.viewmodel + +/** + * A generic class that holds a value with its loading status. + * @param + */ +data class State(val status: Status, val data: T?, val message: String?) +{ + companion object { + fun success(data: T?): State { + return State(Status.SUCCESS, data, null) + } + + fun empty(): State { + return State(Status.SUCCESS, null, null) + } + + fun error(msg: String?): State { + return State(Status.ERROR, null, msg) + } + + fun loading(): State { + return State(Status.LOADING, null, null) + } + } +} + +enum class Status { + SUCCESS, + ERROR, + LOADING +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/org/neutrino/ktans/main.kt b/composeApp/src/desktopMain/kotlin/org/neutrino/ktans/main.kt index 8e46cdb..9880c95 100644 --- a/composeApp/src/desktopMain/kotlin/org/neutrino/ktans/main.kt +++ b/composeApp/src/desktopMain/kotlin/org/neutrino/ktans/main.kt @@ -2,12 +2,15 @@ package org.neutrino.ktans import androidx.compose.ui.window.Window import androidx.compose.ui.window.application +import service.DatabaseFactory fun main() = application { Window( onCloseRequest = ::exitApplication, title = "KTrans", ) { + DatabaseFactory.connectAndMigrate() + DatabaseFactory.connectAll() App() } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 440ca28..ecc0374 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,16 +11,27 @@ androidx-espresso-core = "3.6.1" androidx-lifecycle = "2.8.4" androidx-material = "1.12.0" androidx-test-junit = "1.2.1" +androidxLifecycleLivedataKtx = "2.6.1" +androidxRuntimeLivedata = "x.x.x" assertjVersion = "3.26.3" compose-multiplatform = "1.7.0" +coreKtx = "1.9.0" exposedVersion = "0.55.0" flywayVersion = "10.20.1" h2 = "2.3.232" hikariCpVersion = "5.1.0" +koin = "4.0.2" +koin-bom = "4.0.2" +koinComposeMultiplatform = "4.0.2" +koin-compose = "1.0.4" +hiltNavigationCompose = "1.2.0" junit = "4.13.2" junitVersion = "5.11.3" kformat = "0.11" +koinAndroidxCompose = "4.0.2)" +koinCore = "3.1.2" kotlin = "2.1.0" +ksp = "2.1.0-1.0.29" kotlinSerializationCompilerPluginEmbeddable = "1.9.22" kotlinx-coroutines = "1.9.0" kotlinxSerializationCore = "1.8.0" @@ -45,12 +56,38 @@ ktorServerCore = "3.0.2" ktorServerCors = "3.0.2" ktorServerNetty = "3.0.2" ktorServerRequestValidation = "3.0.2" +lifecycleLivedataCore = "2.8.7" +lifecycleLivedataKtx = "2.8.7" +lifecycleLivedataKtxVersion = "x.x.x" +lifecycleRuntimeCompose = "2.8.7" +lifecycle = "2.3.1" +lifecycleRuntimeComposeVersion = "2.6.1" +lifecycleViewmodelKtx = "2.6.1" +lifecycleViewmodelCompose = "2.8.4" logback = "1.5.12" material = "1.7.6" psqlVersion = "42.7.4" restAssuredVersion = "5.5.0" +runtimeLivedata = "1.7.6" +runtimeLivedataVersion = "1.0.0-beta01" +symbolProcessingApi = "2.1.0-1.0.29" +lifecycleLivedataCoreKtx = "2.8.7" [libraries] +androidx-core-ktx-v190 = { module = "androidx.core:core-ktx", version.ref = "coreKtx" } +androidx-lifecycle-livedata-core = { module = "androidx.lifecycle:lifecycle-livedata-core", version.ref = "lifecycleLivedataCore" } +androidx-lifecycle-livedata-ktx-v261 = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "androidxLifecycleLivedataKtx" } +androidx-lifecycle-livedata-ktx-v287 = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" } +androidx-lifecycle-livedata-ktx-vxxx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtxVersion" } +androidx-lifecycle-runtime-compose-v261 = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycleRuntimeComposeVersion" } +androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidxLifecycleLivedataKtx" } +androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" } +androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleLivedataCore" } +androidx-lifecycle-viewmodel-ktx-v261 = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" } +androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "androidxLifecycleLivedataKtx" } +androidx-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "runtimeLivedata" } +androidx-runtime-livedata-v100beta01 = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "runtimeLivedataVersion" } +androidx-runtime-livedata-vxxx = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "androidxRuntimeLivedata" } assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertjVersion" } exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposedVersion" } exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposedVersion" } @@ -59,17 +96,23 @@ exposed-json = { module = "org.jetbrains.exposed:exposed-json", version.ref = "e flyway-core = { module = "org.flywaydb:flyway-core", version.ref = "flywayVersion" } h2 = { module = "com.h2database:h2", version.ref = "h2" } hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikariCpVersion" } +insert-koin-koin-compose = { module = "io.insert-koin:koin-compose" } io-ktor-ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } io-ktor-ktor-server-netty = { module = "io.ktor:ktor-server-netty" } junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junitVersion" } junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junitVersion" } kformat = { module = "de.m3y.kformat:kformat", version.ref = "kformat" } + + +koin-compose = { module = "io.insert-koin:koin-compose" } +koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koinComposeMultiplatform" } kotlin-serialization-compiler-plugin-embeddable = { module = "org.jetbrains.kotlin:kotlin-serialization-compiler-plugin-embeddable", version.ref = "kotlinSerializationCompilerPluginEmbeddable" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", 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" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } +androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle" } androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-junit" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-espresso-core" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" } @@ -113,6 +156,8 @@ ktor-server-netty-v302 = { module = "io.ktor:ktor-server-netty", version.ref = " ktor-server-request-validation = { module = "io.ktor:ktor-server-request-validation", version.ref = "ktorServerRequestValidation" } ktor-server-test-host-jvm = { module = "io.ktor:ktor-server-test-host-jvm" } ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor" } +lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycleRuntimeCompose" } +lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" } logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor" } ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor" } @@ -120,6 +165,10 @@ ktor-server-tests = { module = "io.ktor:ktor-server-tests-jvm", version.ref = "k material = { module = "androidx.compose.material:material", version.ref = "material" } postgresql = { module = "org.postgresql:postgresql", version.ref = "psqlVersion" } rest-assured = { module = "io.rest-assured:rest-assured", version.ref = "restAssuredVersion" } +symbol-processing-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "symbolProcessingApi" } +koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin-bom" } +koin-core = { module = "io.insert-koin:koin-core" } +androidx-lifecycle-livedata-core-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-core-ktx", version.ref = "lifecycleLivedataCoreKtx" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 6d54cf9..da2a701 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -3,6 +3,8 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig + + val ktorVersion = "3.0.2" val exposedVersion = "0.55.0" val h2Version = "2.3.232" @@ -18,7 +20,6 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidLibrary) kotlin("plugin.serialization") - } repositories { @@ -94,6 +95,8 @@ kotlin { implementation(libs.flyway.core) implementation(libs.logback) implementation(libs.kformat) + implementation(project.dependencies.platform(libs.koin.bom)) + implementation(libs.koin.core) } }