diff --git a/.gitignore b/.gitignore index 7d9c0e4..e316370 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ captures .externalNativeBuild .cxx *.xcodeproj/* +data/** +composeApp/data/** +composeApp/src/commonMain/resources/media/** !*.xcodeproj/project.pbxproj !*.xcodeproj/xcshareddata/ !*.xcodeproj/project.xcworkspace/ diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index f02160c..c080535 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -83,6 +83,9 @@ kotlin { implementation(libs.koin.core) implementation(libs.koin.compose) implementation(libs.koin.compose.viewmodel) + implementation(libs.composeIcons.tablerIcons) + implementation(libs.composeIcons.fontAwesome) + implementation(libs.korau) } diff --git a/composeApp/src/commonMain/kotlin/org/neutrino/ktans/view/MainView.kt b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/view/MainView.kt index 0544907..e9a0ce8 100644 --- a/composeApp/src/commonMain/kotlin/org/neutrino/ktans/view/MainView.kt +++ b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/view/MainView.kt @@ -1,5 +1,6 @@ package org.neutrino.ktans.view +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.ui.graphics.Color @@ -12,12 +13,14 @@ import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight 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.material.icons.filled.Settings import androidx.compose.runtime.Composable import kotlinx.coroutines.flow.observeOn import org.koin.compose.koinInject @@ -28,6 +31,16 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.lifecycle.viewmodel.compose.viewModel +import compose.icons.TablerIcons +import compose.icons.tablericons.Table +import androidx.compose.foundation.layout.Arrangement +import compose.icons.FontAwesomeIcons +import compose.icons.fontawesomeicons.Solid +import compose.icons.tablericons.ArrowBigRight +import compose.icons.fontawesomeicons.solid.PlayCircle +import models.Term +import models.TermFull +import kotlinx.coroutines.* @Composable fun RowScope.TableCell( @@ -39,37 +52,76 @@ fun RowScope.TableCell( Modifier .border(1.dp, Color.Black) .weight(weight) - .padding(8.dp) + .padding(4.dp) ) } +fun groupByString1(terms: List): MutableList> { + val fullList: MutableList> = mutableListOf() + var lastTerm: TermFull? = null + var termsEq: MutableList = mutableListOf() + for (t in terms) { + if (lastTerm == null) { + termsEq += t + } else { + if (lastTerm.string1 == t.string1){ + termsEq += t + } else { + fullList += termsEq + termsEq = mutableListOf() + termsEq += t + } + + } + lastTerm = t + } + fullList += termsEq + return fullList +} + @Composable fun SearchBarTextField(viewModel: MainModelView) { val query = remember { mutableStateOf("") } + val columnViewType = remember { mutableStateOf(false) } 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() - ) + Row( + verticalAlignment = Alignment.CenterVertically, +// horizontalArrangement = Arrangement.End, + modifier = Modifier.background(Color.LightGray) + ) { + + 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.weight(3f) + ) + Row (Modifier.width(155.dp).border(width = 2.dp, Color.Black)) { + Spacer(modifier = Modifier.padding(2.dp)) + Icon(TablerIcons.Table, contentDescription = "Table View", modifier = Modifier.size(width = 50.dp, height = 50.dp)) + Spacer(modifier = Modifier.padding(2.dp)) + Icon(TablerIcons.ArrowBigRight, contentDescription = "Table View", modifier = Modifier.size(width = 50.dp, height = 50.dp)) + Spacer(modifier = Modifier.padding(2.dp)) + Icon(Icons.Filled.Settings, contentDescription = "Table View", modifier = Modifier.size(width = 50.dp, height = 50.dp)) + //Spacer(modifier = Modifier.fillMaxWidth()) + } + } Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { val terms by viewModel.terms.collectAsState() @@ -77,27 +129,85 @@ fun SearchBarTextField(viewModel: MainModelView) { 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 + if (columnViewType.value) { + 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 -> + items(items = terms!!, itemContent = { t -> - Row(Modifier.fillMaxWidth()) { - TableCell(text = t.string1, weight = column1Weight) - TableCell(text = t.string2, weight = column2Weight) - } + Row(Modifier.fillMaxWidth()) { + TableCell(text = t.string1, weight = column1Weight) + TableCell(text = t.string2, weight = column2Weight) + } - }) + }) + + } + } else { + val terms2: MutableList> = groupByString1(terms!!) + + LazyColumn(Modifier.fillMaxSize().padding(0.dp)) { + var i=0 + var rowBackground = Color.LightGray + items(items = terms2, itemContent = { t2 -> + var t : TermFull? = null + if (t2.size > 0) t = t2[0] + else return@items + when ( i++ % 2 ) { + 0 -> rowBackground = Color.LightGray + 1 -> rowBackground = Color.Gray + } + Row(Modifier.fillMaxWidth().background(color = rowBackground)) { + Column { + Row( verticalAlignment = Alignment.CenterVertically) { + Text(t.string1, fontWeight = FontWeight.Bold) + for (p in t.pronunciations!!) { + Spacer(modifier = Modifier.padding(2.dp)) + if (p.ipa != null) { + var color = Color.Blue + when (p.typeId) { + 1 -> color = Color.Green + 2 -> color = Color.Red + 3 -> color = Color.Blue + } + Text("[${p.ipa}]", color = color) + } + if (p.filename != null) { + IconButton( + modifier = Modifier.size(20.dp), + onClick = { + viewModel.playSound("media/${p.filename}") + }) { + Icon(FontAwesomeIcons.Solid.PlayCircle,contentDescription = "Play sound",Modifier.size(20.dp).padding(start = 2.dp),) + } + + + + } + Spacer(modifier = Modifier.padding(2.dp)) + } + } + + val str2 = t2.joinToString(separator = ", ") { it.string2 } + Text(text = str2, modifier = Modifier.padding(start = 20.dp)) + Spacer(modifier = Modifier + .border(1.dp, Color.Black) + .padding(1.dp).fillMaxWidth()) + } + } + }) + } } } + } } diff --git a/composeApp/src/commonMain/kotlin/org/neutrino/ktans/viewmodel/MainModelView.kt b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/viewmodel/MainModelView.kt index f48d974..4178fdc 100644 --- a/composeApp/src/commonMain/kotlin/org/neutrino/ktans/viewmodel/MainModelView.kt +++ b/composeApp/src/commonMain/kotlin/org/neutrino/ktans/viewmodel/MainModelView.kt @@ -2,16 +2,18 @@ package org.neutrino.ktans.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope - +import korlibs.audio.sound.* +import korlibs.io.file.std.resourcesVfs +import kotlinx.coroutines.Dispatchers 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 +import service.TermServiceImpl +import korlibs.audio.format.* + class MainModelView(private val repository: TermServiceImpl): ViewModel() { val terms : StateFlow?> get() = _terms private val _terms = MutableStateFlow?>(null) @@ -28,5 +30,16 @@ class MainModelView(private val repository: TermServiceImpl): ViewModel() { } fun clearSearch() { _terms.value = listOf() + } + + fun playSound(file: String) { + viewModelScope.launch { + + val sound = resourcesVfs[file.replace("ogg","mp3")].readMusic() + sound.play() + } + } + + } \ No newline at end of file diff --git a/composeApp/src/desktopMain/resources/application.conf b/composeApp/src/desktopMain/resources/application.conf index c7314a5..cf65ed4 100644 --- a/composeApp/src/desktopMain/resources/application.conf +++ b/composeApp/src/desktopMain/resources/application.conf @@ -1,6 +1,10 @@ +sound { + database = "./data/media" +} + h2 { driver = org.h2.Driver url = "jdbc:h2:file:./data/%s;DB_CLOSE_DELAY=-1;" user = "sa" password = "" - } \ No newline at end of file + } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ecc0374..d4a68cb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,6 +30,7 @@ junitVersion = "5.11.3" kformat = "0.11" koinAndroidxCompose = "4.0.2)" koinCore = "3.1.2" +korauOggVorbisJvm = "1.8.6" kotlin = "2.1.0" ksp = "2.1.0-1.0.29" kotlinSerializationCompilerPluginEmbeddable = "1.9.22" @@ -72,8 +73,24 @@ runtimeLivedata = "1.7.6" runtimeLivedataVersion = "1.0.0-beta01" symbolProcessingApi = "2.1.0-1.0.29" lifecycleLivedataCoreKtx = "2.8.7" +composeIcons = "1.1.1" +korau = "4.0.10" + [libraries] + +composeIcons-cssGg = { module = "br.com.devsrsouza.compose.icons:css-gg", version.ref = "composeIcons" } +composeIcons-weatherIcons = { module = "br.com.devsrsouza.compose.icons:erikflowers-weather-icons", version.ref = "composeIcons" } +composeIcons-evaIcons = { module = "br.com.devsrsouza.compose.icons:eva-icons", version.ref = "composeIcons" } +composeIcons-feather = { module = "br.com.devsrsouza.compose.icons:feather", version.ref = "composeIcons" } +composeIcons-fontAwesome = { module = "br.com.devsrsouza.compose.icons:font-awesome", version.ref = "composeIcons" } +composeIcons-lineAwesome = { module = "br.com.devsrsouza.compose.icons:line-awesome", version.ref = "composeIcons" } +composeIcons-linea = { module = "br.com.devsrsouza.compose.icons:linea", version.ref = "composeIcons" } +composeIcons-octicons = { module = "br.com.devsrsouza.compose.icons:octicons", version.ref = "composeIcons" } +composeIcons-simpleIcons = { module = "br.com.devsrsouza.compose.icons:simple-icons", version.ref = "composeIcons" } +composeIcons-tablerIcons = { module = "br.com.devsrsouza.compose.icons:tabler-icons", version.ref = "composeIcons" } +korau = { module = "com.soywiz.korlibs.korau:korau", version.ref = "korau" } + 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" } @@ -106,6 +123,8 @@ 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" } +korau-macosarm64 = { module = "com.soywiz.korlibs.korau:korau-macosarm64", version.ref = "korau" } +korau-ogg-vorbis-jvm = { module = "com.soywiz.korlibs.korau:korau-ogg-vorbis-jvm", version.ref = "korauOggVorbisJvm" } 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" }