mirror of
https://gitlab.futo.org/keyboard/latinime.git
synced 2024-09-28 14:54:30 +01:00
Creatre initial updated settings menu
This commit is contained in:
parent
7f656bb622
commit
263165b596
@ -87,12 +87,12 @@
|
||||
</service>
|
||||
|
||||
<!-- Activities -->
|
||||
<activity android:name=".setup.SetupActivity"
|
||||
android:theme="@style/platformActivityTheme"
|
||||
<activity android:name=".uix.settings.SettingsActivity"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
|
||||
android:label="@string/english_ime_name"
|
||||
android:icon="@drawable/ic_launcher_keyboard"
|
||||
android:launchMode="singleTask"
|
||||
android:noHistory="true"
|
||||
android:noHistory="false"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
@ -106,25 +106,6 @@
|
||||
android:taskAffinity="">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".setup.SetupWizardActivity"
|
||||
android:theme="@style/platformActivityTheme"
|
||||
android:label="@string/english_ime_name"
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".settings.SettingsActivity"
|
||||
android:theme="@style/platformSettingsTheme"
|
||||
android:label="@string/english_ime_settings"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".spellcheck.SpellCheckerSettingsActivity"
|
||||
android:theme="@style/platformSettingsTheme"
|
||||
android:label="@string/android_spell_checker_settings"
|
||||
|
11
java/res/drawable/futo_logo.xml
Normal file
11
java/res/drawable/futo_logo.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="92dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="92"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M91.636,12C91.636,18.627 86.267,24 79.644,24C73.02,24 67.651,18.627 67.651,12C67.651,5.373 73.02,0 79.644,0C86.267,0 91.636,5.373 91.636,12ZM76.15,14.422C74.92,13.191 74.305,12.575 74.305,11.811C74.305,11.046 74.92,10.431 76.15,9.2L77.153,8.197C78.383,6.966 78.998,6.351 79.762,6.351C80.526,6.351 81.141,6.966 82.371,8.197L83.374,9.2C84.604,10.431 85.219,11.046 85.219,11.811C85.219,12.575 84.604,13.191 83.374,14.422L82.371,15.425C81.141,16.655 80.526,17.271 79.762,17.271C78.998,17.271 78.383,16.655 77.153,15.425L76.15,14.422ZM16.913,7.077C17.252,7.077 17.528,6.801 17.528,6.462V1.846C17.528,1.506 17.252,1.231 16.913,1.231H0.615C0.275,1.231 0,1.506 0,1.846V22.154C0,22.494 0.275,22.769 0.615,22.769H6.15C6.49,22.769 6.765,22.494 6.765,22.154V16.492C6.765,16.152 7.04,15.877 7.38,15.877H14.822C15.161,15.877 15.437,15.601 15.437,15.262V10.646C15.437,10.306 15.161,10.031 14.822,10.031H7.38C7.04,10.031 6.765,9.755 6.765,9.415V7.692C6.765,7.352 7.04,7.077 7.38,7.077H16.913ZM31.209,23.139H31.302C37.882,23.139 41.91,19.631 41.91,12.954V1.846C41.91,1.506 41.635,1.231 41.295,1.231H35.76C35.421,1.231 35.145,1.506 35.145,1.846V12.339C35.145,14.615 34.161,16.615 31.302,16.615H31.209C28.38,16.615 27.365,14.615 27.365,12.339V1.846C27.365,1.506 27.09,1.231 26.75,1.231H21.215C20.876,1.231 20.6,1.506 20.6,1.846V12.954C20.6,19.631 24.629,23.139 31.209,23.139ZM44.985,1.846C44.985,1.506 45.26,1.231 45.599,1.231H65.464C65.804,1.231 66.079,1.506 66.079,1.846V6.554C66.079,6.894 65.804,7.169 65.464,7.169H59.529C59.19,7.169 58.915,7.445 58.915,7.785V22.154C58.915,22.494 58.639,22.769 58.299,22.769H52.764C52.425,22.769 52.149,22.494 52.149,22.154V7.785C52.149,7.445 51.874,7.169 51.534,7.169H45.599C45.26,7.169 44.985,6.894 44.985,6.554V1.846Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
@ -113,7 +113,7 @@
|
||||
<!-- If IME doesn't have an applicable subtype, the first subtype will be used as a default
|
||||
subtype.-->
|
||||
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:settingsActivity="org.futo.inputmethod.latin.settings.SettingsActivity"
|
||||
android:settingsActivity="org.futo.inputmethod.latin.uix.settings.SettingsActivity"
|
||||
android:isDefault="@bool/im_is_default"
|
||||
android:supportsSwitchingToNextInputMethod="true"
|
||||
android:supportsInlineSuggestions="true">
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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<Preferences> by preferencesDataStore(name = "settings")
|
||||
|
||||
@ -28,6 +28,10 @@ suspend fun <T> Context.getSetting(key: Preferences.Key<T>, default: T): T {
|
||||
return valueFlow.first()
|
||||
}
|
||||
|
||||
fun <T> Context.getSettingFlow(key: Preferences.Key<T>, default: T): Flow<T> {
|
||||
return dataStore.data.map { preferences -> preferences[key] ?: default }.take(1)
|
||||
}
|
||||
|
||||
suspend fun <T> Context.setSetting(key: Preferences.Key<T>, value: T) {
|
||||
this.dataStore.edit { preferences ->
|
||||
preferences[key] = value
|
||||
@ -75,16 +79,27 @@ data class SettingsKey<T>(
|
||||
)
|
||||
|
||||
suspend fun <T> Context.getSetting(key: SettingsKey<T>): T {
|
||||
val valueFlow: Flow<T> =
|
||||
this.dataStore.data.map { preferences -> preferences[key.key] ?: key.default }.take(1)
|
||||
return getSetting(key.key, key.default)
|
||||
}
|
||||
|
||||
return valueFlow.first()
|
||||
fun <T> Context.getSettingFlow(key: SettingsKey<T>): Flow<T> {
|
||||
return getSettingFlow(key.key, key.default)
|
||||
}
|
||||
|
||||
suspend fun <T> Context.setSetting(key: SettingsKey<T>, value: T) {
|
||||
this.dataStore.edit { preferences ->
|
||||
preferences[key.key] = value
|
||||
}
|
||||
return setSetting(key.key, value)
|
||||
}
|
||||
|
||||
val THEME_KEY = stringPreferencesKey("activeThemeOption")
|
||||
fun <T> LifecycleOwner.deferGetSetting(key: SettingsKey<T>, onObtained: (T) -> Unit): Job {
|
||||
return deferGetSetting(key.key, key.default, onObtained)
|
||||
}
|
||||
|
||||
fun <T> LifecycleOwner.deferSetSetting(key: SettingsKey<T>, value: T): Job {
|
||||
return deferSetSetting(key.key, value)
|
||||
}
|
||||
|
||||
|
||||
val THEME_KEY = SettingsKey(
|
||||
key = stringPreferencesKey("activeThemeOption"),
|
||||
default = DynamicSystemTheme.key
|
||||
)
|
@ -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<RecognizerView?> = mutableStateOf(null)
|
||||
private var modelException: MutableState<ModelDoesNotExistException?> = 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()
|
||||
}
|
||||
}
|
||||
|
||||
|
295
java/src/org/futo/inputmethod/latin/uix/settings/Components.kt
Normal file
295
java/src/org/futo/inputmethod/latin/uix/settings/Components.kt
Normal file
@ -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<Boolean>,
|
||||
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<Boolean>,
|
||||
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 -> {}
|
||||
}
|
||||
}
|
||||
}
|
88
java/src/org/futo/inputmethod/latin/uix/settings/Hooks.kt
Normal file
88
java/src/org/futo/inputmethod/latin/uix/settings/Hooks.kt
Normal file
@ -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<T>(val value: T, val setValue: (T) -> Job)
|
||||
@Composable
|
||||
fun <T> useDataStore(key: Preferences.Key<T>, default: T): DataStoreItem<T> {
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val enableSoundFlow: Flow<T> = 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<Boolean> {
|
||||
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)
|
||||
}
|
@ -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<ThemeOption?> = 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()
|
||||
}
|
||||
}
|
@ -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) }
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
147
java/src/org/futo/inputmethod/latin/uix/settings/Setup.kt
Normal file
147
java/src/org/futo/inputmethod/latin/uix/settings/Setup.kt
Normal file
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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<ModelLoader>, setting: SettingsKey<Int>) {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user