From 23297d26d36c00ff09a45582ebc91918a920f53a Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 3 Jul 2024 15:01:13 +0200 Subject: [PATCH] feat: search support for tesseract download menu (closes #434) --- .idea/other.xml | 263 ++++++++++++++++++ .../translate/ui/components/SearchAppBar.kt | 2 +- .../bnyro/translate/ui/models/TessModel.kt | 49 ++++ .../bnyro/translate/ui/views/TessSettings.kt | 127 +++++---- 4 files changed, 386 insertions(+), 55 deletions(-) create mode 100644 .idea/other.xml create mode 100644 app/src/main/java/com/bnyro/translate/ui/models/TessModel.kt diff --git a/.idea/other.xml b/.idea/other.xml new file mode 100644 index 000000000..0d3a1fbb1 --- /dev/null +++ b/.idea/other.xml @@ -0,0 +1,263 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/translate/ui/components/SearchAppBar.kt b/app/src/main/java/com/bnyro/translate/ui/components/SearchAppBar.kt index d80fd04e5..18ad3a879 100644 --- a/app/src/main/java/com/bnyro/translate/ui/components/SearchAppBar.kt +++ b/app/src/main/java/com/bnyro/translate/ui/components/SearchAppBar.kt @@ -75,7 +75,7 @@ fun SearchAppBar( Text(title) }, actions = { - Crossfade(isSearchViewVisible) { + Crossfade(isSearchViewVisible, label = "search-bar-crossfade") { when (it) { true -> { Row( diff --git a/app/src/main/java/com/bnyro/translate/ui/models/TessModel.kt b/app/src/main/java/com/bnyro/translate/ui/models/TessModel.kt new file mode 100644 index 000000000..0385921c0 --- /dev/null +++ b/app/src/main/java/com/bnyro/translate/ui/models/TessModel.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 You Apps + * + * 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 . + */ + +package com.bnyro.translate.ui.models + +import android.content.Context +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.bnyro.translate.obj.TessLanguage +import com.bnyro.translate.util.TessHelper +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class TessModel: ViewModel() { + var availableLanguages by mutableStateOf(emptyList()) + var downloadedLanguages by mutableStateOf(emptyList()) + var notYetDownloadedLanguages by mutableStateOf(emptyList()) + + fun init(context: Context) { + downloadedLanguages = TessHelper.getDownloadedLanguages(context) + + viewModelScope.launch(Dispatchers.IO) { + availableLanguages = TessHelper.getAvailableLanguages() + } + } + + fun refreshDownloadedLanguages(context: Context) { + downloadedLanguages = TessHelper.getDownloadedLanguages(context) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/translate/ui/views/TessSettings.kt b/app/src/main/java/com/bnyro/translate/ui/views/TessSettings.kt index 1fade8e90..434aa4abc 100644 --- a/app/src/main/java/com/bnyro/translate/ui/views/TessSettings.kt +++ b/app/src/main/java/com/bnyro/translate/ui/views/TessSettings.kt @@ -40,9 +40,8 @@ import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Download import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults @@ -59,15 +58,17 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import androidx.lifecycle.viewmodel.compose.viewModel import com.bnyro.translate.R import com.bnyro.translate.obj.TessLanguage import com.bnyro.translate.ui.components.DialogButton +import com.bnyro.translate.ui.components.SearchAppBar import com.bnyro.translate.ui.components.StyledIconButton import com.bnyro.translate.ui.dialogs.FullscreenDialog +import com.bnyro.translate.ui.models.TessModel import com.bnyro.translate.util.Preferences import com.bnyro.translate.util.TessHelper -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -75,53 +76,77 @@ fun TessSettings( onDismissRequest: () -> Unit ) { val context = LocalContext.current + val tessModel: TessModel = viewModel() + val topAppBarBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - var availableLanguages by remember { - mutableStateOf(emptyList()) - } - - var downloadedLanguages by remember { - mutableStateOf(TessHelper.getDownloadedLanguages(context)) + var query by remember { + mutableStateOf("") } var selectedLanguage by remember { mutableStateOf(Preferences.get(Preferences.tessLanguageKey, "")) } + LaunchedEffect(Unit) { + tessModel.init(context) + } + + var filteredDownloadedLanguages by remember { + mutableStateOf(emptyList()) + } + var filteredAvailableLanguages by remember { + mutableStateOf(emptyList()) + } + + LaunchedEffect(query, tessModel.availableLanguages, tessModel.downloadedLanguages) { + val lowerQuery = query.lowercase() + + filteredAvailableLanguages = tessModel.availableLanguages.filter { tessLang -> + tessLang.path.replace(TessHelper.DATA_FILE_SUFFIX, "").contains(lowerQuery) && + tessModel.downloadedLanguages.none { + tessLang.path.replace(TessHelper.DATA_FILE_SUFFIX, "") == it + } + } + + filteredDownloadedLanguages = tessModel.downloadedLanguages.filter { lang -> + lowerQuery.contains(lang) + } + } + val onDownloadComplete = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { // Refresh the downloaded languages every time a download finishes - downloadedLanguages = TessHelper.getDownloadedLanguages(context) + tessModel.refreshDownloadedLanguages(context) } } DisposableEffect(Unit) { - context.registerReceiver( + ContextCompat.registerReceiver( + context, onDownloadComplete, - IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE) + IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), + ContextCompat.RECEIVER_EXPORTED ) onDispose { context.unregisterReceiver(onDownloadComplete) } } - LaunchedEffect(Unit) { - availableLanguages = withContext(Dispatchers.IO) { - TessHelper.getAvailableLanguages() - } - } - - val topAppBarBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - FullscreenDialog( onDismissRequest = onDismissRequest, topBar = { - LargeTopAppBar( - title = { Text(stringResource(R.string.image_translation)) }, + SearchAppBar( + title = stringResource(R.string.image_translation), + value = query, + onValueChange = { query = it }, scrollBehavior = topAppBarBehavior, navigationIcon = { - StyledIconButton(imageVector = Icons.Default.ArrowBack, onClick = onDismissRequest) - } + StyledIconButton( + imageVector = Icons.Default.ArrowBack, + onClick = onDismissRequest + ) + }, + actions = {} ) }, content = { @@ -130,19 +155,22 @@ fun TessSettings( .fillMaxWidth() .nestedScroll(topAppBarBehavior.nestedScrollConnection) ) { - SelectionContainer( - modifier = Modifier.padding(horizontal = 12.dp) - ) { - Text(text = stringResource(R.string.tess_summary, TessHelper.tessRepoUrl)) - } - Spacer(modifier = Modifier.height(12.dp)) - - // downloaded languages LazyColumn( modifier = Modifier .fillMaxWidth() + .weight(1f), + horizontalAlignment = Alignment.CenterHorizontally ) { - items(downloadedLanguages) { + item { + SelectionContainer( + modifier = Modifier.padding(horizontal = 12.dp) + ) { + Text(text = stringResource(R.string.tess_summary, TessHelper.tessRepoUrl)) + } + Spacer(modifier = Modifier.height(12.dp)) + } + + items(filteredDownloadedLanguages) { TessSettingsRow( packName = "$it${TessHelper.DATA_FILE_SUFFIX}", size = null, @@ -151,7 +179,7 @@ fun TessSettings( ) { StyledIconButton(imageVector = Icons.Default.Delete) { if (TessHelper.deleteLanguage(context, it)) { - downloadedLanguages = TessHelper.getDownloadedLanguages(context) + tessModel.refreshDownloadedLanguages(context) } else { Toast.makeText( context, @@ -162,27 +190,18 @@ fun TessSettings( } } } - } - Divider( - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(10.dp) - .size(70.dp, 1.dp) - ) - // not yet downloaded languages - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .weight(1f) - ) { - val notYetDownloadedLanguages = availableLanguages.filter { tessLang -> - downloadedLanguages.none { - tessLang.path.replace(TessHelper.DATA_FILE_SUFFIX, "") == it - } + + item { + HorizontalDivider( + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(10.dp) + .size(70.dp, 1.dp) + ) } - items(notYetDownloadedLanguages) { + items(filteredAvailableLanguages) { TessSettingsRow( packName = it.path, size = it.size,