Update import/delete dialog for Languages screen

This commit is contained in:
Aleksandras Kostarevas 2024-05-25 18:14:57 +03:00
parent 3208ab3050
commit a116eb73a2
6 changed files with 170 additions and 65 deletions

View File

@ -23,6 +23,7 @@ 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.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.datastore.preferences.core.Preferences
@ -42,7 +43,7 @@ import org.futo.inputmethod.latin.Subtypes
import org.futo.inputmethod.latin.SubtypesSetting
import org.futo.inputmethod.latin.uix.settings.NavigationItem
import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle
import org.futo.inputmethod.latin.uix.settings.ScreenTitle
import org.futo.inputmethod.latin.uix.settings.ScreenTitleWithIcon
import org.futo.inputmethod.latin.uix.settings.ScrollableList
import org.futo.inputmethod.latin.uix.settings.Tip
import org.futo.inputmethod.latin.uix.settings.useDataStore
@ -98,7 +99,7 @@ fun ImportScreen(fileKind: FileKindAndInfo, file: String?, onApply: (FileKindAnd
val importing = remember { mutableStateOf(false) }
val importingLanguage = remember { mutableStateOf("") }
ScrollableList {
ScreenTitle(title = "Resource Importer")
ScreenTitleWithIcon(title = "Import ${fileKind.kind.kindTitle()}", painter = painterResource(id = fileKind.kind.icon()))
if(fileKind.kind == FileKind.Invalid) {
Text("This file does not appear to be a dictionary, voice input or transformer model. It may be an invalid file or corrupted. Please try a different file.")
@ -111,9 +112,10 @@ fun ImportScreen(fileKind: FileKindAndInfo, file: String?, onApply: (FileKindAnd
}
)
} else {
Text("You are importing a ${fileKind.kind.youAreImporting()}. ${fileKind.name?.let { "Info: $it" } ?: ""}", modifier = Modifier.padding(8.dp))
Spacer(modifier = Modifier.height(32.dp))
fileKind.name?.let {
Text("Info: $it", modifier = Modifier.padding(16.dp, 8.dp))
Spacer(modifier = Modifier.height(32.dp))
}
if(importing.value) {
Box(modifier = Modifier
@ -124,8 +126,8 @@ fun ImportScreen(fileKind: FileKindAndInfo, file: String?, onApply: (FileKindAnd
Text("Importing for ${importingLanguage.value}", textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth())
} else {
Text(
"Which language would you like to set the ${fileKind.kind.youAreImporting()} for?",
modifier = Modifier.padding(8.dp)
"Select the language to import for:",
modifier = Modifier.padding(16.dp, 8.dp)
)
val languages = getActiveLanguages(context).let {
@ -163,7 +165,16 @@ enum class FileKind {
VoiceInput,
Transformer,
Dictionary,
Invalid
Invalid;
fun getAddonUrlForLocale(locale: Locale?): String {
return when(this) {
VoiceInput -> "https://keyboard.futo.org/voice-input-models?locale=${locale?.toLanguageTag() ?: ""}"
Transformer -> "https://keyboard.futo.org/models?locale=${locale?.toLanguageTag() ?: ""}"
Dictionary -> "https://keyboard.futo.org/dictionaries?locale=${locale?.toLanguageTag() ?: ""}"
Invalid -> "https://keyboard.futo.org/"
}
}
}
fun FileKind.youAreImporting(): String {
@ -175,6 +186,25 @@ fun FileKind.youAreImporting(): String {
}
}
fun FileKind.kindTitle(): String {
return when(this) {
FileKind.VoiceInput -> "Voice Input"
FileKind.Transformer -> "Transformer"
FileKind.Dictionary -> "Dictionary"
FileKind.Invalid -> "(invalid)"
}
}
fun FileKind.icon(): Int {
return when(this) {
FileKind.VoiceInput -> R.drawable.mic_fill
FileKind.Transformer -> R.drawable.cpu
FileKind.Dictionary -> R.drawable.book
FileKind.Invalid -> R.drawable.close
}
}
fun FileKind.extension(): String {
return when(this) {
FileKind.VoiceInput -> ".bin"

View File

@ -90,6 +90,19 @@ fun ScreenTitle(title: String, showBack: Boolean = false, navController: NavHost
}
}
@Composable
fun ScreenTitleWithIcon(title: String, painter: Painter) {
Row(modifier = Modifier.fillMaxWidth()) {
Spacer(modifier = Modifier.width(16.dp))
Icon(painter, contentDescription = "", modifier = Modifier.align(CenterVertically))
Spacer(modifier = Modifier.width(18.dp))
Text(title, style = Typography.titleLarge, modifier = Modifier
.align(CenterVertically)
.padding(0.dp, 16.dp))
}
}
@Composable
@Preview
fun Tip(text: String = "This is an example tip") {

View File

@ -30,7 +30,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.futo.inputmethod.latin.R
import org.futo.inputmethod.latin.uix.ImportResourceActivity
import org.futo.inputmethod.latin.uix.THEME_KEY
import org.futo.inputmethod.latin.uix.USE_SYSTEM_VOICE_INPUT
import org.futo.inputmethod.latin.uix.deferGetSetting
@ -40,7 +40,6 @@ import org.futo.inputmethod.latin.uix.theme.ThemeOption
import org.futo.inputmethod.latin.uix.theme.ThemeOptions
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme
import org.futo.inputmethod.latin.uix.urlEncode
import org.futo.inputmethod.latin.xlm.ModelPaths
import org.futo.inputmethod.updates.checkForUpdateAndSaveToPreferences
import java.io.File
@ -222,14 +221,13 @@ class SettingsActivity : ComponentActivity() {
if(requestCode == IMPORT_GGUF_MODEL_REQUEST && resultCode == Activity.RESULT_OK) {
data?.data?.also { uri ->
try {
val model = ModelPaths.importModel(this, uri)
navController.navigate("model/${model.absolutePath.urlEncode()}")
}catch(error: IllegalArgumentException) {
navController.navigateToError(getString(R.string.model_import_failed), error.message ?: getString(
R.string.failed_to_import_the_selected_model
))
}
val intent = Intent()
intent.setClass(this, ImportResourceActivity::class.java)
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
)
intent.setData(uri)
startActivity(intent)
}
} else if(requestCode == EXPORT_GGUF_MODEL_REQUEST && resultCode == Activity.RESULT_OK && fileBeingSaved != null) {
data?.data?.also { uri ->

View File

@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
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.lazy.LazyColumn
@ -39,12 +40,14 @@ import org.futo.inputmethod.latin.common.Constants
import org.futo.inputmethod.latin.uix.FileKind
import org.futo.inputmethod.latin.uix.ResourceHelper
import org.futo.inputmethod.latin.uix.getSetting
import org.futo.inputmethod.latin.uix.icon
import org.futo.inputmethod.latin.uix.kindTitle
import org.futo.inputmethod.latin.uix.namePreferenceKeyFor
import org.futo.inputmethod.latin.uix.settings.NavigationItem
import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle
import org.futo.inputmethod.latin.uix.settings.ScreenTitle
import org.futo.inputmethod.latin.uix.settings.ScrollableList
import org.futo.inputmethod.latin.uix.settings.SettingItem
import org.futo.inputmethod.latin.uix.settings.pages.modelmanager.openModelImporter
import org.futo.inputmethod.latin.uix.settings.useDataStoreValueBlocking
import org.futo.inputmethod.latin.uix.theme.Typography
import org.futo.inputmethod.latin.uix.youAreImporting
@ -59,41 +62,56 @@ data class LanguageOptions(
)
@Composable
fun ConfirmDeleteResourceDialog(
fun ConfirmResourceActionDialog(
onDismissRequest: () -> Unit,
onConfirmation: () -> Unit,
dialogTitle: String,
dialogText: String,
onExplore: () -> Unit,
onDelete: () -> Unit,
onImport: () -> Unit,
resourceKind: FileKind,
isCurrentlySet: Boolean,
locale: Locale
) {
AlertDialog(
icon = {
Icon(painterResource(id = R.drawable.delete), contentDescription = "Example Icon")
Icon(painterResource(id = resourceKind.icon()), contentDescription = "Action")
},
title = {
Text(text = dialogTitle)
Text(text = "${locale.displayLanguage} - ${resourceKind.kindTitle()}")
},
text = {
Text(text = dialogText)
if(isCurrentlySet) {
Text(text = "Would you like to delete ${resourceKind.youAreImporting()} for ${locale.displayLanguage}, or replace it with another file?")
} else {
Text(text = "No ${resourceKind.youAreImporting()} override is set for ${locale.displayLanguage}. You can explore downloads online, or import an existing file.")
}
},
onDismissRequest = {
onDismissRequest()
},
confirmButton = {
TextButton(
onClick = {
onConfirmation()
}
) {
Text("Delete")
TextButton(onClick = {
onImport()
}) {
Text(
if(isCurrentlySet) {
"Replace"
} else {
"Import"
}
)
}
},
dismissButton = {
TextButton(
onClick = {
onDismissRequest()
if(isCurrentlySet) {
TextButton(onClick = { onDelete() }) {
Text("Delete")
}
} else {
TextButton(onClick = { onExplore() }) {
Text("Explore")
}
) {
Text("Cancel")
}
}
)
@ -119,14 +137,24 @@ fun LanguagesScreen(navController: NavHostController = rememberNavController())
if(deleteDialogInfo.value != null) {
val info = deleteDialogInfo.value!!
ConfirmDeleteResourceDialog(
ConfirmResourceActionDialog(
onDismissRequest = { deleteDialogInfo.value = null },
onConfirmation = {
onExplore = {
context.openURI(info.kind.getAddonUrlForLocale(info.locale), true)
deleteDialogInfo.value = null
},
onDelete = {
ResourceHelper.deleteResourceForLanguage(context, info.kind, info.locale)
deleteDialogInfo.value = null
},
dialogTitle = "Delete ${info.kind.youAreImporting()} for ${info.locale.displayLanguage}?",
dialogText = "If deleted, the imported ${info.kind.youAreImporting()} file for ${info.locale.displayLanguage} will be deleted. If there is no built-in fallback for this language, the feature may cease to function. You can always download and re-import a different ${info.kind.youAreImporting()} file."
onImport = {
openModelImporter(context)
deleteDialogInfo.value = null
},
resourceKind = info.kind,
locale = info.locale,
isCurrentlySet = runBlocking { ResourceHelper.findFileForKind(context, info.locale, info.kind)?.exists() == true }
)
}
LazyColumn {
@ -209,37 +237,25 @@ fun LanguagesScreen(navController: NavHostController = rememberNavController())
title = options.voiceInputModel ?: "None",
style = options.voiceInputModel?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow,
navigate = {
if(runBlocking { ResourceHelper.findFileForKind(context, locale, FileKind.VoiceInput) } == null) {
context.openURI("https://keyboard.futo.org/voice-input-models", true)
} else {
deleteDialogInfo.value = DeleteInfo(locale, FileKind.VoiceInput)
}
deleteDialogInfo.value = DeleteInfo(locale, FileKind.VoiceInput)
},
icon = painterResource(id = R.drawable.mic_fill)
icon = painterResource(FileKind.VoiceInput.icon())
)
NavigationItem(
title = options.dictionary ?: "None",
style = options.dictionary?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow,
navigate = {
if(runBlocking { ResourceHelper.findFileForKind(context, locale, FileKind.Dictionary) } == null) {
context.openURI("https://keyboard.futo.org/dictionaries", true)
} else {
deleteDialogInfo.value = DeleteInfo(locale, FileKind.Dictionary)
}
deleteDialogInfo.value = DeleteInfo(locale, FileKind.Dictionary)
},
icon = painterResource(id = R.drawable.book)
icon = painterResource(FileKind.Dictionary.icon())
)
NavigationItem(
title = options.transformerModel ?: "None",
style = options.transformerModel?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow,
navigate = {
if(options.transformerModel == null) {
context.openURI("https://keyboard.futo.org/models", true)
} else {
navController.navigate("models")
}
navController.navigate("models")
},
icon = painterResource(id = R.drawable.cpu)
icon = painterResource(FileKind.Transformer.icon())
)
if(subtypes.size > 1) {
subtypes.forEach {
@ -260,7 +276,48 @@ fun LanguagesScreen(navController: NavHostController = rememberNavController())
}
}
}
}
item {
Spacer(modifier = Modifier.height(32.dp))
ScreenTitle("Other options")
NavigationItem(
title = "Import resource file",
style = NavigationItemStyle.Misc,
navigate = {
openModelImporter(context)
},
)
NavigationItem(
title = "Explore voice input models",
style = NavigationItemStyle.Misc,
navigate = {
context.openURI(
FileKind.VoiceInput.getAddonUrlForLocale(null),
true
)
},
)
NavigationItem(
title = "Explore dictionaries",
style = NavigationItemStyle.Misc,
navigate = {
context.openURI(
FileKind.Dictionary.getAddonUrlForLocale(null),
true
)
},
)
NavigationItem(
title = "Explore transformer models",
style = NavigationItemStyle.Misc,
navigate = {
context.openURI(
FileKind.Transformer.getAddonUrlForLocale(null),
true
)
},
)
}
}
}

View File

@ -100,12 +100,7 @@ fun ModelListScreen(navController: NavHostController = rememberNavController())
title = "Import from file",
style = NavigationItemStyle.Misc,
navigate = {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/octet-stream"
}
(context as Activity).startActivityForResult(intent, IMPORT_GGUF_MODEL_REQUEST)
openModelImporter(context)
}
)
}

View File

@ -1,5 +1,6 @@
package org.futo.inputmethod.latin.uix.settings.pages.modelmanager
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.view.ContextThemeWrapper
@ -24,6 +25,7 @@ import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import org.futo.inputmethod.latin.uix.settings.EXPORT_GGUF_MODEL_REQUEST
import org.futo.inputmethod.latin.uix.settings.IMPORT_GGUF_MODEL_REQUEST
import org.futo.inputmethod.latin.uix.settings.SettingsActivity
import org.futo.inputmethod.latin.xlm.ModelInfo
import org.futo.inputmethod.latin.xlm.ModelInfoLoader
@ -141,3 +143,13 @@ fun ModelPicker(
}
}
}
fun openModelImporter(context: Context) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/octet-stream"
}
(context as Activity).startActivityForResult(intent, IMPORT_GGUF_MODEL_REQUEST)
}