mirror of
https://github.com/fcitx5-android/fcitx5-android.git
synced 2026-08-02 04:44:35 +08:00
Refactor voice input detection and preference structure
This commit is contained in:
parent
4a4ae575ed
commit
6636e322bd
@ -4,14 +4,11 @@
|
||||
*/
|
||||
package org.fcitx.fcitx5.android.data.prefs
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import androidx.annotation.Keep
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.fcitx.fcitx5.android.R
|
||||
import org.fcitx.fcitx5.android.data.InputFeedbacks.InputFeedbackMode
|
||||
@ -25,7 +22,6 @@ import org.fcitx.fcitx5.android.input.keyboard.SwipeSymbolDirection
|
||||
import org.fcitx.fcitx5.android.input.picker.PickerWindow
|
||||
import org.fcitx.fcitx5.android.input.popup.EmojiModifier
|
||||
import org.fcitx.fcitx5.android.utils.DeviceUtil
|
||||
import org.fcitx.fcitx5.android.utils.InputMethodUtil
|
||||
import org.fcitx.fcitx5.android.utils.appContext
|
||||
import org.fcitx.fcitx5.android.utils.vibrator
|
||||
|
||||
@ -144,50 +140,12 @@ class AppPrefs(private val sharedPreferences: SharedPreferences) {
|
||||
"keep_keyboard_letters_uppercase",
|
||||
false
|
||||
)
|
||||
|
||||
val showVoiceInputButton =
|
||||
switch(R.string.show_voice_input_button, "show_voice_input_button", false)
|
||||
|
||||
val preferredVoiceInput: ManagedPreference.PString
|
||||
|
||||
init {
|
||||
fun voiceInputPreference(
|
||||
@StringRes
|
||||
title: Int,
|
||||
key: String,
|
||||
defaultValue: String,
|
||||
enableUiOn: (() -> Boolean)? = null
|
||||
): ManagedPreference.PString {
|
||||
val pref = ManagedPreference.PString(sharedPreferences, key, defaultValue)
|
||||
val ui = object : ManagedPreferenceUi<ListPreference>(key, enableUiOn) {
|
||||
override fun createUi(context: Context): ListPreference {
|
||||
val ui = ListPreference(context)
|
||||
ui.key = key
|
||||
val voiceInputMethods = InputMethodUtil.voiceInputMethods()
|
||||
val values = voiceInputMethods.map { it.first.packageName }
|
||||
val labels = voiceInputMethods.map { it.first.loadLabel(context.packageManager) }
|
||||
return ui.apply {
|
||||
isIconSpaceReserved = false
|
||||
isSingleLineTitle = false
|
||||
entryValues = values.toTypedArray()
|
||||
summaryProvider = ListPreference.SimpleSummaryProvider.getInstance()
|
||||
setDefaultValue(defaultValue)
|
||||
setTitle(title)
|
||||
entries = labels.toTypedArray()
|
||||
setDialogTitle(title)
|
||||
}
|
||||
}
|
||||
}
|
||||
pref.register()
|
||||
ui.registerUi()
|
||||
return pref
|
||||
}
|
||||
|
||||
preferredVoiceInput = voiceInputPreference(
|
||||
R.string.preferred_voice_input,
|
||||
"preferred_voice_input",
|
||||
""
|
||||
) { showVoiceInputButton.getValue() }
|
||||
}
|
||||
val preferredVoiceInput = voiceInputPreference(
|
||||
R.string.preferred_voice_input, "preferred_voice_input", ""
|
||||
) { showVoiceInputButton.getValue() }
|
||||
|
||||
val expandKeypressArea =
|
||||
switch(R.string.expand_keypress_area, "expand_keypress_area", false)
|
||||
|
||||
@ -64,6 +64,20 @@ abstract class ManagedPreferenceCategory(
|
||||
return list(title, key, defaultValue, codec, entryValues, entryLabels, enableUiOn)
|
||||
}
|
||||
|
||||
protected fun voiceInputPreference(
|
||||
@StringRes
|
||||
title: Int,
|
||||
key: String,
|
||||
defaultValue: String,
|
||||
enableUiOn: (() -> Boolean)? = null
|
||||
): ManagedPreference.PString {
|
||||
val pref = ManagedPreference.PString(sharedPreferences, key, defaultValue)
|
||||
val ui = ManagedPreferenceUi.VoiceInputList(title, key, defaultValue, enableUiOn)
|
||||
pref.register()
|
||||
ui.registerUi()
|
||||
return pref
|
||||
}
|
||||
|
||||
protected fun int(
|
||||
@StringRes
|
||||
title: Int,
|
||||
|
||||
@ -9,10 +9,13 @@ import androidx.annotation.StringRes
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import org.fcitx.fcitx5.android.R
|
||||
import org.fcitx.fcitx5.android.ui.main.modified.MySwitchPreference
|
||||
import org.fcitx.fcitx5.android.ui.main.settings.DialogSeekBarPreference
|
||||
import org.fcitx.fcitx5.android.ui.main.settings.EditTextIntPreference
|
||||
import org.fcitx.fcitx5.android.ui.main.settings.TwinSeekBarPreference
|
||||
import org.fcitx.fcitx5.android.utils.InputMethodUtil
|
||||
import org.fcitx.fcitx5.android.utils.includes
|
||||
|
||||
abstract class ManagedPreferenceUi<T : Preference>(
|
||||
val key: String,
|
||||
@ -67,6 +70,38 @@ abstract class ManagedPreferenceUi<T : Preference>(
|
||||
}
|
||||
}
|
||||
|
||||
class VoiceInputList(
|
||||
@StringRes
|
||||
val title: Int,
|
||||
key: String,
|
||||
val defaultValue: String,
|
||||
enableUiOn: (() -> Boolean)? = null
|
||||
) : ManagedPreferenceUi<ListPreference>(key, enableUiOn) {
|
||||
override fun createUi(context: Context) = ListPreference(context).apply {
|
||||
key = this@VoiceInputList.key
|
||||
isIconSpaceReserved = false
|
||||
isSingleLineTitle = false
|
||||
summaryProvider = ListPreference.SimpleSummaryProvider.getInstance()
|
||||
setDefaultValue(defaultValue)
|
||||
setTitle(this@VoiceInputList.title)
|
||||
setDialogTitle(this@VoiceInputList.title)
|
||||
val voiceInputMethods = InputMethodUtil.listVoiceInputMethods()
|
||||
entryValues = arrayOf("", *voiceInputMethods.map { it.first.id }.toTypedArray())
|
||||
entries = arrayOf(
|
||||
context.getString(R.string.system_default),
|
||||
*voiceInputMethods.map { it.first.loadLabel(context.packageManager) }.toTypedArray()
|
||||
)
|
||||
// shows "(Not Available)" if selected id is not present
|
||||
summaryProvider = Preference.SummaryProvider<ListPreference> { preference ->
|
||||
if (preference.entryValues.includes(preference.value)) {
|
||||
preference.entry
|
||||
} else {
|
||||
context.getString(R.string._not_available_)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EditTextInt(
|
||||
@StringRes
|
||||
val title: Int,
|
||||
|
||||
@ -373,15 +373,7 @@ class KawaiiBarComponent : UniqueViewComponent<KawaiiBarComponent, FrameLayout>(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
idleUi.inlineSuggestionsBar.clear()
|
||||
}
|
||||
voiceInputSubtype = if (preferredVoiceInput.isNotEmpty()) {
|
||||
InputMethodUtil.voiceInputMethods().find {
|
||||
it.first.packageName == preferredVoiceInput
|
||||
}?.let { (info, subType) ->
|
||||
info.id to subType
|
||||
} ?: InputMethodUtil.firstVoiceInput()
|
||||
} else {
|
||||
InputMethodUtil.firstVoiceInput()
|
||||
}
|
||||
voiceInputSubtype = InputMethodUtil.findVoiceSubtype(preferredVoiceInput)
|
||||
val shouldShowVoiceInput =
|
||||
showVoiceInputButton && voiceInputSubtype != null && !capFlags.has(CapabilityFlag.Password)
|
||||
idleUi.setHideKeyboardIsVoiceInput(
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
* SPDX-FileCopyrightText: Copyright 2026 Fcitx5 for Android Contributors
|
||||
*/
|
||||
|
||||
package org.fcitx.fcitx5.android.utils
|
||||
|
||||
import android.view.inputmethod.InputMethodInfo
|
||||
import android.view.inputmethod.InputMethodSubtype
|
||||
|
||||
fun InputMethodInfo.firstVoiceSubtype() : InputMethodSubtype? {
|
||||
for (index in 0 until subtypeCount) {
|
||||
val subtype = getSubtypeAt(index)
|
||||
if (subtype.mode.lowercase() == "voice") {
|
||||
return subtype
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
* SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors
|
||||
* SPDX-FileCopyrightText: Copyright 2021-2026 Fcitx5 for Android Contributors
|
||||
*/
|
||||
package org.fcitx.fcitx5.android.utils
|
||||
|
||||
@ -46,23 +46,36 @@ object InputMethodUtil {
|
||||
|
||||
fun showPicker() = appContext.inputMethodManager.showInputMethodPicker()
|
||||
|
||||
fun voiceInputMethods(): List<Pair<InputMethodInfo, InputMethodSubtype>> =
|
||||
appContext.inputMethodManager
|
||||
.enabledInputMethodList
|
||||
fun listVoiceInputMethods(): List<Pair<InputMethodInfo, InputMethodSubtype>> {
|
||||
return appContext.inputMethodManager.enabledInputMethodList
|
||||
.mapNotNull { info ->
|
||||
for (i in 0 until info.subtypeCount) {
|
||||
val subType = info.getSubtypeAt(i)
|
||||
if (subType.mode.lowercase() == "voice") {
|
||||
return@mapNotNull info to subType
|
||||
}
|
||||
}
|
||||
return@mapNotNull null
|
||||
info.firstVoiceSubtype()?.let { info to it }
|
||||
}
|
||||
}
|
||||
|
||||
fun firstVoiceInput(): Pair<String, InputMethodSubtype>? =
|
||||
voiceInputMethods().firstNotNullOfOrNull { (info, subType) ->
|
||||
info.id to subType
|
||||
/**
|
||||
* Find input method with `"voice"` subtype, preferring one with [id]
|
||||
*/
|
||||
fun findVoiceSubtype(id: String): Pair<String, InputMethodSubtype>? {
|
||||
val inputMethods = appContext.inputMethodManager.enabledInputMethodList
|
||||
if (inputMethods.isEmpty()) return null
|
||||
var firstId: String? = null
|
||||
var firstSubtype: InputMethodSubtype? = null
|
||||
inputMethods.forEach {
|
||||
val voiceSubtype = it.firstVoiceSubtype() ?: return@forEach
|
||||
if (it.id == id) {
|
||||
return id to voiceSubtype
|
||||
}
|
||||
if (firstId == null) {
|
||||
firstId = it.id
|
||||
firstSubtype = voiceSubtype
|
||||
}
|
||||
}
|
||||
if (firstId != null && firstSubtype != null) {
|
||||
return firstId to firstSubtype
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun switchInputMethod(
|
||||
service: FcitxInputMethodService,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user