Compare commits

...

7 Commits

Author SHA1 Message Date
f07ef47de0 Add settings and basic navigation 2025-02-16 11:26:48 +01:00
e02c600ccd Add check icon 2025-02-15 10:20:00 +01:00
1f9ff73a6d Add git ignore 2025-02-09 18:42:18 +01:00
56e7c53f70 Add migrations 2025-02-09 18:33:04 +01:00
66617923b7 Add multiple languages 2025-02-09 18:31:51 +01:00
47e1b47028 Fix bugs and add direction of translation 2025-02-02 17:00:26 +01:00
52eef8dbd4 Cosmetics 2025-02-02 16:23:58 +01:00
21 changed files with 596 additions and 242 deletions

4
.gitignore vendored
View File

@@ -11,8 +11,8 @@ captures
.externalNativeBuild
.cxx
*.xcodeproj/*
data/**
composeApp/data/**
data/*
composeApp/data/*
composeApp/src/commonMain/resources/media/**
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/

View File

@@ -69,12 +69,15 @@ kotlin {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.material3)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(compose.animation)
implementation(libs.androidx.lifecycle.livedata.core)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.lifecycle.runtime.compose)
implementation(libs.lifecycle.viewmodel.compose)
implementation(libs.androidx.lifecycle.viewmodel.compose)
@@ -86,7 +89,8 @@ kotlin {
implementation(libs.composeIcons.tablerIcons)
implementation(libs.composeIcons.fontAwesome)
implementation(libs.korau)
implementation(libs.flagkit)
implementation(libs.navigation.compose.v280alpha10)
}
desktopMain.dependencies {
@@ -94,6 +98,9 @@ kotlin {
implementation(libs.kotlinx.coroutines.swing)
}
}
dependencies {
implementation(libs.androidx.navigation.runtime.android)
}
}
android {

View File

@@ -34,8 +34,7 @@ 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
import org.neutrino.ktans.ui.MainView
@Composable
@Preview

View File

@@ -0,0 +1,9 @@
package org.neutrino.ktans.model.repository
import service.TermServiceImpl
class TermRepository {
private val termService = TermServiceImpl()
suspend fun getTerms
}

View File

@@ -0,0 +1,9 @@
package org.neutrino.ktans.ui
import androidx.compose.runtime.Composable
import org.neutrino.ktans.ui.navigation.NavGraph
@Composable
fun MainView() {
NavGraph()
}

View File

@@ -0,0 +1,23 @@
package org.neutrino.ktans.ui.navigation
import androidx.compose.runtime.Composable
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import org.neutrino.ktans.ui.screens.MainScreen
import org.neutrino.ktans.ui.screens.SettingsScreen
sealed class Screen(val route: String) {
object Home : Screen("home")
object Settings : Screen("settings")
}
@Composable
fun NavGraph(startDestination: String = Screen.Home.route) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = startDestination) {
composable(Screen.Home.route) { MainScreen(navController) }
composable(Screen.Settings.route) { SettingsScreen(navController) }
}
}

View File

@@ -0,0 +1,344 @@
package org.neutrino.ktans.ui.screens
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.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.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.runtime.Composable
import org.koin.compose.koinInject
import org.neutrino.ktans.viewmodel.MainModelView
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import compose.icons.TablerIcons
import compose.icons.tablericons.Table
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.filled.Check
import compose.icons.FontAwesomeIcons
import compose.icons.fontawesomeicons.Solid
import compose.icons.tablericons.ArrowBigRight
import compose.icons.tablericons.FileText
import compose.icons.fontawesomeicons.solid.PlayCircle
import compose.icons.tablericons.ArrowBigLeft
import models.TermFull
import service.DatabaseFactory.getTranslationForLanguages
import service.DatabaseFactory.getDictionaies
import service.DatabaseFactory.getLanguageForId
import dev.carlsen.flagkit.FlagKit
import models.Dictionary
import androidx.navigation.NavController
import org.neutrino.ktans.ui.navigation.Screen
@Composable
fun RowScope.TableCell(
text: String,
weight: Float,
color: Color = Color.Gray
) {
Text(
text = text,
Modifier
.border(1.dp, Color.LightGray)
.weight(weight)
.padding(4.dp)
)
}
@Composable
fun LangugeDropdown(onChangeClick: (dict: Dictionary) -> Unit,dictionary: Dictionary?) {
var expanded by remember { mutableStateOf(false) }
IconButton(onClick = {
expanded = !expanded
}) {
getLanguageForId(dictionary!!.lang1Id)?.let{ l ->
FlagKit.getFlag(countryCode = l.alphaCode!!)?.let {
Image(
imageVector = it,
contentDescription = "English",
)
}
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
for ((id, dict) in getDictionaies()) {
getLanguageForId(dict.lang1Id)?.let { l ->
DropdownMenuItem(
leadingIcon = {
Image(
imageVector = FlagKit.getFlag(countryCode = l.alphaCode!!)!!,
contentDescription = l.name
)
},
text = { Text(l.name) },
onClick = { onChangeClick(dict); expanded = false },
trailingIcon = {
if (dictionary.id == id)
Icon(Icons.Default.Check,contentDescription = "Selected")
}
)
}
}
}
}
}
fun groupByString(terms: List<TermFull>, direction: Int): MutableList<MutableList<TermFull>> {
val fullList: MutableList<MutableList<TermFull>> = mutableListOf()
var lastTerm: TermFull? = null
var termsEq: MutableList<TermFull> = mutableListOf()
for (t in terms) {
if (lastTerm == null) {
termsEq += t
} else {
val s1 = if (direction == 1) lastTerm.string1 else lastTerm.string2
val s2 = if (direction == 1) t.string1 else t.string2
if (s1 == s2) {
termsEq += t
} else {
fullList += termsEq
termsEq = mutableListOf()
termsEq += t
}
}
lastTerm = t
}
fullList += termsEq
return fullList
}
@Composable
fun SearchBarTextField(navController: NavController) {
val viewModel: MainModelView = koinInject<MainModelView>()
val query = remember { mutableStateOf("") }
var columnViewType = remember { mutableStateOf(false) }
var tDirection = remember { mutableStateOf(1) }
Column {
val dict by viewModel.dictionary.collectAsState()
Row(
verticalAlignment = Alignment.CenterVertically,
// horizontalArrangement = Arrangement.End,
modifier = Modifier.background(Color.LightGray)
) {
TextField(
value = query.value,
onValueChange = {
query.value = it
if (dict == null) return@TextField
val l1 = getLanguageForId(dict!!.lang1Id)
val l2 = getLanguageForId(dict!!.lang2Id)
if (l1 != null && l2 != null) {
val trans = if (tDirection.value == 1) getTranslationForLanguages(l1.shortName,l2.shortName) else getTranslationForLanguages(l2.shortName,l1.shortName)
if (trans == null) return@TextField
viewModel.getTerms(query.value, trans)
}
},
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(200.dp).border(width = 2.dp, Color.Black)) {
Spacer(modifier = Modifier.padding(2.dp))
IconButton(onClick = {
columnViewType.value = !columnViewType.value
}) {
if (columnViewType.value)
Icon(
TablerIcons.Table,
contentDescription = "Table View",
modifier = Modifier.size(width = 30.dp, height = 30.dp)
)
else
Icon(
TablerIcons.FileText,
contentDescription = "List View",
modifier = Modifier.size(width = 30.dp, height = 30.dp)
)
}
Spacer(modifier = Modifier.padding(2.dp))
IconButton(onClick = {
tDirection.value = if (tDirection.value == 1) 2 else 1
}) {
if (tDirection.value == 1)
Icon(
TablerIcons.ArrowBigRight,
contentDescription = "Forward",
modifier = Modifier.size(width = 30.dp, height = 30.dp)
)
else
Icon(
TablerIcons.ArrowBigLeft,
contentDescription = "Resverse",
modifier = Modifier.size(width = 30.dp, height = 30.dp)
)
}
LangugeDropdown(
onChangeClick = viewModel::changeDictionary,
dictionary = dict
)
Spacer(modifier = Modifier.padding(2.dp))
IconButton(onClick = {
navController.navigate(Screen.Settings.route)
}) {
Icon(
Icons.Filled.Settings,
contentDescription = "Settings",
modifier = Modifier.size(width = 30.dp, height = 30.dp)
)}
//Spacer(modifier = Modifier.fillMaxWidth())
}
}
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
val terms by viewModel.terms.collectAsState()
if (terms != null) {
SelectionContainer {
Column {
if (columnViewType.value) {
Row(Modifier.background(Color.Gray)) {
TableCell(text = if (tDirection.value == 1) "Anglicky" else "Slovensky", weight = .5f)
TableCell(text = if (tDirection.value == 1) "Slovensky" else "Anglicky", 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)) {
itemsIndexed(items = terms!!, itemContent = { i, t ->
val color = if (i % 2 == 1) Color.hsl(
hue = 168f,
saturation = .77f,
lightness = .68f,
alpha = 1f
) else Color.hsl(hue = 217f, saturation = .77f, lightness = .68f, alpha = 1f)
Row(Modifier.fillMaxWidth().background(color)) {
TableCell(
text = if (tDirection.value == 1) t.string1 else t.string2,
weight = column1Weight
)
TableCell(
text = if (tDirection.value == 1) t.string2 else t.string1,
weight = column2Weight
)
}
})
}
} else {
val terms2: MutableList<MutableList<TermFull>> = groupByString(terms!!, tDirection.value)
LazyColumn(Modifier.fillMaxSize().padding(0.dp)) {
var rowBackground = Color.LightGray
itemsIndexed(items = terms2, itemContent = { i, t2 ->
var t: TermFull? = null
if (t2.size > 0) t = t2[0]
else return@itemsIndexed
when (i % 2) {
0 -> rowBackground = Color.LightGray
1 -> rowBackground = Color.Gray
}
Row(Modifier.fillMaxWidth().background(color = rowBackground)) {
Column {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
if (tDirection.value == 1) t.string1 else t.string2,
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 =
if (tDirection.value == 1) t2.joinToString(separator = ", ") { it.string2 } else t2.joinToString(
separator = ", "
) { it.string1 }
Text(text = str2, modifier = Modifier.padding(start = 20.dp))
Spacer(
modifier = Modifier
.border(1.dp, Color.Black)
.padding(1.dp).fillMaxWidth()
)
}
}
})
}
}
}
}
}
}
}
}
@Composable
fun MainScreen(navController: NavController) {
SearchBarTextField(navController)
}

View File

@@ -0,0 +1,72 @@
package org.neutrino.ktans.ui.screens
import androidx.compose.foundation.layout.IntrinsicSize
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.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.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
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
import compose.icons.TablerIcons
import compose.icons.tablericons.Table
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.filled.Check
import compose.icons.FontAwesomeIcons
import compose.icons.fontawesomeicons.Solid
import compose.icons.tablericons.ArrowBigRight
import compose.icons.tablericons.FileText
import compose.icons.fontawesomeicons.solid.PlayCircle
import compose.icons.tablericons.ArrowBigLeft
import models.Term
import models.TermFull
import kotlinx.coroutines.*
import service.DatabaseFactory.getTranslationForLanguages
import service.DatabaseFactory.getLanguageForCode
import service.DatabaseFactory.getDictionaies
import service.DatabaseFactory.getLanguageForId
import dev.carlsen.flagkit.FlagKit
import dev.carlsen.flagkit.FlagIcons
import models.Dictionary
import models.Settings
import androidx.navigation.NavController
@Composable
fun SettingsScreen(navController: NavController) {
val viewModel: MainModelView = koinInject<MainModelView>()
Column {
Text("Setting Screen")
Button(onClick = { navController.popBackStack() }) {
Text("Back to Home Screen")
}
}
}

View File

@@ -1,224 +0,0 @@
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
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.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
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
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(
text: String,
weight: Float
) {
Text(
text = text,
Modifier
.border(1.dp, Color.Black)
.weight(weight)
.padding(4.dp)
)
}
fun groupByString1(terms: List<TermFull>): MutableList<MutableList<TermFull>> {
val fullList: MutableList<MutableList<TermFull>> = mutableListOf()
var lastTerm: TermFull? = null
var termsEq: MutableList<TermFull> = 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 {
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()
if (terms != null) {
SelectionContainer {
Column {
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 ->
Row(Modifier.fillMaxWidth()) {
TableCell(text = t.string1, weight = column1Weight)
TableCell(text = t.string2, weight = column2Weight)
}
})
}
} else {
val terms2: MutableList<MutableList<TermFull>> = 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())
}
}
})
}
}
}
}
}
}
}
}
@Composable
fun MainView(viewModel: MainModelView = koinInject<MainModelView>(),) {
SearchBarTextField(viewModel)
}

View File

@@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import korlibs.audio.sound.*
import korlibs.io.file.std.resourcesVfs
import korlibs.io.file.std.ZipVfs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -13,21 +14,38 @@ import service.DatabaseFactory.getTranslationForLanguages
import service.SearchType
import service.TermServiceImpl
import korlibs.audio.format.*
import korlibs.io.compression.zip.ZipEntry
import korlibs.io.file.VfsFile
import korlibs.io.file.std.openAsZip
import models.Dictionary
import models.Translation
import service.DatabaseFactory.getDictionaies
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
class MainModelView(private val repository: TermServiceImpl): ViewModel() {
val terms : StateFlow<List<TermFull>?> get() = _terms
private val _terms = MutableStateFlow<List<TermFull>?>(null)
fun getTerms(term: String) {
val dictionary: StateFlow<Dictionary?> get() = _dictionary
private val _dictionary = MutableStateFlow<Dictionary?>(getDictionaies()[1])
fun getTerms(term: String, trans: Translation) {
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 changeDictionary(dict: Dictionary) {
_dictionary.value = dict
}
fun clearSearch() {
_terms.value = listOf()

View File

@@ -17,6 +17,7 @@ assertjVersion = "3.26.3"
compose-multiplatform = "1.7.0"
coreKtx = "1.9.0"
exposedVersion = "0.55.0"
flagkit = "1.1.0"
flywayVersion = "10.20.1"
h2 = "2.3.232"
hikariCpVersion = "5.1.0"
@@ -67,6 +68,9 @@ lifecycleViewmodelKtx = "2.6.1"
lifecycleViewmodelCompose = "2.8.4"
logback = "1.5.12"
material = "1.7.6"
navigationCompose = "2.8.0-alpha10"
okio = "3.10.2"
androidx-navigation = "2.7.0"
psqlVersion = "42.7.4"
restAssuredVersion = "5.5.0"
runtimeLivedata = "1.7.6"
@@ -75,6 +79,7 @@ symbolProcessingApi = "2.1.0-1.0.29"
lifecycleLivedataCoreKtx = "2.8.7"
composeIcons = "1.1.1"
korau = "4.0.10"
navigationRuntimeAndroid = "2.9.0-alpha06"
[libraries]
@@ -89,6 +94,7 @@ composeIcons-linea = { module = "br.com.devsrsouza.compose.icons:linea", version
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" }
flagkit = { module = "dev.carlsen.flagkit:flagkit", version.ref = "flagkit" }
korau = { module = "com.soywiz.korlibs.korau:korau", version.ref = "korau" }
androidx-core-ktx-v190 = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
@@ -182,12 +188,16 @@ ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "kto
ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor" }
ktor-server-tests = { module = "io.ktor:ktor-server-tests-jvm", version.ref = "ktor" }
material = { module = "androidx.compose.material:material", version.ref = "material" }
navigation-compose-v280alpha10 = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
okio = { module = "com.squareup.okio:okio-bom", version.ref = "okio" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "psqlVersion" }
androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
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" }
androidx-navigation-runtime-android = { group = "androidx.navigation", name = "navigation-runtime-android", version.ref = "navigationRuntimeAndroid" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }

View File

@@ -9,10 +9,12 @@ class LanguageDao(id: EntityID<Int>) : IntEntity(id) {
var name by Languages.name
var shortName by Languages.shortName
var alphaCode by Languages.alphaCode
fun toModel() : Language = Language(
id = id.value,
name = name,
shortName = shortName
shortName = shortName,
alphaCode = alphaCode
)
}

View File

@@ -0,0 +1,28 @@
package db.migration
import dao.DictionaryDao
import kotlinx.serialization.decodeFromString
import tables.*
import org.flywaydb.core.api.migration.BaseJavaMigration
import org.flywaydb.core.api.migration.Context
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import kotlin.system.exitProcess
class V4__add_lang_code: BaseJavaMigration() {
override fun migrate(context: Context?) {
transaction {
val mapOfCodes = mapOf(1 to "SK",2 to "CZ", 3 to "GB",4 to "NL", 5 to "FR", 6 to "DE", 7 to "IT", 8 to "IT",
9 to "HU", 10 to "PL", 11 to "PT", 12 to "RU", 13 to "ES", 14 to "SE" )
SchemaUtils.createMissingTablesAndColumns(Languages)
for (l in Languages.selectAll()) {
val id = l[Languages.id]
Languages.update({ Languages.id eq id}) {
it[alphaCode] = mapOfCodes[id.value]
}
}
}
}
}

View File

@@ -0,0 +1,24 @@
package db.migration
import dao.DictionaryDao
import kotlinx.serialization.decodeFromString
import tables.*
import org.flywaydb.core.api.migration.BaseJavaMigration
import org.flywaydb.core.api.migration.Context
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import kotlin.system.exitProcess
import service.DatabaseFactory
class V5__add_indexes: BaseJavaMigration() {
override fun migrate(context: Context?) {
DatabaseFactory.connectAll()
// for ((id,dict) in DatabaseFactory.getDictionaies()) {
transaction (1) {
SchemaUtils.createMissingTablesAndColumns(Terms)
}
//}
}
}

View File

@@ -9,7 +9,8 @@ import org.jetbrains.exposed.sql.Table
data class Language(
val id: Int,
val name: String,
val shortName: String
val shortName: String,
val alphaCode: String?
)

View File

@@ -13,6 +13,8 @@ import javax.sql.DataSource
import tables.*
import dao.*
import com.typesafe.config.ConfigFactory
import models.Dictionary
import models.Language
import models.Translation
object DatabaseFactory {
@@ -21,7 +23,9 @@ object DatabaseFactory {
private val dbs : MutableMap<Int,Database> = mutableMapOf()
private val hdb : MutableMap<String,Database> = mutableMapOf()
private val htrans: MutableMap<String,Translation> = mutableMapOf()
private val halpha: MutableMap<String,Language> = mutableMapOf()
private val dictById: MutableMap<Int,Dictionary> = mutableMapOf()
private val hLang1: MutableMap<Int,Language> = mutableMapOf()
fun connectAndMigrate() {
log.info("Initialising database")
val pool = hikari()
@@ -43,10 +47,17 @@ object DatabaseFactory {
return HikariDataSource(config)
}
fun getTranslationForLanguages(l1: String, l2: String): Translation? = htrans["${l1}${l2}"]?: null
fun getTranslationForLanguages(l1: String, l2: String): Translation? = htrans["${l1.lowercase()}${l2.lowercase()}"]?: null
fun getLanguageForCode(l: String): Language? = halpha[l.uppercase()]?: null
fun getLanguageForId(id: Int): Language? = hLang1[id]?: null
fun getDictionaies(): Map<Int,Dictionary> = dictById
fun getAllLanguages(): List<Language> {
return halpha.values.toList()
}
fun connectAll() {
if (getDictionaies().any()) return
fun connectAll(): Map<Int,Database> {
val hMap : MutableMap<Int, Database> = mutableMapOf()
transaction {
for (trans in TranslationDao.all()) {
val l1 = trans.lang1.shortName.lowercase()
@@ -55,6 +66,11 @@ object DatabaseFactory {
htrans["${l1}${l2}"] = trans.toModel()
}
for (lang in LanguageDao.all()) {
halpha[lang.shortName] = lang.toModel()
hLang1[lang.id.value] = lang.toModel()
}
for (dict in DictionaryDao.all()) {
val cfg = ConfigFactory.load().getConfig("h2")
val config = HikariConfig().apply {
@@ -68,10 +84,14 @@ object DatabaseFactory {
val db = HikariDataSource(config)
val dbc = Database.connect(db)
dbs[dict.id.value] = dbc
dictById[dict.id.value] = dict.toModel()
hdb["${dict.lang1.shortName.lowercase()}${dict.lang2.shortName.lowercase()}"] = dbc
}
}
return hMap
println("DBS PRINT")
println(dbs)
}
private fun runFlyway(datasource: DataSource) {

View File

@@ -6,4 +6,7 @@ import models.Language
interface LanguageService {
fun getLanguage(id: Int): Language?
fun getAllLanguages(): List<Language>
fun getAlphaCode4String(code: String): Language?
fun getAlphaCode4Lang(lang: Language): Language?
}

View File

@@ -3,6 +3,7 @@ package service
import dao.LanguageDao
import org.jetbrains.exposed.sql.transactions.transaction
import service.DatabaseFactory.dbExecId
import service.DatabaseFactory.getLanguageForCode
import models.Language
class LanguageServiceImpl : LanguageService {
@@ -14,4 +15,12 @@ class LanguageServiceImpl : LanguageService {
LanguageDao.all().map { it.toModel() }
}
override fun getAlphaCode4String(code: String): Language? {
return getLanguageForCode(code)
}
override fun getAlphaCode4Lang(lang: Language): Language? {
return getLanguageForCode(lang.shortName)
}
}

View File

@@ -47,7 +47,7 @@ class TermServiceImpl : TermService {
if (type == SearchType.EXACT)
TermDao.find { Terms.string2 eq s }.map { it.toFullModel() }
else
TermDao.find { Terms.string1 like s }.map { it.toFullModel() }
TermDao.find { Terms.string2 like s }.map { it.toFullModel() }
}
}

View File

@@ -8,5 +8,5 @@ object Languages : IntIdTable() {
val name = varchar("name", 255)
val shortName = varchar("short_name", 255)
val alphaCode = char("alpha_code",2).nullable()
}

View File

@@ -6,8 +6,8 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import tables.Suffixes
object Terms : IntIdTable() {
val string1 = varchar("string1", 255)
val string2 = varchar("string2", 255)
val string1 = varchar("string1", 255).index()
val string2 = varchar("string2", 255).index()
val suffix1 = reference("suffix1_id",Suffixes).nullable()
val suffix2 = reference("suffix2_id",Suffixes).nullable()
val type = reference("type_id",DictTypes).nullable()