Separate screen for OCR languages
This commit is contained in:
@@ -69,7 +69,9 @@ import org.fairscan.app.ui.screens.export.ExportEvent
|
||||
import org.fairscan.app.ui.screens.export.ExportResult
|
||||
import org.fairscan.app.ui.screens.export.ExportScreenWrapper
|
||||
import org.fairscan.app.ui.screens.export.ExportViewModel
|
||||
import org.fairscan.app.ui.screens.settings.OcrLanguagesScreen
|
||||
import org.fairscan.app.ui.screens.settings.SettingsScreen
|
||||
import org.fairscan.app.ui.screens.settings.SettingsUiState
|
||||
import org.fairscan.app.ui.screens.settings.SettingsViewModel
|
||||
import org.fairscan.app.ui.theme.FairScanTheme
|
||||
import org.opencv.android.OpenCVLoader
|
||||
@@ -126,6 +128,7 @@ class MainActivity : ComponentActivity() {
|
||||
val documentUiState by viewModel.documentUiState.collectAsStateWithLifecycle()
|
||||
val cropInitialState by viewModel.cropInitState.collectAsStateWithLifecycle()
|
||||
val exportUiState by exportViewModel.uiState.collectAsStateWithLifecycle()
|
||||
val settingsUiState by settingsViewModel.uiState.collectAsStateWithLifecycle()
|
||||
val cameraPermission = rememberCameraPermissionState()
|
||||
CollectCameraEvents(cameraViewModel, viewModel)
|
||||
CollectExportEvents(context, exportViewModel)
|
||||
@@ -237,7 +240,16 @@ class MainActivity : ComponentActivity() {
|
||||
LibrariesScreen(onBack = navigation.back)
|
||||
}
|
||||
is Screen.Overlay.Settings -> {
|
||||
SettingsScreenWrapper(settingsViewModel, navigation, logger)
|
||||
SettingsScreenWrapper(settingsUiState, settingsViewModel, navigation, logger)
|
||||
}
|
||||
is Screen.Overlay.OcrLanguages -> {
|
||||
OcrLanguagesScreen(
|
||||
uiState = settingsUiState,
|
||||
onBack = navigation.back,
|
||||
onLanguageClick = settingsViewModel::onLanguageClick,
|
||||
onRemoveLanguage = settingsViewModel::onRemoveLanguage,
|
||||
onCancelOcrDownload = settingsViewModel::cancelOcrDownload,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,6 +270,7 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
@Composable
|
||||
private fun SettingsScreenWrapper(
|
||||
settingsUiState: SettingsUiState,
|
||||
settingsViewModel: SettingsViewModel,
|
||||
nav: Navigation,
|
||||
logger: FileLogger,
|
||||
@@ -277,7 +290,6 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
val settingsUiState by settingsViewModel.uiState.collectAsStateWithLifecycle()
|
||||
LaunchedEffect(Unit) {
|
||||
settingsViewModel.refreshExportDirName()
|
||||
}
|
||||
@@ -296,11 +308,7 @@ class MainActivity : ComponentActivity() {
|
||||
onResetExportDirClick = { settingsViewModel.setExportDirUri(null) },
|
||||
onExportFormatChanged = { format -> settingsViewModel.setExportFormat(format) },
|
||||
onExportQualityChanged = { quality -> settingsViewModel.setExportQuality(quality) },
|
||||
onInstallOcrLanguage = settingsViewModel::installLanguage,
|
||||
onCancelOcrDownload = settingsViewModel::cancelOcrDownload,
|
||||
onEnableOcrLanguage = settingsViewModel::setOcrLanguageEnabled,
|
||||
onDeleteUnusedOcrLanguages = settingsViewModel::deleteUnusedOcrLanguages,
|
||||
onBack = nav.back,
|
||||
navigation = nav,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -484,6 +492,7 @@ class MainActivity : ComponentActivity() {
|
||||
viewModel.navigateTo(Screen.Overlay.Settings)
|
||||
}
|
||||
},
|
||||
toOcrLanguagesScreen = { viewModel.navigateTo(Screen.Overlay.OcrLanguages) },
|
||||
back = {
|
||||
val origin = viewModel.currentScreen.value
|
||||
viewModel.navigateBack()
|
||||
|
||||
@@ -143,13 +143,6 @@ class OcrLanguageRepository(
|
||||
setLanguageEnabled(code, false)
|
||||
}
|
||||
|
||||
suspend fun deleteInactiveLanguages() {
|
||||
val enabled = enabledLanguagesValue()
|
||||
getInstalledLanguages()
|
||||
.filter { it !in enabled }
|
||||
.forEach { code -> deleteLanguage(code) }
|
||||
}
|
||||
|
||||
suspend fun buildTesseractLanguageString(): String {
|
||||
return getActiveLanguages()
|
||||
.sorted()
|
||||
|
||||
@@ -25,6 +25,7 @@ sealed class Screen {
|
||||
object About : Overlay()
|
||||
object Libraries : Overlay()
|
||||
object Settings : Overlay()
|
||||
object OcrLanguages : Overlay()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +37,7 @@ data class Navigation(
|
||||
val toAboutScreen: () -> Unit,
|
||||
val toLibrariesScreen: () -> Unit,
|
||||
val toSettingsScreen: (() -> Unit)?,
|
||||
val toOcrLanguagesScreen: () -> Unit,
|
||||
val back: () -> Unit,
|
||||
val shouldDisplayBackButton: () -> Boolean,
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.fairscan.app.ui.state.PageThumbnail
|
||||
import org.fairscan.imageprocessing.ColorMode
|
||||
|
||||
fun dummyNavigation(): Navigation {
|
||||
return Navigation({}, {}, {}, {}, {}, {}, {}, {}, { -> true})
|
||||
return Navigation({}, {}, {}, {}, {}, {}, {}, {}, {}, { -> true})
|
||||
}
|
||||
|
||||
fun fakeDocument(pageIds: ImmutableList<String>, context: Context): DocumentUiModel {
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025-2026 The FairScan authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.fairscan.app.ui.screens.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.text.format.Formatter
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.fairscan.app.R
|
||||
import org.fairscan.app.data.OcrLanguage
|
||||
|
||||
@Composable
|
||||
fun AddLanguageBottomSheetContent(
|
||||
uiState: SettingsUiState,
|
||||
onDismiss: () -> Unit,
|
||||
onInstallLanguage: (String) -> Unit,
|
||||
) {
|
||||
val locale = Locale.current.platformLocale
|
||||
|
||||
val availableLanguages = remember(uiState.installedOcrLanguages, locale) {
|
||||
OcrLanguage.AVAILABLE_LANGUAGE_CODES
|
||||
.filter { it !in uiState.installedOcrLanguages }
|
||||
.map { code ->
|
||||
val language = OcrLanguage(code)
|
||||
language to language.displayName(locale)
|
||||
}
|
||||
.sortedBy { (_, name) -> name }
|
||||
}
|
||||
|
||||
val deviceLanguage = locale.displayLanguage
|
||||
val suggested =
|
||||
availableLanguages
|
||||
.map { it.first }
|
||||
// Comparing language code doesn't work for some languages (e.g. English)
|
||||
// because we compare a 2-letter code with a 3-letter one (coming from Tesseract).
|
||||
.firstOrNull { it.locale.displayLanguage == deviceLanguage }
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.Top,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 8.dp, top = 0.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_ocr_download_intro),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(0.dp)
|
||||
)
|
||||
IconButton(onDismiss) {
|
||||
Icon(Icons.Default.Close, contentDescription = stringResource(R.string.close))
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
if (suggested != null) {
|
||||
item { TextItem(stringResource(R.string.settings_ocr_suggested)) }
|
||||
item {
|
||||
val displayName = suggested.displayName(locale)
|
||||
LanguageItem(suggested, displayName, onInstallLanguage)
|
||||
}
|
||||
item { HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) }
|
||||
}
|
||||
item { TextItem(stringResource(R.string.settings_ocr_all_languages)) }
|
||||
items(
|
||||
availableLanguages.filter { (lang, _) -> lang != suggested },
|
||||
key = { (lang, _) -> lang.code }
|
||||
) { (lang, displayName) ->
|
||||
LanguageItem(lang, displayName, onInstallLanguage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextItem(text: String) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LanguageItem(
|
||||
lang: OcrLanguage,
|
||||
displayName: String,
|
||||
onInstallLanguage: (String) -> Unit,
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = { Text(displayName) },
|
||||
trailingContent = {
|
||||
Icon(Icons.Default.Download, contentDescription = null)
|
||||
},
|
||||
modifier = Modifier.clickable { onInstallLanguage(lang.code) }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OcrDownloadDialog(
|
||||
state: OcrDownloadUiState,
|
||||
onCancel: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {}, // tapping outside the dialog should not cancel
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.settings_ocr_downloading),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
},
|
||||
text = {
|
||||
if (state.failed) {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_ocr_download_error),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
} else {
|
||||
Column {
|
||||
Text(state.language.displayName(Locale.current.platformLocale))
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
val progress =
|
||||
state.totalBytes?.let { total ->
|
||||
state.downloadedBytes.toFloat() / total
|
||||
}
|
||||
|
||||
if (progress != null) {
|
||||
LinearProgressIndicator(progress = { progress }, Modifier.fillMaxWidth())
|
||||
} else {
|
||||
LinearProgressIndicator(Modifier.fillMaxWidth())
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
Text(
|
||||
buildProgressText(
|
||||
state.downloadedBytes,
|
||||
state.totalBytes,
|
||||
LocalContext.current
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onCancel) {
|
||||
Text(stringResource(if (state.failed) R.string.close else R.string.cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {}
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildProgressText(
|
||||
downloadedBytes: Long,
|
||||
totalBytes: Long?,
|
||||
context: Context,
|
||||
): String {
|
||||
return if (totalBytes != null) {
|
||||
listOf(
|
||||
Formatter.formatShortFileSize(context, downloadedBytes),
|
||||
Formatter.formatShortFileSize(context, totalBytes),
|
||||
).joinToString(" / ")
|
||||
} else {
|
||||
Formatter.formatShortFileSize(context, downloadedBytes)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
/*
|
||||
* Copyright 2025-2026 The FairScan authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.fairscan.app.ui.screens.settings
|
||||
|
||||
import android.content.Context
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.RadioButtonUnchecked
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.fairscan.app.R
|
||||
import org.fairscan.app.data.OcrLanguage
|
||||
import org.fairscan.app.ui.components.BackButton
|
||||
import org.fairscan.app.ui.theme.FairScanTheme
|
||||
import android.text.format.Formatter
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
enum class LanguageState {
|
||||
ACTIVE,
|
||||
INACTIVE,
|
||||
NOT_INSTALLED,
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun OcrLanguagesScreen(
|
||||
uiState: SettingsUiState,
|
||||
onBack: () -> Unit,
|
||||
onLanguageClick: (String) -> Unit,
|
||||
onRemoveLanguage: (String) -> Unit,
|
||||
onCancelOcrDownload: () -> Unit,
|
||||
) {
|
||||
BackHandler { onBack() }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.settings_ocr_languages)) },
|
||||
navigationIcon = { BackButton(onBack) },
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
OcrLanguagesContent(
|
||||
uiState = uiState,
|
||||
onLanguageClick = onLanguageClick,
|
||||
onCancelOcrDownload = onCancelOcrDownload,
|
||||
onRemoveLanguage = onRemoveLanguage,
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OcrLanguagesContent(
|
||||
uiState: SettingsUiState,
|
||||
onLanguageClick: (String) -> Unit,
|
||||
onRemoveLanguage: (String) -> Unit,
|
||||
onCancelOcrDownload: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val locale = Locale.current.platformLocale
|
||||
|
||||
val installed = remember(
|
||||
uiState.installedOcrLanguages,
|
||||
uiState.enabledOcrLanguages,
|
||||
locale,
|
||||
) {
|
||||
uiState.installedOcrLanguages
|
||||
.map { OcrLanguage(it) }
|
||||
.sortedWith(compareBy { it.displayName(locale) })
|
||||
}
|
||||
|
||||
val available = remember(
|
||||
uiState.installedOcrLanguages,
|
||||
locale,
|
||||
) {
|
||||
OcrLanguage.AVAILABLE_LANGUAGE_CODES
|
||||
.filterNot { it in uiState.installedOcrLanguages }
|
||||
.map { OcrLanguage(it) }
|
||||
.sortedBy { it.displayName(locale) }
|
||||
}
|
||||
|
||||
val suggested = available.firstOrNull {
|
||||
it.locale.displayLanguage == locale.displayLanguage
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize()
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_ocr_download_intro),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (installed.isNotEmpty()) {
|
||||
item {
|
||||
SectionTitle(
|
||||
stringResource(R.string.settings_ocr_languages_installed)
|
||||
)
|
||||
}
|
||||
|
||||
items(
|
||||
items = installed,
|
||||
key = { it.code }
|
||||
) { lang ->
|
||||
LanguageItem(
|
||||
language = lang,
|
||||
state = if (lang.code in uiState.enabledOcrLanguages) {
|
||||
LanguageState.ACTIVE
|
||||
} else {
|
||||
LanguageState.INACTIVE
|
||||
},
|
||||
onClick = { onLanguageClick(lang.code) },
|
||||
onRemove = { onRemoveLanguage(lang.code) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (suggested != null) {
|
||||
item {
|
||||
SectionTitle(
|
||||
stringResource(R.string.settings_ocr_suggested)
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
LanguageItem(
|
||||
language = suggested,
|
||||
state = LanguageState.NOT_INSTALLED,
|
||||
onClick = {
|
||||
onLanguageClick(suggested.code)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
SectionTitle(
|
||||
stringResource(R.string.settings_ocr_languages_available)
|
||||
)
|
||||
}
|
||||
|
||||
items(
|
||||
available.filter { it != suggested },
|
||||
key = { it.code }
|
||||
) { lang ->
|
||||
LanguageItem(
|
||||
language = lang,
|
||||
state = LanguageState.NOT_INSTALLED,
|
||||
onClick = {
|
||||
onLanguageClick(lang.code)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
uiState.currentDownload?.let { download ->
|
||||
OcrDownloadDialog(
|
||||
state = download,
|
||||
onCancel = onCancelOcrDownload,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LanguageItem(
|
||||
language: OcrLanguage,
|
||||
state: LanguageState,
|
||||
onClick: () -> Unit,
|
||||
onRemove: (() -> Unit)? = null,
|
||||
) {
|
||||
var menuExpanded by remember { mutableStateOf(false) }
|
||||
val locale = Locale.current.platformLocale
|
||||
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(language.displayName(locale))
|
||||
},
|
||||
leadingContent = {
|
||||
when (state) {
|
||||
LanguageState.ACTIVE ->
|
||||
Icon(
|
||||
Icons.Default.CheckCircle,
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
LanguageState.INACTIVE ->
|
||||
Icon(
|
||||
Icons.Default.RadioButtonUnchecked,
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
LanguageState.NOT_INSTALLED ->
|
||||
Icon(
|
||||
Icons.Default.Download,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingContent = {
|
||||
if (onRemove != null) {
|
||||
Box {
|
||||
IconButton(onClick = { menuExpanded = true }) {
|
||||
Icon(Icons.Default.MoreVert, contentDescription = null)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = menuExpanded,
|
||||
onDismissRequest = { menuExpanded = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.delete)) },
|
||||
onClick = {
|
||||
menuExpanded = false
|
||||
onRemove()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.clickable(onClick = onClick)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SectionTitle(
|
||||
text: String,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
modifier = Modifier.padding(
|
||||
horizontal = 16.dp,
|
||||
vertical = 16.dp,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OcrDownloadDialog(
|
||||
state: OcrDownloadUiState,
|
||||
onCancel: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {}, // tapping outside the dialog should not cancel
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.settings_ocr_downloading),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
},
|
||||
text = {
|
||||
if (state.failed) {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_ocr_download_error),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
} else {
|
||||
Column {
|
||||
Text(state.language.displayName(Locale.current.platformLocale))
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
val progress =
|
||||
state.totalBytes?.let { total ->
|
||||
state.downloadedBytes.toFloat() / total
|
||||
}
|
||||
|
||||
if (progress != null) {
|
||||
LinearProgressIndicator(progress = { progress }, Modifier.fillMaxWidth())
|
||||
} else {
|
||||
LinearProgressIndicator(Modifier.fillMaxWidth())
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
Text(
|
||||
buildProgressText(
|
||||
state.downloadedBytes,
|
||||
state.totalBytes,
|
||||
LocalContext.current
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onCancel) {
|
||||
Text(stringResource(if (state.failed) R.string.close else R.string.cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {}
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildProgressText(
|
||||
downloadedBytes: Long,
|
||||
totalBytes: Long?,
|
||||
context: Context,
|
||||
): String {
|
||||
return if (totalBytes != null) {
|
||||
listOf(
|
||||
Formatter.formatShortFileSize(context, downloadedBytes),
|
||||
Formatter.formatShortFileSize(context, totalBytes),
|
||||
).joinToString(" / ")
|
||||
} else {
|
||||
Formatter.formatShortFileSize(context, downloadedBytes)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun OcrLanguagesScreenPreviewBasic() {
|
||||
OcrLanguagesScreenPreview(
|
||||
SettingsUiState(
|
||||
installedOcrLanguages = setOf("fra", "deu"),
|
||||
enabledOcrLanguages = setOf("fra")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun OcrLanguagesScreenPreviewWithDownloadDialog() {
|
||||
OcrLanguagesScreenPreview(
|
||||
SettingsUiState(currentDownload =
|
||||
OcrDownloadUiState(OcrLanguage("eng"), 500_000, 1_200_000)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun OcrLanguagesScreenPreviewWithDownloadError() {
|
||||
OcrLanguagesScreenPreview(
|
||||
SettingsUiState(currentDownload =
|
||||
OcrDownloadUiState(OcrLanguage("eng"), failed = true)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OcrLanguagesScreenPreview(uiState: SettingsUiState) {
|
||||
FairScanTheme {
|
||||
OcrLanguagesScreen(uiState, {}, {}, {}, {})
|
||||
}
|
||||
}
|
||||
@@ -19,33 +19,25 @@ import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Folder
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
@@ -54,12 +46,10 @@ import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -69,8 +59,9 @@ import androidx.compose.ui.unit.dp
|
||||
import org.fairscan.app.R
|
||||
import org.fairscan.app.data.OcrLanguage
|
||||
import org.fairscan.app.domain.ExportQuality
|
||||
import org.fairscan.app.ui.Navigation
|
||||
import org.fairscan.app.ui.components.BackButton
|
||||
import org.fairscan.app.ui.components.ConfirmationDialog
|
||||
import org.fairscan.app.ui.dummyNavigation
|
||||
import org.fairscan.app.ui.theme.FairScanTheme
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -82,18 +73,14 @@ fun SettingsScreen(
|
||||
onResetExportDirClick: () -> Unit,
|
||||
onExportFormatChanged: (ExportFormat) -> Unit,
|
||||
onExportQualityChanged: (ExportQuality) -> Unit,
|
||||
onInstallOcrLanguage: (String) -> Unit,
|
||||
onCancelOcrDownload: () -> Unit,
|
||||
onEnableOcrLanguage: (String, Boolean) -> Unit,
|
||||
onDeleteUnusedOcrLanguages: () -> Unit,
|
||||
onBack: () -> Unit,
|
||||
navigation: Navigation,
|
||||
) {
|
||||
BackHandler { onBack() }
|
||||
BackHandler { navigation.back() }
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.settings)) },
|
||||
navigationIcon = { BackButton(onBack) },
|
||||
navigationIcon = { BackButton(navigation.back) },
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
@@ -104,10 +91,7 @@ fun SettingsScreen(
|
||||
onResetExportDirClick,
|
||||
onExportFormatChanged,
|
||||
onExportQualityChanged,
|
||||
onInstallOcrLanguage,
|
||||
onCancelOcrDownload,
|
||||
onEnableOcrLanguage,
|
||||
onDeleteUnusedOcrLanguages,
|
||||
navigation,
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
)
|
||||
}
|
||||
@@ -122,14 +106,9 @@ private fun SettingsContent(
|
||||
onResetExportDirClick: () -> Unit,
|
||||
onExportFormatChanged: (ExportFormat) -> Unit,
|
||||
onExportQualityChanged: (ExportQuality) -> Unit,
|
||||
onInstallOcrLanguage: (String) -> Unit,
|
||||
onCancelOcrDownload: () -> Unit,
|
||||
onEnableOcrLanguage: (String, Boolean) -> Unit,
|
||||
onDeleteUnusedOcrLanguages: () -> Unit,
|
||||
navigation: Navigation,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var showAddLanguageSheet by remember { mutableStateOf(false) }
|
||||
val showDeleteLanguageDialog = rememberSaveable { mutableStateOf(false) }
|
||||
val displayLocale = Locale.current.platformLocale
|
||||
val export = uiState.export
|
||||
val (folderLabel, folderLabelColor) = when {
|
||||
@@ -212,69 +191,24 @@ private fun SettingsContent(
|
||||
stringResource(R.string.settings_section_ocr),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
uiState.installedOcrLanguages.map { OcrLanguage(it) }
|
||||
.sortedBy { it.displayName(displayLocale) }
|
||||
.forEach { lang ->
|
||||
val code = lang.code
|
||||
val selected = code in uiState.enabledOcrLanguages
|
||||
FilterChip(
|
||||
selected = selected,
|
||||
onClick = {
|
||||
onEnableOcrLanguage(code, code !in uiState.enabledOcrLanguages)
|
||||
},
|
||||
leadingIcon = { if (selected) Icon(Icons.Default.Check, contentDescription = null) },
|
||||
label = { Text(lang.displayName(displayLocale)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
Row (verticalAlignment = Alignment.CenterVertically) {
|
||||
OutlinedButton(
|
||||
onClick = { showAddLanguageSheet = true },
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = MaterialTheme.colorScheme.primary
|
||||
),
|
||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.primary)
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = null)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(stringResource(R.string.settings_ocr_add_language))
|
||||
}
|
||||
if (uiState.enabledOcrLanguages != uiState.installedOcrLanguages) {
|
||||
TextButton(onClick = { showDeleteLanguageDialog.value = true }) {
|
||||
Text(stringResource(R.string.settings_ocr_clean_up))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showAddLanguageSheet) {
|
||||
val onDismiss = { showAddLanguageSheet = false }
|
||||
ModalBottomSheet(onDismissRequest = onDismiss) {
|
||||
AddLanguageBottomSheetContent(uiState, onDismiss) { code ->
|
||||
onInstallOcrLanguage(code)
|
||||
showAddLanguageSheet = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showDeleteLanguageDialog.value) {
|
||||
val toRemove = uiState.installedOcrLanguages
|
||||
.filterNot { code -> code in uiState.enabledOcrLanguages}
|
||||
.joinToString(" • ") { OcrLanguage(it).displayName(displayLocale) }
|
||||
ConfirmationDialog(
|
||||
title = "Do you want to remove the following languages?",
|
||||
message = toRemove,
|
||||
showDialog = showDeleteLanguageDialog,
|
||||
onConfirm = onDeleteUnusedOcrLanguages,
|
||||
)
|
||||
}
|
||||
uiState.currentDownload?.let { download ->
|
||||
OcrDownloadDialog(
|
||||
state = download,
|
||||
onCancel = onCancelOcrDownload,
|
||||
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.settings_ocr_languages)) },
|
||||
supportingContent = {
|
||||
Text(uiState.enabledOcrLanguages
|
||||
.map { OcrLanguage(it).displayName(displayLocale) }
|
||||
.sorted()
|
||||
.joinToString(" • ")
|
||||
.ifEmpty { stringResource(R.string.settings_ocr_languages_disabled) }
|
||||
)
|
||||
},
|
||||
trailingContent = {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
modifier = Modifier.clickable { navigation.toOcrLanguagesScreen() }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -402,7 +336,10 @@ fun DirectorySettingItem(
|
||||
@Preview(heightDp = 780)
|
||||
@Composable
|
||||
fun SettingsScreenPreviewWithoutDir() {
|
||||
SettingsScreenPreview(SettingsUiState(installedOcrLanguages = setOf("fra", "eng")))
|
||||
SettingsScreenPreview(SettingsUiState(
|
||||
installedOcrLanguages = setOf("fra", "eng", "deu"),
|
||||
enabledOcrLanguages = setOf("fra", "eng")
|
||||
))
|
||||
}
|
||||
|
||||
@Preview
|
||||
@@ -413,26 +350,6 @@ fun SettingsScreenPreviewWithDir() {
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SettingsScreenPreviewWithDownloadDialog() {
|
||||
SettingsScreenPreview(
|
||||
SettingsUiState(currentDownload =
|
||||
OcrDownloadUiState(OcrLanguage("eng"), 500_000, 1_200_000)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SettingsScreenPreviewWithDownloadError() {
|
||||
SettingsScreenPreview(
|
||||
SettingsUiState(currentDownload =
|
||||
OcrDownloadUiState(OcrLanguage("eng"), failed = true)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsScreenPreview(uiState: SettingsUiState) {
|
||||
FairScanTheme {
|
||||
@@ -443,11 +360,7 @@ fun SettingsScreenPreview(uiState: SettingsUiState) {
|
||||
onResetExportDirClick = {},
|
||||
onExportFormatChanged = {},
|
||||
onExportQualityChanged = {},
|
||||
onInstallOcrLanguage = {},
|
||||
onCancelOcrDownload = {},
|
||||
onEnableOcrLanguage = { _,_->},
|
||||
onDeleteUnusedOcrLanguages = {},
|
||||
onBack = {}
|
||||
navigation = dummyNavigation(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,14 @@ class SettingsViewModel(container: AppContainer) : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun onLanguageClick(code: String) {
|
||||
if (uiState.value.installedOcrLanguages.contains(code)) {
|
||||
setOcrLanguageEnabled(code, !uiState.value.enabledOcrLanguages.contains(code))
|
||||
} else {
|
||||
installLanguage(code)
|
||||
}
|
||||
}
|
||||
|
||||
fun installLanguage(code: String) {
|
||||
downloadJob?.cancel()
|
||||
downloadJob = viewModelScope.launch {
|
||||
@@ -172,9 +180,9 @@ class SettingsViewModel(container: AppContainer) : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteUnusedOcrLanguages() {
|
||||
fun onRemoveLanguage(code: String) {
|
||||
viewModelScope.launch {
|
||||
ocrLanguageRepo.deleteInactiveLanguages()
|
||||
ocrLanguageRepo.deleteLanguage(code)
|
||||
refreshInstalledLanguages()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">انسخ السجلات</string>
|
||||
<string name="creating_export">يجهز التصدير…</string>
|
||||
<string name="crop">اقتصاص</string>
|
||||
<string name="delete">حذف</string>
|
||||
<string name="delete_page">احذف الصفحة</string>
|
||||
<string name="delete_page_warning">هل تريد حذف هذه الصفحة؟</string>
|
||||
<string name="developer">مطوّر</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">احفظ</string>
|
||||
<string name="scan_button">مسح جديد</string>
|
||||
<string name="settings">الإعدادات</string>
|
||||
<string name="settings_ocr_add_language">إضافة لغة</string>
|
||||
<string name="settings_ocr_all_languages">جميع اللغات</string>
|
||||
<string name="settings_ocr_clean_up">تنظيف اللغات غير المستخدمة…</string>
|
||||
<string name="settings_ocr_download_error">فشل التنزيل. تحقق من اتصالك بالإنترنت.</string>
|
||||
<string name="settings_ocr_download_intro">يتم تنزيل ملفات اللغة من مشروع Tesseract الرسمي. تبقى عملية التعرّف على النصوص دون اتصال بالإنترنت بالكامل.</string>
|
||||
<string name="settings_ocr_downloading">جارٍ تنزيل ملف اللغة</string>
|
||||
<string name="settings_ocr_languages">اللغات</string>
|
||||
<string name="settings_ocr_languages_available">اللغات المتاحة</string>
|
||||
<string name="settings_ocr_languages_disabled">معطّل</string>
|
||||
<string name="settings_ocr_languages_installed">مثبّتة</string>
|
||||
<string name="settings_ocr_suggested">مقترح</string>
|
||||
<string name="settings_section_ocr">التعرّف على النصوص</string>
|
||||
<string name="settings_section_scan">المسح</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">Kopírovat protokoly</string>
|
||||
<string name="creating_export">Příprava exportu…</string>
|
||||
<string name="crop">Oříznout</string>
|
||||
<string name="delete">Smazat</string>
|
||||
<string name="delete_page">Smazat stránku</string>
|
||||
<string name="delete_page_warning">Chcete smazat tuto stránku?</string>
|
||||
<string name="developer">Vývojář</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">Uložit</string>
|
||||
<string name="scan_button">Nové skenování</string>
|
||||
<string name="settings">Nastavení</string>
|
||||
<string name="settings_ocr_add_language">Přidat jazyk</string>
|
||||
<string name="settings_ocr_all_languages">Všechny jazyky</string>
|
||||
<string name="settings_ocr_clean_up">Odstranit nepoužívané jazyky…</string>
|
||||
<string name="settings_ocr_download_error">Stahování se nezdařilo. Zkontrolujte své internetové připojení.</string>
|
||||
<string name="settings_ocr_download_intro">Jazykové soubory jsou stahovány z oficiálního projektu Tesseract. Rozpoznávání textu zůstává zcela offline.</string>
|
||||
<string name="settings_ocr_downloading">Stahování jazykového souboru</string>
|
||||
<string name="settings_ocr_languages">Jazyky</string>
|
||||
<string name="settings_ocr_languages_available">Dostupné jazyky</string>
|
||||
<string name="settings_ocr_languages_disabled">Vypnuto</string>
|
||||
<string name="settings_ocr_languages_installed">Nainstalované</string>
|
||||
<string name="settings_ocr_suggested">Doporučeno</string>
|
||||
<string name="settings_section_ocr">Rozpoznávání textu</string>
|
||||
<string name="settings_section_scan">Skenování</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">Logs kopieren</string>
|
||||
<string name="creating_export">Export wird vorbereitet…</string>
|
||||
<string name="crop">Zuschneiden</string>
|
||||
<string name="delete">Löschen</string>
|
||||
<string name="delete_page">Seite löschen</string>
|
||||
<string name="delete_page_warning">Möchten Sie diese Seite löschen?</string>
|
||||
<string name="developer">Entwickler</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">Speichern</string>
|
||||
<string name="scan_button">Neuer Scan</string>
|
||||
<string name="settings">Einstellungen</string>
|
||||
<string name="settings_ocr_add_language">Sprache hinzufügen</string>
|
||||
<string name="settings_ocr_all_languages">Alle Sprachen</string>
|
||||
<string name="settings_ocr_clean_up">Nicht verwendete Sprachen bereinigen…</string>
|
||||
<string name="settings_ocr_download_error">Download fehlgeschlagen. Überprüfen Sie Ihre Internetverbindung.</string>
|
||||
<string name="settings_ocr_download_intro">Sprachdateien werden vom offiziellen Tesseract-Projekt heruntergeladen. Die Texterkennung bleibt vollständig offline.</string>
|
||||
<string name="settings_ocr_downloading">Sprachdatei wird heruntergeladen</string>
|
||||
<string name="settings_ocr_languages">Sprachen</string>
|
||||
<string name="settings_ocr_languages_available">Verfügbare Sprachen</string>
|
||||
<string name="settings_ocr_languages_disabled">Deaktiviert</string>
|
||||
<string name="settings_ocr_languages_installed">Installiert</string>
|
||||
<string name="settings_ocr_suggested">Empfohlen</string>
|
||||
<string name="settings_section_ocr">Texterkennung</string>
|
||||
<string name="settings_section_scan">Scan</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">Copiar registros</string>
|
||||
<string name="creating_export">Preparando la exportación…</string>
|
||||
<string name="crop">Recortar</string>
|
||||
<string name="delete">Eliminar</string>
|
||||
<string name="delete_page">Eliminar página</string>
|
||||
<string name="delete_page_warning">¿Quieres eliminar esta página?</string>
|
||||
<string name="developer">Desarrollador</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">Guardar</string>
|
||||
<string name="scan_button">Nuevo escaneo</string>
|
||||
<string name="settings">Ajustes</string>
|
||||
<string name="settings_ocr_add_language">Añadir idioma</string>
|
||||
<string name="settings_ocr_all_languages">Todos los idiomas</string>
|
||||
<string name="settings_ocr_clean_up">Limpiar idiomas no utilizados…</string>
|
||||
<string name="settings_ocr_download_error">La descarga ha fallado. Comprueba tu conexión a Internet.</string>
|
||||
<string name="settings_ocr_download_intro">Los archivos de idioma se descargan desde el proyecto oficial Tesseract. El reconocimiento de texto sigue siendo completamente local.</string>
|
||||
<string name="settings_ocr_downloading">Descargando archivo de idioma</string>
|
||||
<string name="settings_ocr_languages">Idiomas</string>
|
||||
<string name="settings_ocr_languages_available">Idiomas disponibles</string>
|
||||
<string name="settings_ocr_languages_disabled">Desactivado</string>
|
||||
<string name="settings_ocr_languages_installed">Instalados</string>
|
||||
<string name="settings_ocr_suggested">Sugerido</string>
|
||||
<string name="settings_section_ocr">Reconocimiento de texto</string>
|
||||
<string name="settings_section_scan">Escaneo</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">Copier les logs</string>
|
||||
<string name="creating_export">Préparation de l’export…</string>
|
||||
<string name="crop">Recadrer</string>
|
||||
<string name="delete">Supprimer</string>
|
||||
<string name="delete_page">Supprimer la page</string>
|
||||
<string name="delete_page_warning">Voulez-vous supprimer cette page ?</string>
|
||||
<string name="developer">Développeur</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">Enregistrer</string>
|
||||
<string name="scan_button">Nouveau scan</string>
|
||||
<string name="settings">Paramètres</string>
|
||||
<string name="settings_ocr_add_language">Ajouter une langue</string>
|
||||
<string name="settings_ocr_all_languages">Toutes les langues</string>
|
||||
<string name="settings_ocr_clean_up">Supprimer les langues inutilisées…</string>
|
||||
<string name="settings_ocr_download_error">Le téléchargement a échoué. Vérifiez votre connexion Internet.</string>
|
||||
<string name="settings_ocr_download_intro">Les fichiers de langue sont téléchargés depuis le projet officiel Tesseract. La reconnaissance de texte reste entièrement hors ligne.</string>
|
||||
<string name="settings_ocr_downloading">Téléchargement du fichier de langue</string>
|
||||
<string name="settings_ocr_languages">Langues</string>
|
||||
<string name="settings_ocr_languages_available">Langues disponibles</string>
|
||||
<string name="settings_ocr_languages_disabled">Désactivé</string>
|
||||
<string name="settings_ocr_languages_installed">Installées</string>
|
||||
<string name="settings_ocr_suggested">Suggéré</string>
|
||||
<string name="settings_section_ocr">Reconnaissance de texte</string>
|
||||
<string name="settings_section_scan">Scan</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">Copiar rexistros</string>
|
||||
<string name="creating_export">Preparando exportación…</string>
|
||||
<string name="crop">Recortar</string>
|
||||
<string name="delete">Eliminar</string>
|
||||
<string name="delete_page">Eliminar páxina</string>
|
||||
<string name="delete_page_warning">Queres eliminar esta páxina?</string>
|
||||
<string name="developer">Desenvolvedor</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">Gardar</string>
|
||||
<string name="scan_button">Novo escaneo</string>
|
||||
<string name="settings">Configuración</string>
|
||||
<string name="settings_ocr_add_language">Engadir idioma</string>
|
||||
<string name="settings_ocr_all_languages">Todos os idiomas</string>
|
||||
<string name="settings_ocr_clean_up">Limpar idiomas non utilizados…</string>
|
||||
<string name="settings_ocr_download_error">Produciuse un erro na descarga. Comproba a túa conexión á Internet.</string>
|
||||
<string name="settings_ocr_download_intro">Os ficheiros de idioma descárganse desde o proxecto oficial Tesseract. O recoñecemento de texto segue sendo completamente sen conexión.</string>
|
||||
<string name="settings_ocr_downloading">Descargando ficheiro de idioma</string>
|
||||
<string name="settings_ocr_languages">Idiomas</string>
|
||||
<string name="settings_ocr_languages_available">Idiomas dispoñibles</string>
|
||||
<string name="settings_ocr_languages_disabled">Desactivado</string>
|
||||
<string name="settings_ocr_languages_installed">Instalados</string>
|
||||
<string name="settings_ocr_suggested">Suxerido</string>
|
||||
<string name="settings_section_ocr">Recoñecemento de texto</string>
|
||||
<string name="settings_section_scan">Escaneo</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">Copia log</string>
|
||||
<string name="creating_export">Preparazione dell’esportazione…</string>
|
||||
<string name="crop">Ritaglia</string>
|
||||
<string name="delete">Elimina</string>
|
||||
<string name="delete_page">Elimina pagina</string>
|
||||
<string name="delete_page_warning">Vuoi eliminare questa pagina?</string>
|
||||
<string name="developer">Sviluppatore</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">Salva</string>
|
||||
<string name="scan_button">Nuova scansione</string>
|
||||
<string name="settings">Impostazioni</string>
|
||||
<string name="settings_ocr_add_language">Aggiungi lingua</string>
|
||||
<string name="settings_ocr_all_languages">Tutte le lingue</string>
|
||||
<string name="settings_ocr_clean_up">Rimuovi le lingue inutilizzate…</string>
|
||||
<string name="settings_ocr_download_error">Download non riuscito. Controlla la connessione a Internet.</string>
|
||||
<string name="settings_ocr_download_intro">I file di lingua vengono scaricati dal progetto ufficiale Tesseract. Il riconoscimento del testo rimane completamente offline.</string>
|
||||
<string name="settings_ocr_downloading">Download del file di lingua</string>
|
||||
<string name="settings_ocr_languages">Lingue</string>
|
||||
<string name="settings_ocr_languages_available">Lingue disponibili</string>
|
||||
<string name="settings_ocr_languages_disabled">Disattivato</string>
|
||||
<string name="settings_ocr_languages_installed">Installate</string>
|
||||
<string name="settings_ocr_suggested">Suggerito</string>
|
||||
<string name="settings_section_ocr">Riconoscimento del testo</string>
|
||||
<string name="settings_section_scan">Scansione</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">ログをコピー</string>
|
||||
<string name="creating_export">エクスポートを準備中…</string>
|
||||
<string name="crop">トリミング</string>
|
||||
<string name="delete">削除</string>
|
||||
<string name="delete_page">ページを削除</string>
|
||||
<string name="delete_page_warning">このページを削除しますか?</string>
|
||||
<string name="developer">開発者</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">保存</string>
|
||||
<string name="scan_button">新規スキャン</string>
|
||||
<string name="settings">設定</string>
|
||||
<string name="settings_ocr_add_language">言語を追加</string>
|
||||
<string name="settings_ocr_all_languages">すべての言語</string>
|
||||
<string name="settings_ocr_clean_up">未使用の言語を整理…</string>
|
||||
<string name="settings_ocr_download_error">ダウンロードに失敗しました。インターネット接続を確認してください。</string>
|
||||
<string name="settings_ocr_download_intro">言語ファイルは公式の Tesseract プロジェクトからダウンロードされます。テキスト認識は完全にオフラインで実行されます。</string>
|
||||
<string name="settings_ocr_downloading">言語ファイルをダウンロード中</string>
|
||||
<string name="settings_ocr_languages">言語</string>
|
||||
<string name="settings_ocr_languages_available">利用可能な言語</string>
|
||||
<string name="settings_ocr_languages_disabled">無効</string>
|
||||
<string name="settings_ocr_languages_installed">インストール済み</string>
|
||||
<string name="settings_ocr_suggested">おすすめ</string>
|
||||
<string name="settings_section_ocr">テキスト認識</string>
|
||||
<string name="settings_section_scan">スキャン</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">Copiar registros</string>
|
||||
<string name="creating_export">Preparando exportação…</string>
|
||||
<string name="crop">Recortar</string>
|
||||
<string name="delete">Excluir</string>
|
||||
<string name="delete_page">Excluir página</string>
|
||||
<string name="delete_page_warning">Deseja excluir esta página?</string>
|
||||
<string name="developer">Desenvolvedor</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">Salvar</string>
|
||||
<string name="scan_button">Nova digitalização</string>
|
||||
<string name="settings">Configurações</string>
|
||||
<string name="settings_ocr_add_language">Adicionar idioma</string>
|
||||
<string name="settings_ocr_all_languages">Todos os idiomas</string>
|
||||
<string name="settings_ocr_clean_up">Remover idiomas não utilizados…</string>
|
||||
<string name="settings_ocr_download_error">Falha no download. Verifique sua conexão com a Internet.</string>
|
||||
<string name="settings_ocr_download_intro">Os arquivos de idioma são baixados do projeto oficial Tesseract. O reconhecimento de texto permanece totalmente offline.</string>
|
||||
<string name="settings_ocr_downloading">Baixando arquivo de idioma</string>
|
||||
<string name="settings_ocr_languages">Idiomas</string>
|
||||
<string name="settings_ocr_languages_available">Idiomas disponíveis</string>
|
||||
<string name="settings_ocr_languages_disabled">Desativado</string>
|
||||
<string name="settings_ocr_languages_installed">Instalados</string>
|
||||
<string name="settings_ocr_suggested">Sugerido</string>
|
||||
<string name="settings_section_ocr">Reconhecimento de texto</string>
|
||||
<string name="settings_section_scan">Digitalização</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">Копировать журналы</string>
|
||||
<string name="creating_export">Подготовка экспорта…</string>
|
||||
<string name="crop">Обрезать</string>
|
||||
<string name="delete">Удалить</string>
|
||||
<string name="delete_page">Удалить страницу</string>
|
||||
<string name="delete_page_warning">Вы желаете удалить эту страницу?</string>
|
||||
<string name="developer">Разработчик</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">Сохранить</string>
|
||||
<string name="scan_button">Начать</string>
|
||||
<string name="settings">Настройки</string>
|
||||
<string name="settings_ocr_add_language">Добавить язык</string>
|
||||
<string name="settings_ocr_all_languages">Все языки</string>
|
||||
<string name="settings_ocr_clean_up">Удалить неиспользуемые языки…</string>
|
||||
<string name="settings_ocr_download_error">Не удалось загрузить файл. Проверьте подключение к Интернету.</string>
|
||||
<string name="settings_ocr_download_intro">Языковые файлы загружаются из официального проекта Tesseract. Распознавание текста остаётся полностью офлайн.</string>
|
||||
<string name="settings_ocr_downloading">Загрузка языкового файла</string>
|
||||
<string name="settings_ocr_languages">Языки</string>
|
||||
<string name="settings_ocr_languages_available">Доступные языки</string>
|
||||
<string name="settings_ocr_languages_disabled">Отключено</string>
|
||||
<string name="settings_ocr_languages_installed">Установленные</string>
|
||||
<string name="settings_ocr_suggested">Рекомендуется</string>
|
||||
<string name="settings_section_ocr">Распознавание текста</string>
|
||||
<string name="settings_section_scan">Сканирование</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">Kopiera loggar</string>
|
||||
<string name="creating_export">Förbereder export…</string>
|
||||
<string name="crop">Beskär</string>
|
||||
<string name="delete">Ta bort</string>
|
||||
<string name="delete_page">Ta bort sida</string>
|
||||
<string name="delete_page_warning">Vill du ta bort den här sidan?</string>
|
||||
<string name="developer">Utvecklare</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">Spara</string>
|
||||
<string name="scan_button">Ny skanning</string>
|
||||
<string name="settings">Inställningar</string>
|
||||
<string name="settings_ocr_add_language">Lägg till språk</string>
|
||||
<string name="settings_ocr_all_languages">Alla språk</string>
|
||||
<string name="settings_ocr_clean_up">Rensa oanvända språk…</string>
|
||||
<string name="settings_ocr_download_error">Nedladdningen misslyckades. Kontrollera din internetanslutning.</string>
|
||||
<string name="settings_ocr_download_intro">Språkfiler hämtas från det officiella Tesseract-projektet. Textigenkänning förblir helt offline.</string>
|
||||
<string name="settings_ocr_downloading">Hämtar språkfil</string>
|
||||
<string name="settings_ocr_languages">Språk</string>
|
||||
<string name="settings_ocr_languages_available">Tillgängliga språk</string>
|
||||
<string name="settings_ocr_languages_disabled">Inaktiverad</string>
|
||||
<string name="settings_ocr_languages_installed">Installerade</string>
|
||||
<string name="settings_ocr_suggested">Föreslagen</string>
|
||||
<string name="settings_section_ocr">Textigenkänning</string>
|
||||
<string name="settings_section_scan">Skanning</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">Günlükleri kopyala</string>
|
||||
<string name="creating_export">Dışa aktarma hazırlanıyor…</string>
|
||||
<string name="crop">Kırp</string>
|
||||
<string name="delete">Sil</string>
|
||||
<string name="delete_page">Sayfayı sil</string>
|
||||
<string name="delete_page_warning">Bu sayfayı silmek istiyor musunuz?</string>
|
||||
<string name="developer">Geliştirici</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">Kaydet</string>
|
||||
<string name="scan_button">Yeni Tarama</string>
|
||||
<string name="settings">Ayarlar</string>
|
||||
<string name="settings_ocr_add_language">Dil ekle</string>
|
||||
<string name="settings_ocr_all_languages">Tüm diller</string>
|
||||
<string name="settings_ocr_clean_up">Kullanılmayan dilleri temizle…</string>
|
||||
<string name="settings_ocr_download_error">İndirme başarısız oldu. İnternet bağlantınızı kontrol edin.</string>
|
||||
<string name="settings_ocr_download_intro">Dil dosyaları resmi Tesseract projesinden indirilir. Metin tanıma tamamen çevrimdışı olarak çalışır.</string>
|
||||
<string name="settings_ocr_downloading">Dil dosyası indiriliyor</string>
|
||||
<string name="settings_ocr_languages">Diller</string>
|
||||
<string name="settings_ocr_languages_available">Kullanılabilir diller</string>
|
||||
<string name="settings_ocr_languages_disabled">Devre dışı</string>
|
||||
<string name="settings_ocr_languages_installed">Yüklü</string>
|
||||
<string name="settings_ocr_suggested">Önerilen</string>
|
||||
<string name="settings_section_ocr">Metin tanıma</string>
|
||||
<string name="settings_section_scan">Tarama</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">複製日誌</string>
|
||||
<string name="creating_export">正在準備匯出…</string>
|
||||
<string name="crop">裁切</string>
|
||||
<string name="delete">刪除</string>
|
||||
<string name="delete_page">刪除頁面</string>
|
||||
<string name="delete_page_warning">確定要刪除此頁面嗎?</string>
|
||||
<string name="developer">開發者</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">儲存</string>
|
||||
<string name="scan_button">開始掃描</string>
|
||||
<string name="settings">設定</string>
|
||||
<string name="settings_ocr_add_language">新增語言</string>
|
||||
<string name="settings_ocr_all_languages">所有語言</string>
|
||||
<string name="settings_ocr_clean_up">清理未使用的語言…</string>
|
||||
<string name="settings_ocr_download_error">下載失敗。請檢查您的網路連線。</string>
|
||||
<string name="settings_ocr_download_intro">語言檔案會從官方 Tesseract 專案下載。文字辨識始終完全離線運作。</string>
|
||||
<string name="settings_ocr_downloading">正在下載語言檔案</string>
|
||||
<string name="settings_ocr_languages">語言</string>
|
||||
<string name="settings_ocr_languages_available">可用語言</string>
|
||||
<string name="settings_ocr_languages_disabled">已停用</string>
|
||||
<string name="settings_ocr_languages_installed">已安裝</string>
|
||||
<string name="settings_ocr_suggested">推薦</string>
|
||||
<string name="settings_section_ocr">文字辨識</string>
|
||||
<string name="settings_section_scan">掃描</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="copy_logs">复制日志</string>
|
||||
<string name="creating_export">正在准备导出…</string>
|
||||
<string name="crop">裁剪</string>
|
||||
<string name="delete">删除</string>
|
||||
<string name="delete_page">删除页面</string>
|
||||
<string name="delete_page_warning">是否要删除此页面?</string>
|
||||
<string name="developer">开发者</string>
|
||||
@@ -58,12 +59,13 @@
|
||||
<string name="save">保存</string>
|
||||
<string name="scan_button">新建扫描</string>
|
||||
<string name="settings">设置</string>
|
||||
<string name="settings_ocr_add_language">添加语言</string>
|
||||
<string name="settings_ocr_all_languages">所有语言</string>
|
||||
<string name="settings_ocr_clean_up">清理未使用的语言…</string>
|
||||
<string name="settings_ocr_download_error">下载失败。请检查您的网络连接。</string>
|
||||
<string name="settings_ocr_download_intro">语言文件从官方 Tesseract 项目下载。文本识别始终完全离线运行。</string>
|
||||
<string name="settings_ocr_downloading">正在下载语言文件</string>
|
||||
<string name="settings_ocr_languages">语言</string>
|
||||
<string name="settings_ocr_languages_available">可用语言</string>
|
||||
<string name="settings_ocr_languages_disabled">已禁用</string>
|
||||
<string name="settings_ocr_languages_installed">已安装</string>
|
||||
<string name="settings_ocr_suggested">推荐</string>
|
||||
<string name="settings_section_ocr">文本识别</string>
|
||||
<string name="settings_section_scan">扫描</string>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<string name="copy_logs">Copy logs</string>
|
||||
<string name="creating_export">Preparing export…</string>
|
||||
<string name="crop">Crop</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="delete_page">Delete page</string>
|
||||
<string name="delete_page_warning">Do you want to delete this page?</string>
|
||||
<string name="developer">Developer</string>
|
||||
@@ -62,12 +63,13 @@
|
||||
<string name="save">Save</string>
|
||||
<string name="scan_button">New Scan</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="settings_ocr_add_language">Add language</string>
|
||||
<string name="settings_ocr_all_languages">All languages</string>
|
||||
<string name="settings_ocr_clean_up">Clean up unused languages…</string>
|
||||
<string name="settings_ocr_download_error">Download failed. Check your internet connection.</string>
|
||||
<string name="settings_ocr_download_intro">"Language files are downloaded from the official Tesseract project. Text recognition remains fully offline."</string>
|
||||
<string name="settings_ocr_downloading">Downloading language file</string>
|
||||
<string name="settings_ocr_languages">Languages</string>
|
||||
<string name="settings_ocr_languages_available">Available languages</string>
|
||||
<string name="settings_ocr_languages_disabled">Disabled</string>
|
||||
<string name="settings_ocr_languages_installed">Installed</string>
|
||||
<string name="settings_ocr_suggested">Suggested</string>
|
||||
<string name="settings_section_ocr">Text recognition</string>
|
||||
<string name="settings_section_scan">Scan</string>
|
||||
|
||||
Reference in New Issue
Block a user