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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.datastore.preferences.core.Preferences 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.SubtypesSetting
import org.futo.inputmethod.latin.uix.settings.NavigationItem import org.futo.inputmethod.latin.uix.settings.NavigationItem
import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle 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.ScrollableList
import org.futo.inputmethod.latin.uix.settings.Tip import org.futo.inputmethod.latin.uix.settings.Tip
import org.futo.inputmethod.latin.uix.settings.useDataStore 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 importing = remember { mutableStateOf(false) }
val importingLanguage = remember { mutableStateOf("") } val importingLanguage = remember { mutableStateOf("") }
ScrollableList { ScrollableList {
ScreenTitle(title = "Resource Importer") ScreenTitleWithIcon(title = "Import ${fileKind.kind.kindTitle()}", painter = painterResource(id = fileKind.kind.icon()))
if(fileKind.kind == FileKind.Invalid) { 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.") 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 { } else {
Text("You are importing a ${fileKind.kind.youAreImporting()}. ${fileKind.name?.let { "Info: $it" } ?: ""}", modifier = Modifier.padding(8.dp)) fileKind.name?.let {
Text("Info: $it", modifier = Modifier.padding(16.dp, 8.dp))
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
}
if(importing.value) { if(importing.value) {
Box(modifier = Modifier 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()) Text("Importing for ${importingLanguage.value}", textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth())
} else { } else {
Text( Text(
"Which language would you like to set the ${fileKind.kind.youAreImporting()} for?", "Select the language to import for:",
modifier = Modifier.padding(8.dp) modifier = Modifier.padding(16.dp, 8.dp)
) )
val languages = getActiveLanguages(context).let { val languages = getActiveLanguages(context).let {
@ -163,7 +165,16 @@ enum class FileKind {
VoiceInput, VoiceInput,
Transformer, Transformer,
Dictionary, 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 { 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 { fun FileKind.extension(): String {
return when(this) { return when(this) {
FileKind.VoiceInput -> ".bin" 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 @Composable
@Preview @Preview
fun Tip(text: String = "This is an example tip") { 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.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking 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.THEME_KEY
import org.futo.inputmethod.latin.uix.USE_SYSTEM_VOICE_INPUT import org.futo.inputmethod.latin.uix.USE_SYSTEM_VOICE_INPUT
import org.futo.inputmethod.latin.uix.deferGetSetting 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.ThemeOptions
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme 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.latin.xlm.ModelPaths
import org.futo.inputmethod.updates.checkForUpdateAndSaveToPreferences import org.futo.inputmethod.updates.checkForUpdateAndSaveToPreferences
import java.io.File import java.io.File
@ -222,14 +221,13 @@ class SettingsActivity : ComponentActivity() {
if(requestCode == IMPORT_GGUF_MODEL_REQUEST && resultCode == Activity.RESULT_OK) { if(requestCode == IMPORT_GGUF_MODEL_REQUEST && resultCode == Activity.RESULT_OK) {
data?.data?.also { uri -> data?.data?.also { uri ->
try { val intent = Intent()
val model = ModelPaths.importModel(this, uri) intent.setClass(this, ImportResourceActivity::class.java)
navController.navigate("model/${model.absolutePath.urlEncode()}") intent.setFlags(
}catch(error: IllegalArgumentException) { Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
navController.navigateToError(getString(R.string.model_import_failed), error.message ?: getString( )
R.string.failed_to_import_the_selected_model intent.setData(uri)
)) startActivity(intent)
}
} }
} else if(requestCode == EXPORT_GGUF_MODEL_REQUEST && resultCode == Activity.RESULT_OK && fileBeingSaved != null) { } else if(requestCode == EXPORT_GGUF_MODEL_REQUEST && resultCode == Activity.RESULT_OK && fileBeingSaved != null) {
data?.data?.also { uri -> 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.Spacer
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn 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.FileKind
import org.futo.inputmethod.latin.uix.ResourceHelper import org.futo.inputmethod.latin.uix.ResourceHelper
import org.futo.inputmethod.latin.uix.getSetting 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.namePreferenceKeyFor
import org.futo.inputmethod.latin.uix.settings.NavigationItem import org.futo.inputmethod.latin.uix.settings.NavigationItem
import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle
import org.futo.inputmethod.latin.uix.settings.ScreenTitle 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.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.settings.useDataStoreValueBlocking
import org.futo.inputmethod.latin.uix.theme.Typography import org.futo.inputmethod.latin.uix.theme.Typography
import org.futo.inputmethod.latin.uix.youAreImporting import org.futo.inputmethod.latin.uix.youAreImporting
@ -59,41 +62,56 @@ data class LanguageOptions(
) )
@Composable @Composable
fun ConfirmDeleteResourceDialog( fun ConfirmResourceActionDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onConfirmation: () -> Unit,
dialogTitle: String, onExplore: () -> Unit,
dialogText: String, onDelete: () -> Unit,
onImport: () -> Unit,
resourceKind: FileKind,
isCurrentlySet: Boolean,
locale: Locale
) { ) {
AlertDialog( AlertDialog(
icon = { icon = {
Icon(painterResource(id = R.drawable.delete), contentDescription = "Example Icon") Icon(painterResource(id = resourceKind.icon()), contentDescription = "Action")
}, },
title = { title = {
Text(text = dialogTitle) Text(text = "${locale.displayLanguage} - ${resourceKind.kindTitle()}")
}, },
text = { 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 = {
onDismissRequest() onDismissRequest()
}, },
confirmButton = { confirmButton = {
TextButton( TextButton(onClick = {
onClick = { onImport()
onConfirmation() }) {
} Text(
) { if(isCurrentlySet) {
Text("Delete") "Replace"
} else {
"Import"
}
)
} }
}, },
dismissButton = { dismissButton = {
TextButton( if(isCurrentlySet) {
onClick = { TextButton(onClick = { onDelete() }) {
onDismissRequest() Text("Delete")
}
} else {
TextButton(onClick = { onExplore() }) {
Text("Explore")
} }
) {
Text("Cancel")
} }
} }
) )
@ -119,14 +137,24 @@ fun LanguagesScreen(navController: NavHostController = rememberNavController())
if(deleteDialogInfo.value != null) { if(deleteDialogInfo.value != null) {
val info = deleteDialogInfo.value!! val info = deleteDialogInfo.value!!
ConfirmDeleteResourceDialog( ConfirmResourceActionDialog(
onDismissRequest = { deleteDialogInfo.value = null }, 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) ResourceHelper.deleteResourceForLanguage(context, info.kind, info.locale)
deleteDialogInfo.value = null deleteDialogInfo.value = null
}, },
dialogTitle = "Delete ${info.kind.youAreImporting()} for ${info.locale.displayLanguage}?", onImport = {
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." openModelImporter(context)
deleteDialogInfo.value = null
},
resourceKind = info.kind,
locale = info.locale,
isCurrentlySet = runBlocking { ResourceHelper.findFileForKind(context, info.locale, info.kind)?.exists() == true }
) )
} }
LazyColumn { LazyColumn {
@ -209,37 +237,25 @@ fun LanguagesScreen(navController: NavHostController = rememberNavController())
title = options.voiceInputModel ?: "None", title = options.voiceInputModel ?: "None",
style = options.voiceInputModel?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow, style = options.voiceInputModel?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow,
navigate = { navigate = {
if(runBlocking { ResourceHelper.findFileForKind(context, locale, FileKind.VoiceInput) } == null) { deleteDialogInfo.value = DeleteInfo(locale, FileKind.VoiceInput)
context.openURI("https://keyboard.futo.org/voice-input-models", true)
} else {
deleteDialogInfo.value = DeleteInfo(locale, FileKind.VoiceInput)
}
}, },
icon = painterResource(id = R.drawable.mic_fill) icon = painterResource(FileKind.VoiceInput.icon())
) )
NavigationItem( NavigationItem(
title = options.dictionary ?: "None", title = options.dictionary ?: "None",
style = options.dictionary?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow, style = options.dictionary?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow,
navigate = { navigate = {
if(runBlocking { ResourceHelper.findFileForKind(context, locale, FileKind.Dictionary) } == null) { deleteDialogInfo.value = DeleteInfo(locale, FileKind.Dictionary)
context.openURI("https://keyboard.futo.org/dictionaries", true)
} else {
deleteDialogInfo.value = DeleteInfo(locale, FileKind.Dictionary)
}
}, },
icon = painterResource(id = R.drawable.book) icon = painterResource(FileKind.Dictionary.icon())
) )
NavigationItem( NavigationItem(
title = options.transformerModel ?: "None", title = options.transformerModel ?: "None",
style = options.transformerModel?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow, style = options.transformerModel?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow,
navigate = { navigate = {
if(options.transformerModel == null) { navController.navigate("models")
context.openURI("https://keyboard.futo.org/models", true)
} else {
navController.navigate("models")
}
}, },
icon = painterResource(id = R.drawable.cpu) icon = painterResource(FileKind.Transformer.icon())
) )
if(subtypes.size > 1) { if(subtypes.size > 1) {
subtypes.forEach { 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", title = "Import from file",
style = NavigationItemStyle.Misc, style = NavigationItemStyle.Misc,
navigate = { navigate = {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { openModelImporter(context)
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/octet-stream"
}
(context as Activity).startActivityForResult(intent, IMPORT_GGUF_MODEL_REQUEST)
} }
) )
} }

View File

@ -1,5 +1,6 @@
package org.futo.inputmethod.latin.uix.settings.pages.modelmanager package org.futo.inputmethod.latin.uix.settings.pages.modelmanager
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
@ -24,6 +25,7 @@ import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import org.futo.inputmethod.latin.uix.settings.EXPORT_GGUF_MODEL_REQUEST 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.uix.settings.SettingsActivity
import org.futo.inputmethod.latin.xlm.ModelInfo import org.futo.inputmethod.latin.xlm.ModelInfo
import org.futo.inputmethod.latin.xlm.ModelInfoLoader 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)
}