diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index 4bc131b9c..b4084ab5a 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -87,12 +87,12 @@
-
@@ -106,25 +106,6 @@
android:taskAffinity="">
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 151f87308..1f02f3b40 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -113,7 +113,7 @@
diff --git a/java/src/org/futo/inputmethod/latin/LatinIME.kt b/java/src/org/futo/inputmethod/latin/LatinIME.kt
index 0441f34a8..b207b5079 100644
--- a/java/src/org/futo/inputmethod/latin/LatinIME.kt
+++ b/java/src/org/futo/inputmethod/latin/LatinIME.kt
@@ -185,12 +185,12 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
override fun onCreate() {
super.onCreate()
- colorSchemeLoaderJob = deferGetSetting(THEME_KEY, DynamicSystemTheme.key) {
- var themeKey = it
- var themeOption = ThemeOptions[themeKey]
- if (themeOption == null || !themeOption.available(this@LatinIME)) {
- themeKey = VoiceInputTheme.key
- themeOption = ThemeOptions[themeKey]!!
+ colorSchemeLoaderJob = deferGetSetting(THEME_KEY) {
+ val themeOptionFromSettings = ThemeOptions[it]
+ val themeOption = when {
+ themeOptionFromSettings == null -> VoiceInputTheme
+ !themeOptionFromSettings.available(this@LatinIME) -> VoiceInputTheme
+ else -> themeOptionFromSettings
}
activeThemeOption = themeOption
@@ -418,11 +418,15 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
override fun onFinishInputView(finishingInput: Boolean) {
super.onFinishInputView(finishingInput)
latinIMELegacy.onFinishInputView(finishingInput)
+
+ closeActionWindow()
}
override fun onFinishInput() {
super.onFinishInput()
latinIMELegacy.onFinishInput()
+
+ closeActionWindow()
}
override fun onCurrentInputMethodSubtypeChanged(newSubtype: InputMethodSubtype?) {
@@ -440,6 +444,8 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
override fun onWindowHidden() {
super.onWindowHidden()
latinIMELegacy.onWindowHidden()
+
+ closeActionWindow()
}
override fun onUpdateSelection(
@@ -636,6 +642,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
}
override fun closeActionWindow() {
+ if(currWindowActionWindow == null) return
returnBackToMainKeyboardViewFromAction()
}
diff --git a/java/src/org/futo/inputmethod/latin/uix/Settings.kt b/java/src/org/futo/inputmethod/latin/uix/Settings.kt
index a91cba25f..8011eb50e 100644
--- a/java/src/org/futo/inputmethod/latin/uix/Settings.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/Settings.kt
@@ -6,7 +6,6 @@ import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
-import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
@@ -18,6 +17,7 @@ import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
+import org.futo.inputmethod.latin.uix.theme.presets.DynamicSystemTheme
val Context.dataStore: DataStore by preferencesDataStore(name = "settings")
@@ -28,6 +28,10 @@ suspend fun Context.getSetting(key: Preferences.Key, default: T): T {
return valueFlow.first()
}
+fun Context.getSettingFlow(key: Preferences.Key, default: T): Flow {
+ return dataStore.data.map { preferences -> preferences[key] ?: default }.take(1)
+}
+
suspend fun Context.setSetting(key: Preferences.Key, value: T) {
this.dataStore.edit { preferences ->
preferences[key] = value
@@ -75,16 +79,27 @@ data class SettingsKey(
)
suspend fun Context.getSetting(key: SettingsKey): T {
- val valueFlow: Flow =
- this.dataStore.data.map { preferences -> preferences[key.key] ?: key.default }.take(1)
+ return getSetting(key.key, key.default)
+}
- return valueFlow.first()
+fun Context.getSettingFlow(key: SettingsKey): Flow {
+ return getSettingFlow(key.key, key.default)
}
suspend fun Context.setSetting(key: SettingsKey, value: T) {
- this.dataStore.edit { preferences ->
- preferences[key.key] = value
- }
+ return setSetting(key.key, value)
}
-val THEME_KEY = stringPreferencesKey("activeThemeOption")
\ No newline at end of file
+fun LifecycleOwner.deferGetSetting(key: SettingsKey, onObtained: (T) -> Unit): Job {
+ return deferGetSetting(key.key, key.default, onObtained)
+}
+
+fun LifecycleOwner.deferSetSetting(key: SettingsKey, value: T): Job {
+ return deferSetSetting(key.key, value)
+}
+
+
+val THEME_KEY = SettingsKey(
+ key = stringPreferencesKey("activeThemeOption"),
+ default = DynamicSystemTheme.key
+)
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt b/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt
index 45ca031eb..2aa629f12 100644
--- a/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt
@@ -3,7 +3,9 @@ package org.futo.inputmethod.latin.uix.actions
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
@@ -111,6 +113,7 @@ private class VoiceInputActionWindow(
}
private var recognizerView: MutableState = mutableStateOf(null)
+ private var modelException: MutableState = mutableStateOf(null)
private val initJob = manager.getLifecycleScope().launch {
yield()
@@ -128,8 +131,7 @@ private class VoiceInputActionWindow(
modelManager = state.modelManager
)
} catch(e: ModelDoesNotExistException) {
- // TODO: Show an error to the user, with an option to download
- close()
+ modelException.value = e
return@launch
}
@@ -151,6 +153,15 @@ private class VoiceInputActionWindow(
return inputTransaction!!
}
+ @Composable
+ private fun ModelDownloader(modelException: ModelDoesNotExistException) {
+ Column {
+ Text("Model Download Required")
+ Text("Not yet implemented")
+ // TODO
+ }
+ }
+
@Composable
override fun windowName(): String {
return stringResource(R.string.voice_input_action_title)
@@ -167,7 +178,10 @@ private class VoiceInputActionWindow(
indication = null,
interactionSource = remember { MutableInteractionSource() })) {
Box(modifier = Modifier.align(Alignment.Center)) {
- recognizerView.value?.Content()
+ when {
+ modelException.value != null -> ModelDownloader(modelException.value!!)
+ recognizerView.value != null -> recognizerView.value!!.Content()
+ }
}
}
}
@@ -178,12 +192,14 @@ private class VoiceInputActionWindow(
}
private var wasFinished = false
+ private var cancelPlayed = false
override fun cancelled() {
if (!wasFinished) {
- if (shouldPlaySounds) {
+ if (shouldPlaySounds && !cancelPlayed) {
state.soundPlayer.playCancelSound()
+ cancelPlayed = true
}
- getOrStartInputTransaction().cancel()
+ inputTransaction?.cancel()
}
}
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/Components.kt b/java/src/org/futo/inputmethod/latin/uix/settings/Components.kt
new file mode 100644
index 000000000..336cd6947
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/Components.kt
@@ -0,0 +1,295 @@
+package org.futo.inputmethod.latin.uix.settings
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.ArrowForward
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Switch
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Alignment.Companion.CenterVertically
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import org.futo.inputmethod.latin.uix.SettingsKey
+import org.futo.inputmethod.latin.uix.theme.Typography
+
+@Composable
+fun ScreenTitle(title: String, showBack: Boolean = false, navController: NavHostController = rememberNavController()) {
+ val rowModifier = if(showBack) {
+ Modifier
+ .fillMaxWidth()
+ .clickable { navController.popBackStack() }
+ } else {
+ Modifier.fillMaxWidth()
+ }
+ Row(modifier = rowModifier) {
+ Spacer(modifier = Modifier.width(16.dp))
+
+ if(showBack) {
+ Icon(Icons.Default.ArrowBack, contentDescription = "Back", 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") {
+ Surface(
+ color = MaterialTheme.colorScheme.primaryContainer, modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp), shape = RoundedCornerShape(4.dp)
+ ) {
+ Text(
+ text,
+ modifier = Modifier.padding(8.dp),
+ style = Typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onPrimaryContainer
+ )
+ }
+}
+
+
+@Composable
+fun SettingItem(
+ title: String,
+ subtitle: String? = null,
+ onClick: () -> Unit,
+ icon: (@Composable () -> Unit)? = null,
+ disabled: Boolean = false,
+ content: @Composable () -> Unit
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .defaultMinSize(0.dp, 68.dp)
+ .clickable(enabled = !disabled, onClick = {
+ if (!disabled) {
+ onClick()
+ }
+ })
+ .padding(0.dp, 4.dp, 0.dp, 4.dp)
+ ) {
+ Spacer(modifier = Modifier.width(16.dp))
+ Column(
+ modifier = Modifier
+ .width(48.dp)
+ .align(Alignment.CenterVertically)
+ ) {
+ Box(modifier = Modifier.align(Alignment.CenterHorizontally)) {
+ if (icon != null) {
+ icon()
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.width(12.dp))
+
+ Row(
+ modifier = Modifier
+ .weight(1f)
+ .align(Alignment.CenterVertically)
+ .alpha(
+ if (disabled) {
+ 0.5f
+ } else {
+ 1.0f
+ }
+ )
+ ) {
+ Column {
+ Text(title, style = Typography.bodyLarge)
+
+ if (subtitle != null) {
+ Text(
+ subtitle,
+ style = Typography.bodySmall,
+ color = MaterialTheme.colorScheme.outline
+ )
+ }
+ }
+ }
+ Box(modifier = Modifier.align(Alignment.CenterVertically)) {
+ content()
+ }
+
+ Spacer(modifier = Modifier.width(12.dp))
+ }
+}
+
+@Composable
+fun SettingToggleRaw(
+ title: String,
+ enabled: Boolean,
+ setValue: (Boolean) -> Unit,
+ subtitle: String? = null,
+ disabled: Boolean = false,
+ icon: (@Composable () -> Unit)? = null
+) {
+ SettingItem(
+ title = title,
+ subtitle = subtitle,
+ onClick = {
+ if (!disabled) {
+ setValue(!enabled)
+ }
+ },
+ icon = icon
+ ) {
+ Switch(checked = enabled, onCheckedChange = {
+ if (!disabled) {
+ setValue(!enabled)
+ }
+ }, enabled = !disabled)
+ }
+}
+
+@Composable
+fun SettingToggleDataStoreItem(
+ title: String,
+ dataStoreItem: DataStoreItem,
+ subtitle: String? = null,
+ disabledSubtitle: String? = null,
+ disabled: Boolean = false,
+ icon: (@Composable () -> Unit)? = null
+) {
+ val (enabled, setValue) = dataStoreItem
+
+ val subtitleValue = if (!enabled && disabledSubtitle != null) {
+ disabledSubtitle
+ } else {
+ subtitle
+ }
+
+ SettingToggleRaw(title, enabled, { setValue(it) }, subtitleValue, disabled, icon)
+}
+
+@Composable
+fun SettingToggleDataStore(
+ title: String,
+ setting: SettingsKey,
+ subtitle: String? = null,
+ disabledSubtitle: String? = null,
+ disabled: Boolean = false,
+ icon: (@Composable () -> Unit)? = null
+) {
+ SettingToggleDataStoreItem(
+ title, useDataStore(setting.key, setting.default), subtitle, disabledSubtitle, disabled, icon)
+}
+
+@Composable
+fun SettingToggleSharedPrefs(
+ title: String,
+ key: String,
+ default: Boolean,
+ subtitle: String? = null,
+ disabledSubtitle: String? = null,
+ disabled: Boolean = false,
+ icon: (@Composable () -> Unit)? = null
+) {
+ SettingToggleDataStoreItem(
+ title, useSharedPrefsBool(key, default), subtitle, disabledSubtitle, disabled, icon)
+}
+
+@Composable
+fun ScrollableList(content: @Composable () -> Unit) {
+ val scrollState = rememberScrollState()
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(scrollState)
+ ) {
+ content()
+ }
+}
+
+@Composable
+fun SettingListLazy(content: LazyListScope.() -> Unit) {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ content()
+ }
+}
+
+
+enum class NavigationItemStyle {
+ HomePrimary,
+ HomeSecondary,
+ HomeTertiary,
+ Misc
+}
+
+@Composable
+fun NavigationItem(title: String, style: NavigationItemStyle, navigate: () -> Unit, icon: Painter? = null) {
+ SettingItem(
+ title = title,
+ onClick = navigate,
+ icon = {
+ icon?.let {
+ val circleColor = when(style) {
+ NavigationItemStyle.HomePrimary -> MaterialTheme.colorScheme.primaryContainer
+ NavigationItemStyle.HomeSecondary -> MaterialTheme.colorScheme.secondaryContainer
+ NavigationItemStyle.HomeTertiary -> MaterialTheme.colorScheme.tertiaryContainer
+ NavigationItemStyle.Misc -> Color.Transparent
+ }
+
+ val iconColor = when(style) {
+ NavigationItemStyle.HomePrimary -> MaterialTheme.colorScheme.onPrimaryContainer
+ NavigationItemStyle.HomeSecondary -> MaterialTheme.colorScheme.onSecondaryContainer
+ NavigationItemStyle.HomeTertiary -> MaterialTheme.colorScheme.onTertiaryContainer
+ NavigationItemStyle.Misc -> MaterialTheme.colorScheme.onBackground.copy(alpha = 0.75f)
+ }
+
+ Canvas(modifier = Modifier.fillMaxSize()) {
+ drawCircle(circleColor, this.size.maxDimension / 2.4f)
+ translate(
+ left = this.size.width / 2.0f - icon.intrinsicSize.width / 2.0f,
+ top = this.size.height / 2.0f - icon.intrinsicSize.height / 2.0f
+ ) {
+ with(icon) {
+ draw(icon.intrinsicSize, colorFilter = ColorFilter.tint(iconColor))
+ }
+ }
+ }
+ }
+ }
+ ) {
+ when(style) {
+ NavigationItemStyle.Misc -> Icon(Icons.Default.ArrowForward, contentDescription = "Go")
+ else -> {}
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/Hooks.kt b/java/src/org/futo/inputmethod/latin/uix/settings/Hooks.kt
new file mode 100644
index 000000000..22799f06e
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/Hooks.kt
@@ -0,0 +1,88 @@
+package org.futo.inputmethod.latin.uix.settings
+
+import android.content.SharedPreferences
+import android.preference.PreferenceManager
+import android.provider.Settings
+import android.view.inputmethod.InputMethodManager
+import androidx.activity.ComponentActivity
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.platform.LocalContext
+import androidx.core.content.edit
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.futo.inputmethod.latin.uix.dataStore
+
+data class DataStoreItem(val value: T, val setValue: (T) -> Job)
+@Composable
+fun useDataStore(key: Preferences.Key, default: T): DataStoreItem {
+ val context = LocalContext.current
+ val coroutineScope = rememberCoroutineScope()
+
+ val enableSoundFlow: Flow = remember {
+ context.dataStore.data.map {
+ preferences -> preferences[key] ?: default
+ }
+ }
+
+ val value = enableSoundFlow.collectAsState(initial = default).value!!
+
+ val setValue = { newValue: T ->
+ coroutineScope.launch {
+ context.dataStore.edit { preferences ->
+ preferences[key] = newValue
+ }
+ }
+ }
+
+ return DataStoreItem(value, setValue)
+}
+
+@Composable
+fun useSharedPrefsBool(key: String, default: Boolean): DataStoreItem {
+ val coroutineScope = rememberCoroutineScope()
+ val context = LocalContext.current
+ val sharedPrefs = remember { PreferenceManager.getDefaultSharedPreferences(context) }
+
+ val value = remember { mutableStateOf(sharedPrefs.getBoolean(key, default)) }
+
+ // This is not the most efficient way to do this... but it works for a settings menu
+ DisposableEffect(Unit) {
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, changedKey ->
+ if (key == changedKey) {
+ value.value = sharedPreferences.getBoolean(key, value.value)
+ }
+ }
+
+ sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+
+ onDispose {
+ sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener)
+ }
+ }
+
+ val setValue = { newValue: Boolean ->
+ coroutineScope.launch {
+ withContext(Dispatchers.Main) {
+ sharedPrefs.edit {
+ putBoolean(key, newValue)
+ }
+ }
+ }
+ }
+
+ return DataStoreItem(value.value, setValue)
+}
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/SettingsActivity.kt b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsActivity.kt
new file mode 100644
index 000000000..de087f970
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsActivity.kt
@@ -0,0 +1,158 @@
+package org.futo.inputmethod.latin.uix.settings
+
+import android.content.Context
+import android.content.Context.INPUT_METHOD_SERVICE
+import android.content.Intent
+import android.os.Bundle
+import android.provider.Settings
+import android.view.inputmethod.InputMethodManager
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import org.futo.inputmethod.latin.uix.THEME_KEY
+import org.futo.inputmethod.latin.uix.deferGetSetting
+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
+
+private fun Context.isInputMethodEnabled(): Boolean {
+ val packageName = packageName
+ val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
+
+ var found = false
+ for (imi in imm.enabledInputMethodList) {
+ if (packageName == imi.packageName) {
+ found = true
+ }
+ }
+
+ return found
+}
+
+private fun Context.isDefaultIMECurrent(): Boolean {
+ val value = Settings.Secure.getString(contentResolver, Settings.Secure.DEFAULT_INPUT_METHOD)
+
+ return value.startsWith(packageName)
+}
+
+
+class SettingsActivity : ComponentActivity() {
+ private val themeOption: MutableState = mutableStateOf(null)
+
+ private val inputMethodEnabled = mutableStateOf(false)
+ private val inputMethodSelected = mutableStateOf(false)
+
+ private var wasImeEverDisabled = false
+
+ companion object {
+ private var pollJob: Job? = null
+ }
+
+ @OptIn(DelicateCoroutinesApi::class)
+ private fun updateSystemState() {
+ val inputMethodEnabled = isInputMethodEnabled()
+ val inputMethodSelected = isDefaultIMECurrent()
+ this.inputMethodEnabled.value = inputMethodEnabled
+ this.inputMethodSelected.value = inputMethodSelected
+
+ if(!inputMethodEnabled) {
+ wasImeEverDisabled = true
+ } else if(wasImeEverDisabled) {
+ // We just went from inputMethodEnabled==false to inputMethodEnabled==true
+ // This is because the user is in the input method settings screen and just turned on
+ // our IME. We can bring them back here so that they don't have to press back button
+ wasImeEverDisabled = false
+
+ val intent = Intent()
+ intent.setClass(this, SettingsActivity::class.java)
+ intent.flags = (Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ or Intent.FLAG_ACTIVITY_CLEAR_TOP)
+
+ startActivity(intent)
+ }
+
+ if(!inputMethodEnabled || !inputMethodSelected) {
+ if(pollJob == null || !pollJob!!.isActive) {
+ pollJob = GlobalScope.launch {
+ systemStatePoller()
+ }
+ }
+ }
+ }
+
+ private suspend fun systemStatePoller() {
+ while(!this.inputMethodEnabled.value || !this.inputMethodSelected.value) {
+ delay(200)
+ updateSystemState()
+ }
+ }
+
+ private fun updateContent() {
+ setContent {
+ themeOption.value?.let { themeOption ->
+ val themeIdx = useDataStore(key = THEME_KEY.key, default = themeOption.key)
+ val theme: ThemeOption = ThemeOptions[themeIdx.value] ?: themeOption
+ UixThemeWrapper(theme.obtainColors(LocalContext.current)) {
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.background
+ ) {
+ SetupOrMain(inputMethodEnabled.value, inputMethodSelected.value) {
+ SettingsNavigator()
+ }
+ }
+ }
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ updateSystemState()
+ updateContent()
+ }
+ }
+
+ deferGetSetting(THEME_KEY) {
+ val themeOptionFromSettings = ThemeOptions[it]
+ val themeOption = when {
+ themeOptionFromSettings == null -> VoiceInputTheme
+ !themeOptionFromSettings.available(this) -> VoiceInputTheme
+ else -> themeOptionFromSettings
+ }
+
+ this.themeOption.value = themeOption
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ updateSystemState()
+ }
+
+ override fun onRestart() {
+ super.onRestart()
+
+ updateSystemState()
+ }
+}
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/SettingsNavigator.kt b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsNavigator.kt
new file mode 100644
index 000000000..d1c5da1d7
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsNavigator.kt
@@ -0,0 +1,26 @@
+package org.futo.inputmethod.latin.uix.settings
+
+import androidx.compose.runtime.Composable
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import org.futo.inputmethod.latin.uix.settings.pages.HomeScreen
+import org.futo.inputmethod.latin.uix.settings.pages.PredictiveTextScreen
+import org.futo.inputmethod.latin.uix.settings.pages.TypingScreen
+import org.futo.inputmethod.latin.uix.settings.pages.VoiceInputScreen
+
+@Composable
+fun SettingsNavigator(
+ navController: NavHostController = rememberNavController()
+) {
+ NavHost(
+ navController = navController,
+ startDestination = "home"
+ ) {
+ composable("home") { HomeScreen(navController) }
+ composable("predictiveText") { PredictiveTextScreen(navController) }
+ composable("typing") { TypingScreen(navController) }
+ composable("voiceInput") { VoiceInputScreen(navController) }
+ }
+}
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/SettingsUtils.kt b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsUtils.kt
new file mode 100644
index 000000000..d15ec490c
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsUtils.kt
@@ -0,0 +1,37 @@
+package org.futo.inputmethod.latin.uix.settings
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.view.inputmethod.InputMethodManager
+import androidx.activity.ComponentActivity
+import androidx.compose.runtime.Composable
+import org.futo.inputmethod.latin.utils.UncachedInputMethodManagerUtils
+
+@Composable
+fun SetupOrMain(inputMethodEnabled: Boolean, inputMethodSelected: Boolean, main: @Composable () -> Unit) {
+ if (!inputMethodEnabled) {
+ SetupEnableIME()
+ } else if (!inputMethodSelected) {
+ SetupChangeDefaultIME()
+ } else {
+ main()
+ }
+}
+
+// TODO: We should have one central source of enabled languages to share between
+// keyboard and voice input. We need to pass current language to voice input action
+// and restrict it to that when possible. If active language has no voice input support
+// we must tell the user in the UI.
+fun Context.openLanguageSettings() {
+ val imm = getSystemService(ComponentActivity.INPUT_METHOD_SERVICE) as InputMethodManager
+
+ val imi = UncachedInputMethodManagerUtils.getInputMethodInfoOf(
+ packageName, imm
+ ) ?: return
+ val intent = Intent()
+ intent.action = Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
+ intent.addCategory(Intent.CATEGORY_DEFAULT)
+ intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imi.id)
+ startActivity(intent)
+}
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/Setup.kt b/java/src/org/futo/inputmethod/latin/uix/settings/Setup.kt
new file mode 100644
index 000000000..620d6476d
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/Setup.kt
@@ -0,0 +1,147 @@
+package org.futo.inputmethod.latin.uix.settings
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.view.inputmethod.InputMethodManager
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LinearProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+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.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import org.futo.inputmethod.latin.R
+import org.futo.inputmethod.latin.uix.theme.Typography
+
+@Composable
+fun SetupContainer(inner: @Composable () -> Unit) {
+ Box(modifier = Modifier.fillMaxSize()) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth(fraction = 1.0f)
+ .fillMaxHeight(fraction = 0.4f)
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.futo_logo),
+ contentDescription = "FUTO Logo",
+ modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxWidth(0.75f)
+ .align(Alignment.CenterHorizontally),
+ tint = MaterialTheme.colorScheme.onBackground
+ )
+ }
+
+ Row(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxHeight(fraction = 0.5f)
+ .align(Alignment.CenterVertically)
+ .padding(32.dp)
+ ) {
+ Box(modifier = Modifier.align(Alignment.CenterVertically)) {
+ inner()
+ }
+ }
+ }
+ }
+}
+
+
+@Composable
+fun Step(fraction: Float, text: String) {
+ Column(modifier = Modifier.padding(16.dp)) {
+ Text(text, style = Typography.labelSmall)
+ LinearProgressIndicator(progress = fraction, modifier = Modifier.fillMaxWidth())
+ }
+}
+
+// TODO: May wish to have a skip option
+@Composable
+@Preview
+fun SetupEnableIME() {
+ val context = LocalContext.current
+
+ val launchImeOptions = {
+ // TODO: look into direct boot to get rid of direct boot warning?
+ val intent = Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)
+
+ intent.flags = (Intent.FLAG_ACTIVITY_NEW_TASK
+ or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ or Intent.FLAG_ACTIVITY_NO_HISTORY
+ or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+
+ context.startActivity(intent)
+ }
+
+ SetupContainer {
+ Column {
+ Step(fraction = 1.0f/3.0f, text = "Setup - Step 1 of 2")
+
+ Text(
+ "To use FUTO Keyboard, you must first enable FUTO Keyboard as an input method.",
+ textAlign = TextAlign.Center,
+ modifier = Modifier.fillMaxWidth()
+ )
+ Button(
+ onClick = launchImeOptions,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp)
+ ) {
+ Text("Open Input Method Settings")
+ }
+ }
+ }
+}
+
+
+@Composable
+@Preview
+fun SetupChangeDefaultIME() {
+ val context = LocalContext.current
+
+ val launchImeOptions = {
+ val inputMethodManager =
+ context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+
+ inputMethodManager.showInputMethodPicker()
+ }
+
+ SetupContainer {
+ Column {
+ Step(fraction = 2.0f/3.0f, text = "Setup - Step 2 of 2")
+
+ Text(
+ "Next, select FUTO Keyboard as your active input method.",
+ textAlign = TextAlign.Center,
+ modifier = Modifier.fillMaxWidth()
+ )
+ Button(
+ onClick = launchImeOptions,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp)
+ ) {
+ Text("Switch Input Methods")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Home.kt b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Home.kt
new file mode 100644
index 000000000..cc1dffd2c
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Home.kt
@@ -0,0 +1,65 @@
+package org.futo.inputmethod.latin.uix.settings.pages
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import org.futo.inputmethod.latin.R
+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.openLanguageSettings
+
+@Preview
+@Composable
+fun HomeScreen(navController: NavHostController = rememberNavController()) {
+ val context = LocalContext.current
+ ScrollableList {
+ ScreenTitle("FUTO Keyboard Settings")
+ NavigationItem(
+ title = "Languages",
+ style = NavigationItemStyle.HomePrimary,
+ navigate = { context.openLanguageSettings() },
+ icon = painterResource(id = R.drawable.globe)
+ )
+
+ NavigationItem(
+ title = "Predictive Text",
+ style = NavigationItemStyle.HomeSecondary,
+ navigate = { navController.navigate("predictiveText") },
+ icon = painterResource(id = R.drawable.shift)
+ )
+
+ NavigationItem(
+ title = "Typing Preferences",
+ style = NavigationItemStyle.HomeSecondary,
+ navigate = { navController.navigate("typing") },
+ icon = painterResource(id = R.drawable.delete)
+ )
+
+ NavigationItem(
+ title = "Voice Input",
+ style = NavigationItemStyle.HomeSecondary,
+ navigate = { navController.navigate("voiceInput") },
+ icon = painterResource(id = R.drawable.mic_fill)
+ )
+
+ NavigationItem(
+ title = "Theme",
+ style = NavigationItemStyle.HomeTertiary,
+ navigate = { /* TODO */ },
+ icon = painterResource(id = R.drawable.eye)
+ )
+
+ NavigationItem(
+ title = "Advanced",
+ style = NavigationItemStyle.Misc,
+ navigate = { /* TODO */ },
+ icon = painterResource(id = R.drawable.delete)
+ )
+
+ }
+}
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/pages/PredictiveText.kt b/java/src/org/futo/inputmethod/latin/uix/settings/pages/PredictiveText.kt
new file mode 100644
index 000000000..767721a46
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/pages/PredictiveText.kt
@@ -0,0 +1,83 @@
+package org.futo.inputmethod.latin.uix.settings.pages
+
+import android.content.Intent
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.booleanResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import org.futo.inputmethod.dictionarypack.DictionarySettingsActivity
+import org.futo.inputmethod.latin.R
+import org.futo.inputmethod.latin.settings.Settings
+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.SettingToggleSharedPrefs
+import org.futo.inputmethod.latin.uix.settings.Tip
+
+@Preview
+@Composable
+fun PredictiveTextScreen(navController: NavHostController = rememberNavController()) {
+ val context = LocalContext.current
+ ScrollableList {
+ ScreenTitle("Predictive Text", showBack = true, navController)
+
+ Tip("Note: Transformer LM is not yet finished, the prediction algorithm is still the default AOSP Keyboard prediction algorithm")
+
+ NavigationItem(
+ title = stringResource(R.string.edit_personal_dictionary),
+ style = NavigationItemStyle.Misc,
+ navigate = {
+ val intent = Intent("android.settings.USER_DICTIONARY_SETTINGS")
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ context.startActivity(intent)
+ }
+ )
+
+ NavigationItem(
+ title = stringResource(R.string.configure_dictionaries_title),
+ style = NavigationItemStyle.Misc,
+ navigate = {
+ val intent = Intent()
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ intent.setClass(context, DictionarySettingsActivity::class.java)
+ intent.putExtra("clientId", "org.futo.inputmethod.latin")
+ context.startActivity(intent)
+ }
+ )
+
+ SettingToggleSharedPrefs(
+ title = stringResource(R.string.prefs_block_potentially_offensive_title),
+ subtitle = stringResource(R.string.prefs_block_potentially_offensive_summary),
+ key = Settings.PREF_BLOCK_POTENTIALLY_OFFENSIVE,
+ default = booleanResource(R.bool.config_block_potentially_offensive)
+ )
+ SettingToggleSharedPrefs(
+ title = stringResource(R.string.auto_correction),
+ subtitle = stringResource(R.string.auto_correction_summary),
+ key = Settings.PREF_AUTO_CORRECTION,
+ default = true
+ )
+ SettingToggleSharedPrefs(
+ title = stringResource(R.string.prefs_show_suggestions),
+ subtitle = stringResource(R.string.prefs_show_suggestions_summary),
+ key = Settings.PREF_SHOW_SUGGESTIONS,
+ default = true
+ )
+ SettingToggleSharedPrefs(
+ title = stringResource(R.string.use_personalized_dicts),
+ subtitle = stringResource(R.string.use_personalized_dicts_summary),
+ key = Settings.PREF_KEY_USE_PERSONALIZED_DICTS,
+ default = true
+ )
+ SettingToggleSharedPrefs(
+ title = stringResource(R.string.bigram_prediction),
+ subtitle = stringResource(R.string.bigram_prediction_summary),
+ key = Settings.PREF_BIGRAM_PREDICTIONS,
+ default = booleanResource(R.bool.config_default_next_word_prediction)
+ )
+ }
+}
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt
new file mode 100644
index 000000000..78d0e1b08
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt
@@ -0,0 +1,53 @@
+package org.futo.inputmethod.latin.uix.settings.pages
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.booleanResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import org.futo.inputmethod.latin.R
+import org.futo.inputmethod.latin.settings.Settings
+import org.futo.inputmethod.latin.uix.settings.ScreenTitle
+import org.futo.inputmethod.latin.uix.settings.ScrollableList
+import org.futo.inputmethod.latin.uix.settings.SettingToggleSharedPrefs
+
+@Preview
+@Composable
+fun TypingScreen(navController: NavHostController = rememberNavController()) {
+ val context = LocalContext.current
+ ScrollableList {
+ ScreenTitle("Typing Preferences", showBack = true, navController)
+
+ SettingToggleSharedPrefs(
+ title = stringResource(R.string.auto_cap),
+ subtitle = stringResource(R.string.auto_cap_summary),
+ key = Settings.PREF_AUTO_CAP,
+ default = true
+ )
+ SettingToggleSharedPrefs(
+ title = stringResource(R.string.use_double_space_period),
+ subtitle = stringResource(R.string.use_double_space_period_summary),
+ key = Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD,
+ default = true
+ )
+ SettingToggleSharedPrefs(
+ title = stringResource(R.string.vibrate_on_keypress),
+ key = Settings.PREF_VIBRATE_ON,
+ default = booleanResource(R.bool.config_default_vibration_enabled)
+ )
+ SettingToggleSharedPrefs(
+ title = stringResource(R.string.sound_on_keypress),
+ key = Settings.PREF_SOUND_ON,
+ default = booleanResource(R.bool.config_default_sound_enabled)
+ )
+ SettingToggleSharedPrefs(
+ title = stringResource(R.string.popup_on_keypress),
+ key = Settings.PREF_POPUP_ON,
+ default = booleanResource(R.bool.config_default_key_preview_popup)
+ )
+
+ // TODO: SeekBarDialogPreference pref_vibration_duration_settings etc
+ }
+}
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/pages/VoiceInput.kt b/java/src/org/futo/inputmethod/latin/uix/settings/pages/VoiceInput.kt
new file mode 100644
index 000000000..bcd0f7421
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/pages/VoiceInput.kt
@@ -0,0 +1,129 @@
+package org.futo.inputmethod.latin.uix.settings.pages
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuBox
+import androidx.compose.material3.ExposedDropdownMenuDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import org.futo.inputmethod.latin.uix.DISALLOW_SYMBOLS
+import org.futo.inputmethod.latin.uix.ENABLE_SOUND
+import org.futo.inputmethod.latin.uix.ENGLISH_MODEL_INDEX
+import org.futo.inputmethod.latin.uix.SettingsKey
+import org.futo.inputmethod.latin.uix.VERBOSE_PROGRESS
+import org.futo.inputmethod.latin.uix.settings.ScreenTitle
+import org.futo.inputmethod.latin.uix.settings.ScrollableList
+import org.futo.inputmethod.latin.uix.settings.SettingToggleDataStore
+import org.futo.inputmethod.latin.uix.settings.useDataStore
+import org.futo.voiceinput.shared.ENGLISH_MODELS
+import org.futo.voiceinput.shared.types.ModelLoader
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ModelPicker(label: String, options: List, setting: SettingsKey) {
+ val (modelIndex, setModelIndex) = useDataStore(key = setting.key, default = setting.default)
+
+ var expanded by remember { mutableStateOf(false) }
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp)
+ ) {
+ ExposedDropdownMenuBox(
+ expanded = expanded,
+ onExpandedChange = {
+ expanded = !expanded
+ },
+ modifier = Modifier.align(Alignment.Center)
+ ) {
+ TextField(
+ readOnly = true,
+ value = stringResource(options[modelIndex].name),
+ onValueChange = { },
+ label = { Text(label) },
+ trailingIcon = {
+ ExposedDropdownMenuDefaults.TrailingIcon(
+ expanded = expanded
+ )
+ },
+ colors = ExposedDropdownMenuDefaults.textFieldColors(
+ focusedLabelColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ focusedLeadingIconColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ focusedIndicatorColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ focusedTrailingIconColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ ),
+ modifier = Modifier.menuAnchor()
+ )
+ ExposedDropdownMenu(
+ expanded = expanded,
+ onDismissRequest = {
+ expanded = false
+ }
+ ) {
+ options.forEachIndexed { i, selectionOption ->
+ DropdownMenuItem(
+ text = {
+ Text(stringResource(selectionOption.name))
+ },
+ onClick = {
+ setModelIndex(i)
+ expanded = false
+ }
+ )
+ }
+ }
+ }
+ }
+}
+
+
+
+@Preview
+@Composable
+fun VoiceInputScreen(navController: NavHostController = rememberNavController()) {
+ val context = LocalContext.current
+ ScrollableList {
+ ScreenTitle("Voice Input", showBack = true, navController)
+
+ SettingToggleDataStore(
+ title = "Indication sounds",
+ subtitle = "Play sounds on start and cancel",
+ setting = ENABLE_SOUND
+ )
+
+ SettingToggleDataStore(
+ title = "Verbose progress",
+ subtitle = "Display verbose information about model inference",
+ setting = VERBOSE_PROGRESS
+ )
+
+ SettingToggleDataStore(
+ title = "Suppress symbols",
+ setting = DISALLOW_SYMBOLS
+ )
+
+ ModelPicker(
+ "English Model Option",
+ ENGLISH_MODELS,
+ ENGLISH_MODEL_INDEX
+ )
+ }
+}
\ No newline at end of file