diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 76fc90b3a..69f8b958c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -334,7 +334,6 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2") implementation("com.charleskorn.kaml:kaml:0.57.0") implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7") - } tasks.register("moveFromi18n") { diff --git a/app/src/main/assets/i18n b/app/src/main/assets/i18n index ef12a2bba..e158f934b 160000 --- a/app/src/main/assets/i18n +++ b/app/src/main/assets/i18n @@ -1 +1 @@ -Subproject commit ef12a2bba6119f81f08001fb4f1adacc96c25d6c +Subproject commit e158f934b389d334bb32901329c08aa887548969 diff --git a/app/src/main/java/be/scri/App.kt b/app/src/main/java/be/scri/App.kt index fb19c3225..7b5430beb 100644 --- a/app/src/main/java/be/scri/App.kt +++ b/app/src/main/java/be/scri/App.kt @@ -28,6 +28,8 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState +import be.scri.helpers.AppFlavor +import be.scri.helpers.FlavorProvider import be.scri.helpers.PreferencesHelper import be.scri.navigation.Screen import be.scri.ui.common.appcomponents.HintDialog @@ -216,6 +218,18 @@ fun ScribeApp( ) }, ) + HintDialog( + pagerState = pagerState, + currentPageIndex = page, + sharedPrefsKey = "hint_shown_conjugate", + hintMessageResId = R.string.i18n_app_conjugate_app_hint_tooltip, + isHintChanged = isHintChanged[page] == true, + onDismiss = { onDismiss(it) }, + modifier = + Modifier + .fillMaxWidth() + .padding(8.dp), + ) } HandleBackPress(pagerState, coroutineScope) } @@ -241,7 +255,12 @@ fun ScribeApp( pagerState = pagerState, currentPageIndex = page, sharedPrefsKey = "hint_shown_settings", - hintMessageResId = R.string.i18n_app_settings_app_hint_tooltip, + hintMessageResId = + if (FlavorProvider.get() == AppFlavor.CONJUGATE) { + R.string.i18n_app_settings_conjugate_app_hint_tooltip + } else { + R.string.i18n_app_settings_keyboard_app_hint_tooltip + }, isHintChanged = isHintChanged[page] == true, onDismiss = { onDismiss(it) }, modifier = Modifier.padding(8.dp), diff --git a/app/src/main/java/be/scri/helpers/data/ConjugateDataManager.kt b/app/src/main/java/be/scri/helpers/data/ConjugateDataManager.kt index 895aac28e..090874735 100644 --- a/app/src/main/java/be/scri/helpers/data/ConjugateDataManager.kt +++ b/app/src/main/java/be/scri/helpers/data/ConjugateDataManager.kt @@ -85,7 +85,7 @@ class ConjugateDataManager( * * @return The conjugated word as a [String], or an empty string if not found. */ - private fun getTheValueForTheConjugateWord( + fun getTheValueForTheConjugateWord( word: String, form: String?, language: String, @@ -96,17 +96,35 @@ class ConjugateDataManager( return "" } - val columnName = if (language == "SV") "verb" else "infinitive" - if (!db.columnExists("verbs", columnName)) { - return "" - } + val columnName = db.getInfinitiveColumnName() ?: return "" - getVerbCursor(db, word, language)?.use { cursor -> + getVerbCursor(db, word, columnName)?.use { cursor -> getConjugatedValueFromCursor(cursor, form, language) } } ?: "" } + private fun getColumnIndexWithFallback( + cursor: Cursor, + columnName: String, + ): Int { + var idx = cursor.getColumnIndex(columnName) + if (idx != -1) return idx + + val fallbacks = + listOf( + "combined" + columnName.replaceFirstChar { it.uppercase() }, + columnName.replace("presentParticiple", "combinedPresentParticiple"), + columnName.replace("pastParticiple", "combinedPastParticiple"), + ) + for (fallback in fallbacks) { + idx = cursor.getColumnIndex(fallback) + if (idx != -1) return idx + } + + throw IllegalArgumentException("column '$columnName' does not exist. Available columns: " + cursor.columnNames.joinToString(", ")) + } + /** * Extracts a conjugated value from a database cursor for a given form. * It handles both simple column lookups and complex forms that require parsing. @@ -125,7 +143,7 @@ class ConjugateDataManager( parseComplexForm(cursor, form, language) } else { try { - cursor.getString(cursor.getColumnIndexOrThrow(form)) + cursor.getString(getColumnIndexWithFallback(cursor, form)) } catch (e: IllegalArgumentException) { Log.e("ConjugateDataManager", "Simple form column not found: '$form'", e) "" @@ -154,7 +172,7 @@ class ConjugateDataManager( val dbColumnName = form.replace(bracketRegex, "").trim() return try { - val verbPart = cursor.getString(cursor.getColumnIndexOrThrow(dbColumnName)) + val verbPart = cursor.getString(getColumnIndexWithFallback(cursor, dbColumnName)) // Try to handle it as a dynamic lookup (German style: [form auxiliary_column]). try { @@ -219,11 +237,10 @@ class ConjugateDataManager( /** * Creates and returns a database cursor pointing to the requested verb's data row. - * Note: Handles a special case for Swedish ("SV") where the key column is 'verb' instead of 'infinitive'. * * @param db The SQLite database instance to query. * @param word The verb to search for. - * @param language The language code, used for special query conditions. + * @param columnName The name of the infinitive/verb column. * * @return A [Cursor] positioned at the verb's row, or null if the verb is not found. * The caller is responsible for closing the cursor. @@ -231,14 +248,9 @@ class ConjugateDataManager( private fun getVerbCursor( db: SQLiteDatabase, word: String, - language: String, + columnName: String, ): Cursor? { - val query = - if (language == "SV") { - "SELECT * FROM verbs WHERE verb = ?" - } else { - "SELECT * FROM verbs WHERE infinitive = ?" - } + val query = "SELECT * FROM verbs WHERE $columnName = ?" val cursor = db.rawQuery(query, arrayOf(word)) return if (cursor.moveToFirst()) { cursor diff --git a/app/src/main/java/be/scri/helpers/data/SQLiteExtensions.kt b/app/src/main/java/be/scri/helpers/data/SQLiteExtensions.kt index 5ed412a3a..4d129bc29 100644 --- a/app/src/main/java/be/scri/helpers/data/SQLiteExtensions.kt +++ b/app/src/main/java/be/scri/helpers/data/SQLiteExtensions.kt @@ -25,3 +25,13 @@ fun SQLiteDatabase.columnExists( } false } + +fun SQLiteDatabase.getInfinitiveColumnName(): String? { + val candidates = listOf("infinitive", "verb", "activeInfinitive", "simplePresent") + for (candidate in candidates) { + if (columnExists("verbs", candidate)) { + return candidate + } + } + return null +} diff --git a/app/src/main/java/be/scri/ui/screens/ConjugateScreen.kt b/app/src/main/java/be/scri/ui/screens/ConjugateScreen.kt index 0498daf23..37a53ca74 100644 --- a/app/src/main/java/be/scri/ui/screens/ConjugateScreen.kt +++ b/app/src/main/java/be/scri/ui/screens/ConjugateScreen.kt @@ -2,6 +2,8 @@ package be.scri.ui.screens +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -32,13 +34,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import be.scri.R @@ -73,19 +75,23 @@ fun ConjugateScreen( .verticalScroll(scrollState), horizontalAlignment = Alignment.CenterHorizontally, ) { - Spacer(modifier = Modifier.height(dynamicSpacing)) + if (searchQuery.isEmpty()) { + Spacer(modifier = Modifier.height(dynamicSpacing)) - Image( - painter = painterResource(id = R.drawable.scribe_logo), - contentDescription = stringResource(R.string.app_launcher_name), - modifier = - Modifier - .align(Alignment.CenterHorizontally) - .padding(bottom = 16.dp) - .width(248.dp) - .height(122.dp), - contentScale = ContentScale.Fit, - ) + Image( + painter = painterResource(id = R.drawable.scribe_logo), + contentDescription = stringResource(R.string.app_launcher_name), + modifier = + Modifier + .align(Alignment.CenterHorizontally) + .padding(bottom = 16.dp) + .width(248.dp) + .height(122.dp), + contentScale = ContentScale.Fit, + ) + } else { + Spacer(modifier = Modifier.height(Dimensions.PaddingSmall)) + } // Header 1: Conjugate verbs Text( @@ -102,155 +108,188 @@ fun ConjugateScreen( ).align(Alignment.Start), ) - // Search Bar - Row( + Card( modifier = Modifier .fillMaxWidth() - .height(56.dp) - .background( - color = MaterialTheme.colorScheme.surfaceContainer, - shape = RoundedCornerShape(dimensionResource(id = R.dimen.rounded_corner_radius_standard)), - ).border( - width = 1.dp, - color = MaterialTheme.colorScheme.outline, - shape = RoundedCornerShape(dimensionResource(id = R.dimen.rounded_corner_radius_standard)), - ).padding(horizontal = Dimensions.PaddingMedium), - verticalAlignment = Alignment.CenterVertically, - ) { - Image( - painter = painterResource(id = R.drawable.ic_search_vector), - contentDescription = "Search", - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimary), - modifier = Modifier.size(Dimensions.IconSize), - ) - - Spacer(modifier = Modifier.width(12.dp)) - - BasicTextField( - value = searchQuery, - onValueChange = { viewModel.onSearchQueryChanged(it) }, - textStyle = - MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimary, - fontWeight = FontWeight.Bold, - ), - singleLine = true, - cursorBrush = SolidColor(MaterialTheme.colorScheme.onPrimary), - modifier = Modifier.weight(1f), - decorationBox = { innerTextField -> - Box( - modifier = Modifier.fillMaxWidth(), - contentAlignment = Alignment.CenterStart, - ) { + .padding(bottom = Dimensions.PaddingMedium) + .animateContentSize(), + shape = RoundedCornerShape(dimensionResource(id = R.dimen.rounded_corner_radius_standard)), + colors = + CardDefaults.cardColors( + containerColor = if (searchQuery.isEmpty()) { - Text( - text = stringResource(R.string.i18n_app_conjugate_verbs_search_placeholder), - color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.6f), - style = - MaterialTheme.typography.bodyLarge.copy( - fontWeight = FontWeight.Bold, - ), - ) - } - innerTextField() - } + MaterialTheme.colorScheme.surfaceContainer + } else { + MaterialTheme.colorScheme.surface + }, + ), + border = + if (searchQuery.isEmpty()) { + BorderStroke(1.dp, MaterialTheme.colorScheme.outline) + } else { + null }, - ) - - if (searchQuery.isNotEmpty()) { - Image( - painter = painterResource(id = R.drawable.close), - contentDescription = "Clear", - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimary), + ) { + Column(modifier = Modifier.fillMaxWidth()) { + // Search Bar Row (Always at the exact same composition tree location!) + Row( modifier = Modifier - .size(Dimensions.IconSize) - .clickable { viewModel.clearSearchQuery() }, - ) - Spacer(modifier = Modifier.width(8.dp)) - } + .fillMaxWidth() + .height(56.dp) + .padding(horizontal = Dimensions.PaddingMedium), + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + painter = painterResource(id = R.drawable.ic_search_vector), + contentDescription = "Search", + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimary), + modifier = Modifier.size(Dimensions.IconSize), + ) - Image( - painter = painterResource(id = R.drawable.play_button), - contentDescription = "Play button", - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimary), - modifier = - Modifier - .width(21.dp) - .height(18.dp) - .clickable { - val first = displayResults.firstOrNull { !it.isDummy } - if (first != null) { - viewModel.onVerbSelected(first) - onNavigateToConjugationSelection(first.verb, first.languageAlias) + Spacer(modifier = Modifier.width(12.dp)) + + BasicTextField( + value = searchQuery, + onValueChange = { viewModel.onSearchQueryChanged(it) }, + textStyle = + MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onPrimary, + fontWeight = FontWeight.Bold, + ), + singleLine = true, + modifier = Modifier.weight(1f), + decorationBox = { innerTextField -> + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.CenterStart, + ) { + if (searchQuery.isEmpty()) { + Text( + text = stringResource(R.string.i18n_app_conjugate_verbs_search_placeholder), + color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.6f), + style = + MaterialTheme.typography.bodyLarge.copy( + fontWeight = FontWeight.Bold, + ), + ) + } + innerTextField() } }, - ) - } + ) - Spacer(modifier = Modifier.height(Dimensions.PaddingMedium)) + if (searchQuery.isNotEmpty()) { + Image( + painter = painterResource(id = R.drawable.close), + contentDescription = "Clear", + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimary), + modifier = + Modifier + .size(Dimensions.IconSize) + .clickable { viewModel.clearSearchQuery() }, + ) + Spacer(modifier = Modifier.width(8.dp)) + } - if (searchQuery.isNotEmpty()) { - // Search suggestion container — dummy rows until DB is populated (#570) - Card( - modifier = - Modifier - .fillMaxWidth() - .padding(bottom = Dimensions.PaddingMedium), - shape = RoundedCornerShape(dimensionResource(id = R.dimen.rounded_corner_radius_standard)), - colors = - CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surface, - ), - elevation = CardDefaults.cardElevation(defaultElevation = Dimensions.ElevationSmall), - ) { - Column( - modifier = - Modifier - .fillMaxWidth() - .padding(Dimensions.PaddingSmall), - ) { - displayResults.forEachIndexed { index, result -> - Row( + Image( + painter = painterResource(id = R.drawable.play_button), + contentDescription = "Play button", + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimary), + modifier = + Modifier + .width(21.dp) + .height(18.dp) + .clickable { + val first = displayResults.firstOrNull() + if (first != null) { + viewModel.onVerbSelected(first) + onNavigateToConjugationSelection(first.verb, first.languageAlias) + } + }, + ) + } + + if (searchQuery.isNotEmpty()) { + // Thin divider between Search Bar and Results + Spacer( + modifier = + Modifier + .fillMaxWidth() + .height(1.dp) + .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)), + ) + + if (displayResults.isNotEmpty()) { + displayResults.forEachIndexed { index, result -> + Row( + modifier = + Modifier + .fillMaxWidth() + .clickable { + viewModel.onVerbSelected(result) + onNavigateToConjugationSelection(result.verb, result.languageAlias) + }.padding(Dimensions.PaddingMedium), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "${result.verb} (${getLanguageDisplayName(result.languageAlias)})", + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.labelMedium, + ) + Image( + painter = painterResource(id = R.drawable.right_arrow), + contentDescription = "Right Arrow", + modifier = + Modifier + .size(Dimensions.IconSize) + .alpha(Alpha.HIGH), + ) + } + if (index < displayResults.lastIndex) { + Spacer( + modifier = + Modifier + .fillMaxWidth() + .height(1.dp) + .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)), + ) + } + } + } else { + val downloaded = viewModel.getDownloadedLanguages() + val noResultsMessage = + if (downloaded.isEmpty()) { + "No results found. Please download verb data first." + } else { + "No results found in ${viewModel.getDownloadedLanguagesFormatted()}" + } + Column( modifier = Modifier .fillMaxWidth() - .clickable { - viewModel.onVerbSelected(result) - onNavigateToConjugationSelection(result.verb, result.languageAlias) - }.padding(Dimensions.PaddingMedium), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, + .padding(Dimensions.PaddingMedium), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { Text( - text = "${result.verb} (${getLanguageDisplayName(result.languageAlias)})", + text = noResultsMessage, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.labelMedium, - ) - Image( - painter = painterResource(id = R.drawable.right_arrow), - contentDescription = "Right Arrow", - modifier = - Modifier - .size(Dimensions.IconSize) - .alpha(Alpha.HIGH), - ) - } - if (index < displayResults.lastIndex) { - Spacer( - modifier = - Modifier - .fillMaxWidth() - .height(1.dp) - .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)), + textAlign = TextAlign.Center, ) } } } } - } else { + } + + if (searchQuery.isEmpty()) { + Spacer(modifier = Modifier.height(Dimensions.PaddingMedium)) // normal screen flow when not searching // Header 2: Verb data diff --git a/app/src/main/java/be/scri/ui/screens/ConjugateViewModel.kt b/app/src/main/java/be/scri/ui/screens/ConjugateViewModel.kt index d7b351f07..2c27f2a73 100644 --- a/app/src/main/java/be/scri/ui/screens/ConjugateViewModel.kt +++ b/app/src/main/java/be/scri/ui/screens/ConjugateViewModel.kt @@ -8,7 +8,7 @@ import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import be.scri.helpers.DatabaseFileManager -import be.scri.helpers.data.columnExists +import be.scri.helpers.data.getInfinitiveColumnName import be.scri.helpers.data.tableExists import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -50,17 +50,13 @@ class ConjugateViewModel( * Results shown in the search dropdown. * - Blank query → empty * - Real DB results → show them - * - No DB yet → dummy rows using the typed query as label */ val displayResults = combine(_searchQuery, _searchResults) { query, results -> - when { - query.isBlank() -> emptyList() - results.isNotEmpty() -> results - else -> - listOf("DE", "ES", "FR", "IT", "PT", "RU", "SV", "EN", "DE").map { alias -> - ConjugateSearchResult(verb = query, languageAlias = alias, isDummy = true) - } + if (query.isBlank()) { + emptyList() + } else { + results } }.stateIn( scope = viewModelScope, @@ -68,6 +64,30 @@ class ConjugateViewModel( initialValue = emptyList(), ) + /** + * Returns a list of language aliases that have been downloaded (i.e. conjugate database exists). + */ + fun getDownloadedLanguages(): List { + val aliases = listOf("EN", "FR", "DE", "IT", "PT", "RU", "ES", "SV") + return aliases.filter { alias -> + val dbName = "${alias}ConjugateData.sqlite" + getApplication().getDatabasePath(dbName).exists() + } + } + + /** + * Formats the list of downloaded languages into a user-friendly display string. + */ + fun getDownloadedLanguagesFormatted(): String { + val downloaded = getDownloadedLanguages() + val names = downloaded.map { getLanguageDisplayName(it) } + return when { + names.isEmpty() -> "" + names.size == 1 -> names.first() + else -> names.dropLast(1).joinToString(", ") + " and " + names.last() + } + } + init { loadRecentlyConjugated() } @@ -101,8 +121,8 @@ class ConjugateViewModel( val db = fileManager.getConjugateDatabase(alias) ?: continue try { - val columnName = if (alias == "SV") "verb" else "infinitive" - if (db.tableExists("verbs") && db.columnExists("verbs", columnName)) { + val columnName = db.getInfinitiveColumnName() + if (columnName != null && db.tableExists("verbs")) { db .rawQuery( "SELECT DISTINCT $columnName FROM verbs WHERE $columnName LIKE ? LIMIT 10", @@ -134,7 +154,17 @@ class ConjugateViewModel( withContext(Dispatchers.Main) { if (_searchQuery.value == query) { - _searchResults.value = results.distinctBy { it.verb.lowercase() + "_" + it.languageAlias } + val distinctResults = results.distinctBy { it.verb.lowercase() + "_" + it.languageAlias } + _searchResults.value = + distinctResults.sortedWith( + compareBy { + if (it.verb.equals(query, ignoreCase = true)) 0 else 1 + }.thenBy { + it.verb.length + }.thenBy { + it.verb.lowercase() + }, + ) } } } diff --git a/app/src/main/java/be/scri/ui/screens/ConjugationSelectionScreen.kt b/app/src/main/java/be/scri/ui/screens/ConjugationSelectionScreen.kt index 459e04b81..0dc6aaccd 100644 --- a/app/src/main/java/be/scri/ui/screens/ConjugationSelectionScreen.kt +++ b/app/src/main/java/be/scri/ui/screens/ConjugationSelectionScreen.kt @@ -2,84 +2,66 @@ package be.scri.ui.screens +import android.widget.Toast +import androidx.compose.animation.Crossfade +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import be.scri.R +import be.scri.helpers.DatabaseFileManager +import be.scri.helpers.data.ConjugateDataManager +import be.scri.helpers.data.ContractDataLoader import be.scri.ui.common.ScribeBaseScreen - -// --------------------------------------------------------------------------- -// Dummy data — replaced by real DB data once #564 / #570 are finalised -// --------------------------------------------------------------------------- - -private data class DummyTenseRow( - val label: String, - val forms: List, -) - -private data class DummyTenseGroup( - val sectionTitle: String, - val rows: List, -) - -private val DUMMY_CONJUGATION_DATA: List = - listOf( - DummyTenseGroup( - sectionTitle = "Indicative", - rows = - listOf( - DummyTenseRow("Present", listOf("I verb", "you verb", "he/she verbs", "we verb", "you verb", "they verb")), - DummyTenseRow("Past", listOf("I verbed", "you verbed", "he/she verbed", "we verbed", "you verbed", "they verbed")), - DummyTenseRow("Future", listOf("I will verb", "you will verb", "he/she will verb", "we will verb", "you will verb", "they will verb")), - ), - ), - DummyTenseGroup( - sectionTitle = "Subjunctive", - rows = - listOf( - DummyTenseRow("Present", listOf("I verb", "you verb", "he/she verb", "we verb", "you verb", "they verb")), - DummyTenseRow("Past", listOf("I had verbed", "you had verbed", "he/she had verbed", "we had verbed", "you had verbed", "they had verbed")), - ), - ), - DummyTenseGroup( - sectionTitle = "Imperative", - rows = - listOf( - DummyTenseRow("Present", listOf("verb!", "let's verb!", "verb!")), - ), - ), - ) +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext // --------------------------------------------------------------------------- // Screen // --------------------------------------------------------------------------- /** - * Displays dummy conjugation tables for a selected verb. - * - * The header shows "[verb] ([language])" as specified in the Figma designs for issue #567. - * Real DB data will replace [DUMMY_CONJUGATION_DATA] once #564 / #570 are finalised. + * Displays conjugation tables for a selected verb using a beautiful grid-based layout. * * @param verb The verb selected by the user. * @param languageAlias The language code (e.g. "DE", "ES"). @@ -93,9 +75,49 @@ fun ConjugationSelectionScreen( onBackNavigation: () -> Unit, modifier: Modifier = Modifier, ) { + val context = LocalContext.current val pageTitle = "$verb (${getLanguageDisplayName(languageAlias)})" val backLabel = stringResource(R.string.i18n_app_conjugate_title) + var conjugationData by remember(verb, languageAlias) { + mutableStateOf>>>?>(null) + } + + LaunchedEffect(verb, languageAlias) { + withContext(Dispatchers.IO) { + val loader = ContractDataLoader(context) + val contract = loader.loadContract(languageAlias) + if (contract != null) { + val fileManager = DatabaseFileManager(context) + val manager = ConjugateDataManager(fileManager) + + val structuredData = mutableMapOf>>>() + contract.conjugations.values.forEach { tenseGroup -> + val categories = mutableMapOf>>() + tenseGroup.tenses.values.forEach { conjugationCategory -> + val pairs = + conjugationCategory.tenseForms.values + .map { form -> + val resolvedForm = manager.getTheValueForTheConjugateWord(verb.lowercase(), form.value, languageAlias) + form.label to resolvedForm + }.filter { it.second.isNotEmpty() } + if (pairs.isNotEmpty()) { + categories[conjugationCategory.tenseTitle] = pairs + } + } + if (categories.isNotEmpty()) { + structuredData[tenseGroup.sectionTitle] = categories + } + } + conjugationData = structuredData + } + } + } + + var expanded by remember { mutableStateOf(false) } + var selectedTenseGroup by remember { mutableStateOf(null) } + val tenseGroupsList = conjugationData?.keys?.toList() ?: emptyList() + ScribeBaseScreen( pageTitle = pageTitle, lastPage = backLabel, @@ -109,8 +131,152 @@ fun ConjugationSelectionScreen( .padding(horizontal = Dimensions.PaddingMedium) .verticalScroll(rememberScrollState()), ) { - DUMMY_CONJUGATION_DATA.forEach { group -> - TenseGroupSection(group = group) + Spacer(modifier = Modifier.height(Dimensions.PaddingMedium)) + + Text( + text = stringResource(R.string.i18n_app_conjugate_choose_conjugation_title), + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(start = 4.dp, bottom = Dimensions.PaddingMedium), + ) + + Card( + modifier = + Modifier + .fillMaxWidth() + .padding(bottom = Dimensions.PaddingLarge) + .animateContentSize(), + shape = RoundedCornerShape(12.dp), + colors = + CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surface, + ), + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.2f)), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + ) { + Column(modifier = Modifier.fillMaxWidth()) { + Spacer(modifier = Modifier.height(12.dp)) + + // Dropdown Tense Selector (padded on sides) + Box( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .padding(bottom = 12.dp), + ) { + Row( + modifier = + Modifier + .fillMaxWidth() + .height(35.dp) + .background( + color = Color(0xFFFFA000), + shape = RoundedCornerShape(8.dp), + ).clickable { expanded = !expanded } + .padding(horizontal = Dimensions.PaddingMedium), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = selectedTenseGroup ?: stringResource(R.string.i18n_app_conjugate_choose_conjugation_select_tense), + color = Color.Black, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.labelMedium, + ) + Image( + painter = painterResource(id = R.drawable.ic_tab_rounded), + contentDescription = "Expand tenses", + colorFilter = ColorFilter.tint(Color.Black), + modifier = Modifier.size(18.dp), + ) + } + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = Modifier.fillMaxWidth(0.9f), + ) { + DropdownMenuItem( + text = { Text("All tenses") }, + onClick = { + selectedTenseGroup = null + expanded = false + }, + ) + tenseGroupsList.forEach { group -> + DropdownMenuItem( + text = { Text(group) }, + onClick = { + selectedTenseGroup = group + expanded = false + }, + ) + } + } + } + + // Display Conjugation Tense Groups flush inside this single Card + val dataToShow = conjugationData + Crossfade( + targetState = dataToShow, + label = "loadingTransition", + ) { currentData -> + if (currentData != null) { + if (currentData.isEmpty()) { + // Empty State + Box( + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 40.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = "Data not present in Wikidata", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) + } + } else { + Crossfade( + targetState = selectedTenseGroup, + label = "tenseGroupTransition", + ) { currentTenseGroup -> + val filteredGroups = + if (currentTenseGroup != null) { + currentData.filterKeys { it == currentTenseGroup } + } else { + currentData + } + + Column(modifier = Modifier.fillMaxWidth()) { + filteredGroups.entries.forEachIndexed { groupIndex, (groupTitle, categories) -> + TenseGroupGridSection(groupTitle = groupTitle, categories = categories) + } + Spacer(modifier = Modifier.height(Dimensions.PaddingLarge)) + } + } + } + } else { + // Loading Placeholder + Box( + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 40.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = "Loading conjugation tables...", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) + } + } + } + } } Spacer(modifier = Modifier.height(Dimensions.PaddingLarge)) } @@ -122,34 +288,137 @@ fun ConjugationSelectionScreen( // --------------------------------------------------------------------------- @Composable -private fun TenseGroupSection( - group: DummyTenseGroup, +private fun TenseGroupGridSection( + groupTitle: String, + categories: Map>>, modifier: Modifier = Modifier, ) { - Column(modifier = modifier) { + Column( + modifier = + modifier + .fillMaxWidth() + .padding(horizontal = 12.dp), + ) { Spacer(modifier = Modifier.height(Dimensions.PaddingLarge)) - Text( - text = group.sectionTitle, - style = MaterialTheme.typography.headlineMedium, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(start = 4.dp, bottom = Dimensions.PaddingSmall), - ) - + // Single Card containing BOTH the gray header bar and the conjugation cell grid Card( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(dimensionResource(R.dimen.rounded_corner_radius_standard)), + shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surface, ), - elevation = CardDefaults.cardElevation(defaultElevation = Dimensions.ElevationSmall), + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.2f)), + elevation = CardDefaults.cardElevation(defaultElevation = 1.dp), ) { Column(modifier = Modifier.fillMaxWidth()) { - group.rows.forEachIndexed { index, row -> - TenseRow(row = row) - if (index < group.rows.lastIndex) { + // 1. Header with gray background bar inside the Card (rounded top corners) + Box( + modifier = + Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.06f)) + .padding(horizontal = 16.dp, vertical = 8.dp), + ) { + Text( + text = groupTitle, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + ) + } + + // Divider line between Header and the first row of cells + Spacer( + modifier = + Modifier + .fillMaxWidth() + .height(1.dp) + .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)), + ) + + // 2. Grid Cells for categories + val showSubHeaders = categories.size > 1 + categories.entries.forEachIndexed { catIndex, (categoryTitle, forms) -> + if (showSubHeaders) { + Text( + text = categoryTitle, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + modifier = Modifier.padding(start = 12.dp, top = 8.dp, bottom = 4.dp), + ) + } + + val chunkedForms = forms.chunked(2) + chunkedForms.forEachIndexed { rowIndex, pair -> + Row( + modifier = + Modifier + .fillMaxWidth() + .height(100.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + if (pair.size > 1) { + // Left Cell + val left = pair[0] + Box( + modifier = + Modifier + .weight(1f) + .fillMaxHeight(), + ) { + ConjugationCell(label = left.first, form = left.second) + } + + // Vertical Divider + Box( + modifier = + Modifier + .width(1.dp) + .fillMaxHeight() + .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)), + ) + + // Right Cell + val right = pair[1] + Box( + modifier = + Modifier + .weight(1f) + .fillMaxHeight(), + ) { + ConjugationCell(label = right.first, form = right.second) + } + } else { + // Only 1 form in this row -> span full width, no vertical divider + val single = pair[0] + Box( + modifier = + Modifier + .fillMaxWidth() + .fillMaxHeight(), + ) { + ConjugationCell(label = single.first, form = single.second) + } + } + } + + // Horizontal Divider (between cell rows) + if (rowIndex < chunkedForms.lastIndex) { + Spacer( + modifier = + Modifier + .fillMaxWidth() + .height(1.dp) + .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)), + ) + } + } + + // Horizontal Divider (between categories) + if (catIndex < categories.size - 1) { Spacer( modifier = Modifier @@ -165,40 +434,62 @@ private fun TenseGroupSection( } @Composable -private fun TenseRow( - row: DummyTenseRow, +private fun ConjugationCell( + label: String, + form: String, modifier: Modifier = Modifier, ) { - Row( + val context = LocalContext.current + val clipboardManager = LocalClipboardManager.current + + Column( modifier = modifier - .fillMaxWidth() - .padding(horizontal = Dimensions.PaddingMedium, vertical = Dimensions.PaddingSmall), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.Top, + .fillMaxSize() + .clickable { + clipboardManager.setText(AnnotatedString(form)) + Toast + .makeText(context, "Copied \"$form\" to clipboard", Toast.LENGTH_SHORT) + .show() + }.padding(horizontal = 12.dp, vertical = 8.dp), + verticalArrangement = Arrangement.SpaceBetween, ) { - Text( - text = row.label, - style = MaterialTheme.typography.labelMedium, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurface, + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = label, + style = MaterialTheme.typography.bodySmall, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), + ) + Image( + painter = painterResource(id = R.drawable.ic_clipboard_vector), + contentDescription = "Copy conjugation", + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)), + modifier = + Modifier + .size(18.dp), + ) + } + + Box( modifier = Modifier - .weight(1f) - .padding(end = Dimensions.PaddingSmall), - ) - Column( - modifier = Modifier.weight(2f), - horizontalAlignment = Alignment.End, + .fillMaxWidth() + .weight(1f), + contentAlignment = Alignment.Center, ) { - row.forms.forEach { form -> - Text( - text = form, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface, - textAlign = TextAlign.End, - ) - } + Text( + text = form, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + textAlign = TextAlign.Center, + maxLines = 1, + ) } } } diff --git a/app/src/main/java/be/scri/ui/screens/about/AboutUtil.kt b/app/src/main/java/be/scri/ui/screens/about/AboutUtil.kt index 9782b47f0..ebb4d4cec 100644 --- a/app/src/main/java/be/scri/ui/screens/about/AboutUtil.kt +++ b/app/src/main/java/be/scri/ui/screens/about/AboutUtil.kt @@ -121,14 +121,18 @@ fun feedbackAndSupportList( onTutorialClick: () -> Unit, isConjugateApp: Boolean = false, ): List = - listOf( - ScribeItem.ExternalLinkItem( - leadingIcon = R.drawable.tutorial, - title = R.string.i18n_app_installation_button_quick_tutorial, - trailingIcon = R.drawable.right_arrow, - url = null, - onClick = { onTutorialClick() }, - ), + listOfNotNull( + if (!isConjugateApp) { + ScribeItem.ExternalLinkItem( + leadingIcon = R.drawable.tutorial, + title = R.string.i18n_app_installation_button_quick_tutorial, + trailingIcon = R.drawable.right_arrow, + url = null, + onClick = { onTutorialClick() }, + ) + } else { + null + }, ScribeItem.ExternalLinkItem( leadingIcon = R.drawable.star, title = diff --git a/app/src/main/res/drawable/ic_clipboard_vector.xml b/app/src/main/res/drawable/ic_clipboard_vector.xml index 7fc46c5f0..bf8c4063e 100644 --- a/app/src/main/res/drawable/ic_clipboard_vector.xml +++ b/app/src/main/res/drawable/ic_clipboard_vector.xml @@ -1,4 +1,9 @@ - - - - + + + diff --git a/app/src/main/res/values-ar/string.xml b/app/src/main/res/values-ar/string.xml index 15d67f342..a77195e11 100644 --- a/app/src/main/res/values-ar/string.xml +++ b/app/src/main/res/values-ar/string.xml @@ -68,7 +68,6 @@ لاختيار لوحات المفاتيح تثبيت لوحة المفاتيح تثبيت - توجد إعدادات التطبيق ولوحات المفاتيح اللغوية المثبتة هنا. تثبيت لوحات المفاتيح توضيح الاقتراحات/الاكتمال تسطير الاقتراحات والاكتملات لإظهار جنسها أثناء الكتابة. @@ -107,6 +106,7 @@ لغة الترجمة تغيير اللغة للترجمة منها. لغة مصدر الترجمة + توجد إعدادات التطبيق ولوحات المفاتيح اللغوية المثبتة هنا. الوضع الداكن تغيير عرض التطبيق إلى الوضع الداكن. لغة التطبيق diff --git a/app/src/main/res/values-de/string.xml b/app/src/main/res/values-de/string.xml index 2bf61ca55..392ff7ee4 100644 --- a/app/src/main/res/values-de/string.xml +++ b/app/src/main/res/values-de/string.xml @@ -84,7 +84,6 @@ um Tastaturen auszuwählen Tastaturinstallation Installation - Hier sind die Einstellungen der App und installierte Tastaturen zu finden. Tastaturen installieren Vorschläge annotieren Vorschläge unterstreichen, um ihr Genus beim Tippen anzuzeigen. @@ -125,6 +124,7 @@ Übersetzungssprache Wähle die Sprache, von der übersetzt wird. Ausgangssprache für die Übersetzung + Hier sind die Einstellungen der App und installierte Tastaturen zu finden. Dunkler Modus Die App-Darstellung auf dunkel umstellen. App-Sprache diff --git a/app/src/main/res/values-el/string.xml b/app/src/main/res/values-el/string.xml index 9778bdc27..8d0e969ad 100644 --- a/app/src/main/res/values-el/string.xml +++ b/app/src/main/res/values-el/string.xml @@ -93,7 +93,6 @@ για να επιλέξετε πληκτρολόγια Εγκατάσταση πληκτρολογίου Εγκατάσταση - Οι ρυθμίσεις για την εφαρμογή και τα εγκατεστημένα πληκτρολόγια γλώσσας βρίσκονται εδώ. Εγκατάσταση πληκτρολογίων Σήμανση προτάσεων/συμπληρώσεων Υπογράμμιση προτάσεων και συμπληρώσεων για εμφάνιση του γένους τους καθώς πληκτρολογείτε. @@ -136,6 +135,7 @@ Γλώσσα μετάφρασης Αλλαγή της γλώσσας από την οποία γίνεται η μετάφραση. Γλώσσα πηγής μετάφρασης + Οι ρυθμίσεις για την εφαρμογή και τα εγκατεστημένα πληκτρολόγια γλώσσας βρίσκονται εδώ. Σκοτεινή λειτουργία Αλλαγή της εμφάνισης της εφαρμογής σε σκοτεινή λειτουργία. Γλώσσα εφαρμογής diff --git a/app/src/main/res/values-es/string.xml b/app/src/main/res/values-es/string.xml index 45b1fbe61..ed47ca259 100644 --- a/app/src/main/res/values-es/string.xml +++ b/app/src/main/res/values-es/string.xml @@ -68,7 +68,6 @@ para seleccionar teclados Instalación del teclado Instalación - La configuración de la aplicación y los teclados de idiomas instalados se encuentran aquí. Instalar teclados Anotar, sugerir/completar Subraya sugerencias y terminaciones para mostrar sus géneros mientras escribes. @@ -107,6 +106,7 @@ Idioma de la traducción Elige un idioma para traducir Idioma de origen de la traducción + La configuración de la aplicación y los teclados de idiomas instalados se encuentran aquí. Modo oscuro Cambia la visualización de la aplicación al modo oscuro. Idioma de la aplicación diff --git a/app/src/main/res/values-fr/string.xml b/app/src/main/res/values-fr/string.xml index af6a5a5ee..f8e002d10 100644 --- a/app/src/main/res/values-fr/string.xml +++ b/app/src/main/res/values-fr/string.xml @@ -68,7 +68,6 @@ pour sélectionner le clavier Installation du clavier Installation - Les paramètres de l\'application et des claviers linguistiques installés se trouvent ici. Installer les claviers Annoter, suggérer/compléter Souligner les suggestions et les complétions pour indiquer leur genre au fur et à mesure de la saisie. @@ -107,6 +106,7 @@ Langue de traduction Modifier la langue à partir de laquelle la traduction doit être effectuée. Langue source de la traduction + Les paramètres de l\'application et des claviers linguistiques installés se trouvent ici. Thème sombre Modifier l\'affichage de l\'application en mode sombre. Langue de l\'application diff --git a/app/src/main/res/values-hi/string.xml b/app/src/main/res/values-hi/string.xml index af6fd674b..33af5e9eb 100644 --- a/app/src/main/res/values-hi/string.xml +++ b/app/src/main/res/values-hi/string.xml @@ -74,7 +74,6 @@ कीबोर्ड चुनने के लिए कीबोर्ड इंस्टॉलेशन इंस्टॉलेशन - ऐप और इंस्टॉल किए गए भाषा कीबोर्ड की सेटिंग्स यहाँ मिलेंगी। कीबोर्ड इंस्टॉल करें अनोटेट सुझाव/समाप्ति टाइप करते समय सुझाव और समाप्ति को रेखांकित करें ताकि उनके लिंग दिख सकें। @@ -113,6 +112,7 @@ अनुवाद भाषा जिस भाषा से अनुवाद करना है, उसे बदलें। अनुवाद स्रोत भाषा + ऐप और इंस्टॉल किए गए भाषा कीबोर्ड की सेटिंग्स यहाँ मिलेंगी। डार्क मोड ऐप प्रदर्शनी को डार्क मोड में बदलें। ऐप की भाषा diff --git a/app/src/main/res/values-id/string.xml b/app/src/main/res/values-id/string.xml index 818079406..99200fd46 100644 --- a/app/src/main/res/values-id/string.xml +++ b/app/src/main/res/values-id/string.xml @@ -68,7 +68,6 @@ untuk memilih keyboard Instalasi Keyboard Instalasi - Pengaturan aplikasi dan keyboard bahasa yang terpasang bisa ditemukan di sini. Pasang keyboard Anotasi saran/pelengkapan Garis bawahi saran dan pelengkapan kata untuk menunjukkan gender saat kamu mengetik. @@ -107,6 +106,7 @@ Bahasa terjemahan Ubah bahasa untuk diterjemahkan dari. Bahasa asal terjemahan + Pengaturan aplikasi dan keyboard bahasa yang terpasang bisa ditemukan di sini. Mode gelap Aktifkan mode gelap untuk tampilan aplikasi. Bahasa aplikasi diff --git a/app/src/main/res/values-in/string.xml b/app/src/main/res/values-in/string.xml index 818079406..99200fd46 100644 --- a/app/src/main/res/values-in/string.xml +++ b/app/src/main/res/values-in/string.xml @@ -68,7 +68,6 @@ untuk memilih keyboard Instalasi Keyboard Instalasi - Pengaturan aplikasi dan keyboard bahasa yang terpasang bisa ditemukan di sini. Pasang keyboard Anotasi saran/pelengkapan Garis bawahi saran dan pelengkapan kata untuk menunjukkan gender saat kamu mengetik. @@ -107,6 +106,7 @@ Bahasa terjemahan Ubah bahasa untuk diterjemahkan dari. Bahasa asal terjemahan + Pengaturan aplikasi dan keyboard bahasa yang terpasang bisa ditemukan di sini. Mode gelap Aktifkan mode gelap untuk tampilan aplikasi. Bahasa aplikasi diff --git a/app/src/main/res/values-kn/string.xml b/app/src/main/res/values-kn/string.xml index 93173914d..b55fb2abc 100644 --- a/app/src/main/res/values-kn/string.xml +++ b/app/src/main/res/values-kn/string.xml @@ -68,7 +68,6 @@ ಕೀಬೋರ್ಡ್‌ಗಳನ್ನು ಆಯ್ಕೆ ಮಾಡಲು ಕೀಬೋರ್ಡ್ ಇನ್‌ಸ್ಟಾಲೇಷನ್ ಇನ್‌ಸ್ಟಾಲೇಷನ್ - ಅಪ್ಲಿಕೇಶನ್ ಮತ್ತು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿದ ಭಾಷಾ ಕೀಬೋರ್ಡ್‌ಗಳಿಗಾಗಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಇಲ್ಲಿ ಲಭ್ಯವಿದೆ. ಕೀಬೋರ್ಡ್‌ಗಳನ್ನು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ ಸೂಚನೆಗಳನ್ನು ಎದ್ದುಕಾಣುವಂತೆ ಮಾಡಿ ನೀವು ಟೈಪ್ ಮಾಡುವಾಗ ಅವುಗಳ ಲಿಂಗಗಳನ್ನು ತೋರಿಸಲು ಸೂಚನೆಗಳು ಮತ್ತು ಪೂರ್ಣಗೊಳಿಸುವಿಕೆಯನ್ನು ಅಂಡರ್‌ಲೈನ್ ಮಾಡಿ. @@ -109,6 +108,7 @@ ಅನುವಾದ ಭಾಷೆ ಯಾವ ಭಾಷೆಯಿಂದ ಅನುವಾದಿಸಬೇಕು ಎಂಬುದನ್ನು ಬದಲಾಯಿಸಿ. ಅನುವಾದ ಮೂಲ ಭಾಷೆ + ಅಪ್ಲಿಕೇಶನ್ ಮತ್ತು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿದ ಭಾಷಾ ಕೀಬೋರ್ಡ್‌ಗಳಿಗಾಗಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಇಲ್ಲಿ ಲಭ್ಯವಿದೆ. ಡಾರ್ಕ್ ಮೋಡ್ ಅಪ್ಲಿಕೇಶನ್ ಪ್ರದರ್ಶನವನ್ನು ಡಾರ್ಕ್ ಮೋಡ್‌ಗೆ ಬದಲಾಯಿಸಿ. ಆಪ್ ಭಾಷೆ diff --git a/app/src/main/res/values-ko/string.xml b/app/src/main/res/values-ko/string.xml index 4fc56a669..4cc4bc6fd 100644 --- a/app/src/main/res/values-ko/string.xml +++ b/app/src/main/res/values-ko/string.xml @@ -68,7 +68,6 @@ 키보드를 선택할 수 있습니다. 키보드 설치 설치 - 앱 설정과 설치된 언어 키보드는 여기서 찾을 수 있습니다. 키보드 설치하기 추천 및 완성 설명 입력 시 성별에 따라 다른 단어가 제안될 때, 해당 단어에 밑줄 쳐서 성별을 나타낸다. @@ -107,6 +106,7 @@ 번역 언어 번역할 언어를 변경하세요. 번역할 언어 + 앱 설정과 설치된 언어 키보드는 여기서 찾을 수 있습니다. 다크 모드 앱 디스플레이를 다크 모드로 변경합니다. 언어 설정 diff --git a/app/src/main/res/values-mr/string.xml b/app/src/main/res/values-mr/string.xml index 897997790..03a355b73 100644 --- a/app/src/main/res/values-mr/string.xml +++ b/app/src/main/res/values-mr/string.xml @@ -68,7 +68,6 @@ कीबोर्ड निवडण्यासाठी कीबोर्ड इंस्टॉलेशन इंस्टॉलेशन - अ‍ॅप आणि इंस्टॉल केलेल्या भाषा कीबोर्डची सेटिंग्ज इथे मिळतील. कीबोर्ड इंस्टॉल करा सुचवण्या/समाप्ति वर्णन करा टाइप करताना सुचवण्या आणि समाप्ति रेखांकित करा जेणेकरून त्यांच्या लिंगाची माहिती मिळू शकेल. @@ -107,6 +106,7 @@ अनुवाद भाषा अनुवाद करायची भाषा बदला. अनुवाद स्रोत भाषा + अ‍ॅप आणि इंस्टॉल केलेल्या भाषा कीबोर्डची सेटिंग्ज इथे मिळतील. डार्क मोड अ‍ॅप डिस्प्ले डार्क मोडमध्ये बदला. अ‍ॅपची भाषा diff --git a/app/src/main/res/values-ne/string.xml b/app/src/main/res/values-ne/string.xml index 790ec6ac5..2563b329d 100644 --- a/app/src/main/res/values-ne/string.xml +++ b/app/src/main/res/values-ne/string.xml @@ -1,10 +1,153 @@ बारेमा + Android चेन्जलग + उपलब्ध डेटा + ब्लग पोस्टहरू + परिवर्तन लग + कन्जुगेट एप्स पूर्वाधार + डेटा सर्भर छाप + iOS परिवर्तन लग + किबोर्ड एप्स थप जान्नुहोस् + कानुनी + हाम्रो समुदाय गोपनीयता नीति + स्क्राइबलाई समर्थन गर्नुहोस् समर्थकहरू ट्रेडमार्क नीति + डेटा डाउनलोड गर्नुहोस् + अंग्रेजी + फ्रान्सेली + जर्मन + इन्डोनेसियन + इटालियन + नर्वेजियन + पोर्चुगिज + रसियन + स्पेनिस् + स्विडिस् + यहाँ तपाईंले स्क्राइब र यसको समुदायबारे थप जान्न सक्नुहुन्छ। + गिटहबमा कोड हेर्नुहोस् + मास्टोडनमा फलो गर्नुहोस् + म्याट्रिक्समा टिमसँग कुराकानी गर्नुहोस् + स्क्राइब कन्जुगेट साझा गर्नुहोस् + स्क्राइब साझा गर्नुहोस् + समुदाय + स्क्राइब वेबसाइट भ्रमण गर्नुहोस् + विकिमिडिया र स्क्राइब + हामी कसरी साथ मिलेर काम गर्छौं + विकिमिडिया योगदानकर्ताहरूको अनगिन्ती योगदान बिना स्क्राइब सम्भव हुने थिएन। स्क्राइबले विकिडेटा शब्दकोश डेटा समुदायको डेटा र स्क्राइबले समर्थन गर्ने प्रत्येक भाषाका लागि विकिपिडियाको डेटा उपयोग गर्छ। + विकिडेटा विकिमिडिया फाउन्डेसनद्वारा संचालित सहयोगी रूपमा सम्पादित बहुभाषिक ज्ञान ग्राफ हो। यसले CC0 लाइसेन्स अन्तर्गत स्वतन्त्र रूपमा उपलब्ध डेटा प्रदान गर्छ। + विकिपिडिया स्वयंसेवकहरूको समुदायद्वारा लेखित र संचालित बहुभाषिक मुक्त ऑनलाइन विश्वकोश हो। स्क्राइबले भाषामा सबैभन्दा सामान्य शब्दहरू निकाल्न विकिपिडियाको डेटा उपयोग गर्छ। + बग रिपोर्ट गर्नुहोस् + स्क्राइब कन्जुगेटलाई मूल्याङ्कन गर्नुहोस् + स्क्राइबलाई मूल्याङ्कन गर्नुहोस् + एप संकेतहरू रिसेट गर्नुहोस् + हामीलाई इमेल पठाउनुहोस् + प्रतिक्रिया र समर्थन + संस्करण + तपाईंलाई सुरक्षित राख्दै + कृपया ध्यान दिनुहोस् कि यस नीतिको अंग्रेजी संस्करण अन्य सबै संस्करणभन्दा माथि रहन्छ। + तृतीय-पक्ष लाइसेन्सहरू + हामीले कुन कोड प्रयोग गर्यौं + Custom Keyboard\n• लेखक: EthanSK\n• लाइसेन्स: MIT\n• लिङ्क: https://github.com/EthanSK/CustomKeyboard/blob/master/LICENSE + Simple Keyboard\n• लेखक: Simple Mobile Tools\n• लाइसेन्स: GPL-3.0\n• लिङ्क: https://github.com/SimpleMobileTools/Simple-Keyboard/blob/main/LICENSE + स्क्राइब विकासकर्ताहरूले तृतीय-पक्ष कोड प्रयोग गरेर iOS एप्लिकेसन बनाएका छन्। + बारेमा + काल चयन गर्नुहोस् + तलको कन्जुगेसन चयन गर्नुहोस् + हालै कन्जुगेट गरिएका + कन्जुगेट + क्रियाहरू खोज्नुहोस् + क्रियाहरू कन्जुगेट गर्नुहोस् + स्क्राइब कन्जुगेटमा नयाँ डेटा थप्नुहोस्। + क्रिया डेटा डाउनलोड गर्नुहोस् + कन्जुगेट सुरु गर्न डेटा डाउनलोड गर्नुहोस्! + क्रिया डेटा + स्क्राइब किबोर्डमा नयाँ डेटा थप्नुहोस्। + किबोर्ड डेटा डाउनलोड गर्नुहोस् + भाषा डेटा + सबै भाषाहरू + डाउनलोड हुँदैछ + डाउनलोड गर्न डेटा चयन गर्नुहोस् + अद्यावधिक + अपडेट गर्नुहोस् + तपाईंसँग हाल कुनै स्क्राइब किबोर्ड स्थापना गरिएको छैन। किबोर्ड स्थापना गर्न तलको बटन क्लिक गर्नुहोस्। + भाषा बद्ल्नुहोस् + तपाईंले डाउनलोड गर्नुहुने डेटाले तपाईंलाई {source_language} बाट {target_language} मा अनुवाद गर्न दिनेछ। क्षमा गर्नुहोस्, कुन भाषाबाट अनुवाद गर्ने भाषा बद्ल्नुहुन्छ? + {source_language} प्रयोग गर्नुहोस् + डेटा अपडेट गर्नुहोस् + नयाँ डेटा जाँच गर्नुहोस् + नियमित रूपमा डेटा अपडेट गर्नुहोस् + तपाईंको उपकरणमा स्क्राइब किबोर्ड स्थापना गर्न तलका निर्देशनहरू पालना गर्नुहोस्। + छिटो ट्युटोरियल + किबोर्ड सेटिङ्स खोल्नुहोस् + किबोर्डहरू + स्क्राइब सेटिङ्स खोल्नुहोस् + चयन गर्नुहोस् + तपाईंले प्रयोग गर्न चाहनुभएका किबोर्डहरू सक्रिय गर्नुहोस् + टाइप गर्दा, थिच्नुहोस् + किबोर्ड चयन गर्न + किबोर्ड स्थापना + स्थापना + किबोर्ड स्थापना गर्नुहोस् + सुझाव/पूर्ण गर्नुहोस् टिप्पणी गर्नुहोस् + टाइप गर्दा लिङ्ग देखाउन सुझाव र पूर्णताहरूलाई रेखाङ्कन गर्नुहोस्। + इमोजी स्वतः सुझाव + थप अभिव्यक्तिपूर्ण टाइपिङ्का लागि इमोजी सुझाव र पूर्णता चालू गर्नुहोस्। + पूर्वनिर्धारित इमोजी चमडाको रङ + प्रयोग गरिने चमडाको रङ + इमोजी स्वतः सुझाव र पूर्णताका लागि पूर्वनिर्धारित चमडाको रङ सेट गर्नुहोस्। + लंबो थिच्नेमा शब्द दर शब्द मेट्नुहोस् + मेट्ने कुञजी थिचिरहेको बेला शब्द दर शब्द पाठ मेट्नुहोस्। + दोहोरो स्पेस पिरियड + स्पेस कुञजी दुई पटक थिच्नेमा स्वतः पिरियड राख्नुहोस्। + वैकल्पिक अक्षरहरूका लागि थिच्नुहोस् + कुञजीहरू थिचेर इच्छित अक्षरमा तानेर वैकल्पिक अक्षरहरू चयन गर्नुहोस्। + कुञजी थिच्नेमा पपअप देखाउनुहोस् + कुञजीहरू थिचिएको बेला पपअप देखाउनुहोस्। + विराम चिह्न स्पेसिङ् मेट्नुहोस् + विराम चिह्नहरूअगाडि अतिरिक्त स्पेस हटाउनुहोस्। + कार्यक्षमता + कुञजी थिच्नेमा आवाज बजाउनुहोस् + कुञजीहरू थिचिएको बेला उपकरणले आवाज बजाउने गराउनुहोस्। + कुञजी थिच्नेमा कम्पन गर्नुहोस् + कुञजीहरू थिचिएको बेला उपकरणले कम्पन गर्ने गराउनुहोस्। + पूर्वनिर्धारित मुद्रा चिह्न + 123 कुञजीका लागि चिह्न + नंबर कुञजीमा कुन मुद्रा चिह्न देखाउने चयन गर्नुहोस्। + पूर्वनिर्धारित किबोर्ड + प्रयोग गर्ने लेआउट + तपाईंको टाइपिङ् प्राथमिकता र भाषा आवश्यकताअनुसार किबोर्ड लेआउट चयन गर्नुहोस्। + उच्चारण अक्षरहरू अक्षम गर्नुहोस् + मुख्य किबोर्ड लेआउटबाट उच्चारण अक्षर कुञजीहरू हटाउनुहोस्। + ABC मा पिरियड र कमा + सुविधाजनक टाइपिङ्का लागि मुख्य किबोर्डमा पिरियड र कमा कुञजीहरू सामेल गर्नुहोस्। + लेआउट + स्थापित किबोर्ड चयन गर्नुहोस् + तपाईंले अनुवाद क्षमताको भाषा बद्ल्नुभएको छ। क्षमा गर्नुहोस्, {source_language} बाट अनुवाद गर्न नयाँ डेटा डाउनलोड गर्नुहुन्छ? + {source_language} राख्नुहोस् + भाषा चयन गर्नुहोस् + स्रोत भाषा कुन हो + अनुवाद भाषा + जुन भाषाबाट अनुवाद गर्ने हो त्यो बद्ल्नुहोस्। + अनुवाद स्रोत भाषा + एप र स्थापित भाषा किबोर्डहरूका सेटिङ्स यहाँ पाइन्छ। + डार्क मोड + एप्लिकेसन प्रदर्शन डार्क मोडमा बद्ल्नुहोस्। + एप भाषा + एप पाठहरूका लागि भाषा चयन गर्नुहोस् + तपाईंको उपकरणमा एउटा मात्र भाषा स्थापना गरिएको छ। कृपया सेटिङ्समा थप भाषाहरू स्थापना गर्नुहोस्। + एउटा मात्र उपकरण भाषा + स्क्राइब एप कुन भाषामा छ त्यो बद्ल्नुहोस्। + उच्च रङ कन्ट्रास्ट + सुधारित पहुँच र स्पष्ट देख्ने अनुभवका लागि रङ कन्ट्रास्ट बढाउनुहोस्। + एप पाठ आकार बढाउनुहोस् + राम्रो पठनीयताका लागि मेनु पाठहरूको आकार बढाउनुहोस्। + एप सेटिङ्स + सेटिङ्स + अनुवाद diff --git a/app/src/main/res/values-pt/string.xml b/app/src/main/res/values-pt/string.xml index ea347acfe..7c2695abb 100644 --- a/app/src/main/res/values-pt/string.xml +++ b/app/src/main/res/values-pt/string.xml @@ -68,7 +68,6 @@ para selecionar teclados Como instalar um teclado Instalação - Aqui você encontra as configurações do aplicativo e teclados de idioma instalados. Instalar teclados Sugestões e autocompletar Sublinhar sugestões para indicar o gênero enquanto você digita. @@ -107,6 +106,7 @@ Idioma da tradução Altere o idioma a ser traduzido. Idioma de origem da tradução + Aqui você encontra as configurações do aplicativo e teclados de idioma instalados. Modo escuro Muda o visual do aplicativo para o modo escuro. Idioma do aplicativo diff --git a/app/src/main/res/values-sv/string.xml b/app/src/main/res/values-sv/string.xml index fddc101db..f2b9eaa90 100644 --- a/app/src/main/res/values-sv/string.xml +++ b/app/src/main/res/values-sv/string.xml @@ -68,7 +68,6 @@ För att välja tangentbord Tangentbords-installation Installation - Inställningar för appen och installerade tangentbord finns här. Installera tangentbord Kommentera förslag/kompletteringar Stryk under förslag och kompletteringar och visa deras genus medan du skriver. @@ -107,6 +106,7 @@ Språk för översättning Välj ett språk att översätta ifrån Källspråk för översättning + Inställningar för appen och installerade tangentbord finns här. Mörkt läge Ändra programmets visning till mörkt läge. App-språk diff --git a/app/src/main/res/values-ta/string.xml b/app/src/main/res/values-ta/string.xml index 399687e76..ad707775f 100644 --- a/app/src/main/res/values-ta/string.xml +++ b/app/src/main/res/values-ta/string.xml @@ -74,7 +74,6 @@ விசைப்பலகைகளைத் தேர்ந்தெடுக்க விசைப்பலகை நிறுவல் நிறுவல் - பயன்பாடு மற்றும் நிறுவப்பட்ட மொழி விசைப்பலகைகளுக்கான அமைப்புகள் இங்கே காணப்படுகின்றன. விசைப்பலகைகளை நிறுவுங்கள் பரிந்துரை/நிறைவுகளைக் குறிப்பிடுங்கள் நீங்கள் தட்டச்சு செய்யும் போது அவற்றின் பாலினத்தைக் காட்ட பரிந்துரைகள் மற்றும் நிறைவுகளுக்கு அடிக்கோடு இடுங்கள். @@ -115,6 +114,7 @@ மொழிபெயர்ப்பு மொழி எந்த மொழியிலிருந்து மொழிபெயர்க்க வேண்டும் என்பதை மாற்றவும். மொழிபெயர்ப்பு மூல மொழி + பயன்பாடு மற்றும் நிறுவப்பட்ட மொழி விசைப்பலகைகளுக்கான அமைப்புகள் இங்கே காணப்படுகின்றன. இருள் முறை பயன்பாட்டுக் காட்சியை இருள் முறைக்கு மாற்றவும். பயன்பாட்டு மொழி diff --git a/app/src/main/res/values-tr/string.xml b/app/src/main/res/values-tr/string.xml index c2893fd01..8cd39b849 100644 --- a/app/src/main/res/values-tr/string.xml +++ b/app/src/main/res/values-tr/string.xml @@ -69,7 +69,6 @@ klavyeleri seçmek için Klavye kurulumu Kurulum - Uygulama ve yüklenmiş dil klavyelerinin ayarlarını burada bulun. Klavye yükle Önerileri/otomatik tamamlamayı etkinleştir Yazarken önerilerin ve otomatik tamamlamaların altını çizerek cinsiyetlerini gösterin. @@ -110,6 +109,7 @@ Çeviri dili Çevrilecek dili değiştirin. Çeviri kaynak dili + Uygulama ve yüklenmiş dil klavyelerinin ayarlarını burada bulun. Karanlık mod Uygulama görünümünü karanlık mod olarak değiştirin. Uygulama dili diff --git a/app/src/main/res/values/string.xml b/app/src/main/res/values/string.xml index 4cc3f4814..fcb056bef 100644 --- a/app/src/main/res/values/string.xml +++ b/app/src/main/res/values/string.xml @@ -57,12 +57,21 @@ Simple Keyboard\n• Author: Simple Mobile Tools\n• License: GPL-3.0\n• Link: https://github.com/SimpleMobileTools/Simple-Keyboard/blob/main/LICENSE The Scribe developers (SCRIBE) built the iOS application "Scribe - Language Keyboards" (SERVICE) using third party code. All source code used in the creation of this SERVICE comes from sources that allow its full use in the manner done so by the SERVICE. This section lists the source code on which the SERVICE was based as well as the coinciding licenses of each.\n\nThe following is a list of all used source code, the main author or authors of the code, the license under which it was released at time of usage, and a link to the license. About + Download verb data with the menu below to start conjugating verbs. Select tense Choose a conjugation below Recently conjugated Conjugate Search for verbs Conjugate verbs + Database error: {error} + Decoding failed: {error} + Download failed: {error} + Invalid server response + Invalid URL + Network error: {error} + Server error: {code} + Download timed out Add new data to Scribe Conjugate. Download verb data Download data to start conjugating! @@ -70,11 +79,17 @@ Add new data to Scribe keyboards. Download keyboard data Language data + {language} conjugate data is already up to date + {language} conjugate data downloaded successfully! All languages + {language} data is already up to date + {language} data downloaded successfully! Downloading + Already up to date! Select data to download Up to date Update + Update all You currently do not have any Scribe keyboard installed. Please click the Install keyboards button below to install a Scribe keyboard and then come back to download the needed data. Change language The data you will download will allow you to translate from {source_language} to {target_language}. Do you want to change the language you\'ll translate from? @@ -93,8 +108,8 @@ to select keyboards Keyboard installation Installation - Settings for the app and installed language keyboards are found here. Install keyboards + Settings for the app\'s language and interface are found here. Annotate suggest/complete Underline suggestions and completions to show their genders as you type. Autosuggest emojis @@ -136,6 +151,7 @@ Translation language Change the language to translate from. Translation source language + Settings for the app and installed language keyboards are found here. Dark mode Change the application display to dark mode. App language diff --git a/app/src/test/kotlin/be/scri/ui/screens/about/AboutUtilTest.kt b/app/src/test/kotlin/be/scri/ui/screens/about/AboutUtilTest.kt index 7aeeda5e0..00e850a32 100644 --- a/app/src/test/kotlin/be/scri/ui/screens/about/AboutUtilTest.kt +++ b/app/src/test/kotlin/be/scri/ui/screens/about/AboutUtilTest.kt @@ -214,7 +214,7 @@ class AboutUtilTest { onResetHintsClick = {}, isConjugateApp = true, ) - assertEquals(R.string.i18n_app_about_feedback_rate_conjugate, list[1].title) // Updated to 1 + assertEquals(R.string.i18n_app_about_feedback_rate_conjugate, list[0].title) } @Test