mirror of
https://gitlab.futo.org/keyboard/latinime.git
synced 2024-09-28 14:54:30 +01:00
Move UIX management to separate class
This commit is contained in:
parent
adfab75086
commit
fd905bcabd
@ -1,6 +1,5 @@
|
||||
package org.futo.inputmethod.latin
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.os.Build
|
||||
@ -13,34 +12,16 @@ import android.view.inputmethod.InlineSuggestionsRequest
|
||||
import android.view.inputmethod.InlineSuggestionsResponse
|
||||
import android.view.inputmethod.InputMethodSubtype
|
||||
import androidx.annotation.RequiresApi
|
||||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import androidx.lifecycle.ViewModelStore
|
||||
@ -56,36 +37,24 @@ import androidx.savedstate.SavedStateRegistryOwner
|
||||
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
|
||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.futo.inputmethod.latin.common.Constants
|
||||
import org.futo.inputmethod.latin.common.Constants.CODE_DELETE
|
||||
import org.futo.inputmethod.latin.common.Constants.NOT_A_COORDINATE
|
||||
import org.futo.inputmethod.latin.uix.Action
|
||||
import org.futo.inputmethod.latin.uix.ActionBar
|
||||
import org.futo.inputmethod.latin.uix.ActionInputTransaction
|
||||
import org.futo.inputmethod.latin.uix.ActionWindow
|
||||
import org.futo.inputmethod.latin.uix.BasicThemeProvider
|
||||
import org.futo.inputmethod.latin.uix.DynamicThemeProvider
|
||||
import org.futo.inputmethod.latin.uix.DynamicThemeProviderOwner
|
||||
import org.futo.inputmethod.latin.uix.KeyboardManagerForAction
|
||||
import org.futo.inputmethod.latin.uix.PersistentActionState
|
||||
import org.futo.inputmethod.latin.uix.THEME_KEY
|
||||
import org.futo.inputmethod.latin.uix.UixManager
|
||||
import org.futo.inputmethod.latin.uix.createInlineSuggestionsRequest
|
||||
import org.futo.inputmethod.latin.uix.deferGetSetting
|
||||
import org.futo.inputmethod.latin.uix.deferSetSetting
|
||||
import org.futo.inputmethod.latin.uix.differsFrom
|
||||
import org.futo.inputmethod.latin.uix.inflateInlineSuggestion
|
||||
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
|
||||
import org.futo.inputmethod.latin.uix.theme.ThemeOption
|
||||
import org.futo.inputmethod.latin.uix.theme.ThemeOptions
|
||||
import org.futo.inputmethod.latin.uix.theme.Typography
|
||||
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
|
||||
import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme
|
||||
import org.futo.inputmethod.latin.xlm.LanguageModelFacilitator
|
||||
|
||||
class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner,
|
||||
LatinIMELegacy.SuggestionStripController, DynamicThemeProviderOwner, KeyboardManagerForAction {
|
||||
LatinIMELegacy.SuggestionStripController, DynamicThemeProviderOwner {
|
||||
|
||||
private val mSavedStateRegistryController = SavedStateRegistryController.create(this)
|
||||
|
||||
@ -103,7 +72,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
override val viewModelStore
|
||||
get() = store
|
||||
|
||||
private fun setOwners() {
|
||||
fun setOwners() {
|
||||
val decorView = window.window?.decorView
|
||||
if (decorView?.findViewTreeLifecycleOwner() == null) {
|
||||
decorView?.setViewTreeLifecycleOwner(this)
|
||||
@ -116,14 +85,14 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
}
|
||||
}
|
||||
|
||||
private var composeView: ComposeView? = null
|
||||
|
||||
private val latinIMELegacy = LatinIMELegacy(
|
||||
val latinIMELegacy = LatinIMELegacy(
|
||||
this as InputMethodService,
|
||||
this as LatinIMELegacy.SuggestionStripController
|
||||
)
|
||||
|
||||
public val languageModelFacilitator = LanguageModelFacilitator(
|
||||
val inputLogic get() = latinIMELegacy.mInputLogic
|
||||
|
||||
val languageModelFacilitator = LanguageModelFacilitator(
|
||||
this,
|
||||
latinIMELegacy.mInputLogic,
|
||||
latinIMELegacy.mDictionaryFacilitator,
|
||||
@ -132,21 +101,18 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
lifecycleScope
|
||||
)
|
||||
|
||||
val uixManager = UixManager(this)
|
||||
|
||||
private var activeThemeOption: ThemeOption? = null
|
||||
private var activeColorScheme = DarkColorScheme
|
||||
private var colorSchemeLoaderJob: Job? = null
|
||||
private var pendingRecreateKeyboard: Boolean = false
|
||||
|
||||
val themeOption get() = activeThemeOption
|
||||
val colorScheme get() = activeColorScheme
|
||||
|
||||
private var drawableProvider: DynamicThemeProvider? = null
|
||||
|
||||
private var currWindowAction: Action? = null
|
||||
private var currWindowActionWindow: ActionWindow? = null
|
||||
private var persistentStates: HashMap<Action, PersistentActionState?> = hashMapOf()
|
||||
private fun isActionWindowOpen(): Boolean {
|
||||
return currWindowActionWindow != null
|
||||
}
|
||||
|
||||
private var inlineSuggestions: List<MutableState<View?>> = listOf()
|
||||
|
||||
private var lastEditorInfo: EditorInfo? = null
|
||||
|
||||
// TODO: Calling this repeatedly as the theme changes tends to slow everything to a crawl
|
||||
@ -160,7 +126,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
drawableProvider = BasicThemeProvider(this, overrideColorScheme = colorScheme)
|
||||
|
||||
window.window?.navigationBarColor = drawableProvider!!.primaryKeyboardColor
|
||||
setContent()
|
||||
uixManager.onColorSchemeChanged()
|
||||
}
|
||||
|
||||
override fun getDrawableProvider(): DynamicThemeProvider {
|
||||
@ -195,6 +161,32 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTheme(newTheme: ThemeOption) {
|
||||
assert(newTheme.available(this))
|
||||
|
||||
if (activeThemeOption != newTheme) {
|
||||
activeThemeOption = newTheme
|
||||
updateDrawableProvider(newTheme.obtainColors(this))
|
||||
deferSetSetting(THEME_KEY, newTheme.key)
|
||||
|
||||
if(!uixManager.isActionWindowOpen) {
|
||||
recreateKeyboard()
|
||||
} else {
|
||||
pendingRecreateKeyboard = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called by UixManager when the intention is to subsequently call LegacyKeyboardView with hidden=false
|
||||
// Maybe this can be changed to LaunchedEffect
|
||||
fun onKeyboardShown() {
|
||||
if(pendingRecreateKeyboard) {
|
||||
pendingRecreateKeyboard = false
|
||||
recreateKeyboard()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
@ -243,39 +235,23 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
private var touchableHeight: Int = 0
|
||||
override fun onCreateInputView(): View {
|
||||
legacyInputView = latinIMELegacy.onCreateInputView()
|
||||
composeView = ComposeView(this).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setParentCompositionContext(null)
|
||||
|
||||
this@LatinIME.setOwners()
|
||||
}
|
||||
|
||||
setContent()
|
||||
|
||||
val composeView = uixManager.createComposeView()
|
||||
latinIMELegacy.setComposeInputView(composeView)
|
||||
|
||||
return composeView!!
|
||||
}
|
||||
|
||||
private fun onActionActivated(action: Action) {
|
||||
// Finish what we are typing so far
|
||||
latinIMELegacy.onFinishInputViewInternal(false)
|
||||
|
||||
if (action.windowImpl != null) {
|
||||
enterActionWindowView(action)
|
||||
} else if (action.simplePressImpl != null) {
|
||||
action.simplePressImpl.invoke(this, persistentStates[action])
|
||||
} else {
|
||||
throw IllegalStateException("An action must have either a window implementation or a simple press implementation")
|
||||
}
|
||||
return composeView
|
||||
}
|
||||
|
||||
private var inputViewHeight: Int = -1
|
||||
private var shouldShowSuggestionStrip: Boolean = true
|
||||
private var suggestedWords: SuggestedWords? = null
|
||||
|
||||
// Both called by UixManager
|
||||
fun updateTouchableHeight(to: Int) { touchableHeight = to }
|
||||
fun getInputViewHeight(): Int = inputViewHeight
|
||||
|
||||
// The keyboard view really doesn't like being detached, so it's always
|
||||
// shown, but resized to 0 if an action window is open
|
||||
@Composable
|
||||
private fun LegacyKeyboardView(hidden: Boolean) {
|
||||
internal fun LegacyKeyboardView(hidden: Boolean) {
|
||||
val modifier = if(hidden) {
|
||||
Modifier
|
||||
.clipToBounds()
|
||||
@ -292,127 +268,13 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainKeyboardViewWithActionBar() {
|
||||
Column {
|
||||
// Don't show suggested words when it's not meant to be shown
|
||||
val suggestedWordsOrNull = if(shouldShowSuggestionStrip) {
|
||||
suggestedWords
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
ActionBar(
|
||||
suggestedWordsOrNull,
|
||||
latinIMELegacy,
|
||||
inlineSuggestions = inlineSuggestions,
|
||||
onActionActivated = { onActionActivated(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun enterActionWindowView(action: Action) {
|
||||
assert(action.windowImpl != null)
|
||||
|
||||
latinIMELegacy.mKeyboardSwitcher.saveKeyboardState()
|
||||
|
||||
currWindowAction = action
|
||||
|
||||
if (persistentStates[action] == null) {
|
||||
persistentStates[action] = action.persistentState?.let { it(this) }
|
||||
}
|
||||
|
||||
currWindowActionWindow = action.windowImpl?.let { it(this, persistentStates[action]) }
|
||||
|
||||
setContent()
|
||||
}
|
||||
|
||||
private fun returnBackToMainKeyboardViewFromAction() {
|
||||
if(currWindowActionWindow == null) return
|
||||
|
||||
currWindowActionWindow!!.close()
|
||||
|
||||
currWindowAction = null
|
||||
currWindowActionWindow = null
|
||||
|
||||
if(hasThemeChanged) {
|
||||
hasThemeChanged = false
|
||||
recreateKeyboard()
|
||||
}
|
||||
|
||||
setContent()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActionViewWithHeader(windowImpl: ActionWindow) {
|
||||
Column {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(40.dp), color = MaterialTheme.colorScheme.background
|
||||
)
|
||||
{
|
||||
Row {
|
||||
IconButton(onClick = {
|
||||
returnBackToMainKeyboardViewFromAction()
|
||||
}) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.arrow_left_26),
|
||||
contentDescription = "Back"
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
windowImpl.windowName(),
|
||||
style = Typography.titleMedium,
|
||||
modifier = Modifier.align(CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(with(LocalDensity.current) { inputViewHeight.toDp() })
|
||||
) {
|
||||
windowImpl.WindowContents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setContent() {
|
||||
composeView?.setContent {
|
||||
UixThemeWrapper(activeColorScheme) {
|
||||
Column {
|
||||
Spacer(modifier = Modifier.weight(1.0f))
|
||||
Surface(modifier = Modifier.onSizeChanged {
|
||||
touchableHeight = it.height
|
||||
}) {
|
||||
Column {
|
||||
when {
|
||||
isActionWindowOpen() -> ActionViewWithHeader(
|
||||
currWindowActionWindow!!
|
||||
)
|
||||
|
||||
else -> MainKeyboardViewWithActionBar()
|
||||
}
|
||||
|
||||
// The keyboard view really doesn't like being detached, so it's always
|
||||
// shown, but resized to 0 if an action window is open
|
||||
LegacyKeyboardView(hidden = isActionWindowOpen())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// necessary for when KeyboardSwitcher updates the theme
|
||||
fun updateLegacyView(newView: View) {
|
||||
legacyInputView = newView
|
||||
setContent()
|
||||
|
||||
if (composeView != null) {
|
||||
latinIMELegacy.setComposeInputView(composeView)
|
||||
uixManager.setContent()
|
||||
uixManager.getComposeView()?.let {
|
||||
latinIMELegacy.setComposeInputView(it)
|
||||
}
|
||||
|
||||
latinIMELegacy.setInputView(legacyInputView)
|
||||
@ -421,8 +283,8 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
override fun setInputView(view: View?) {
|
||||
super.setInputView(view)
|
||||
|
||||
if (composeView != null) {
|
||||
latinIMELegacy.setComposeInputView(composeView)
|
||||
uixManager.getComposeView()?.let {
|
||||
latinIMELegacy.setComposeInputView(it)
|
||||
}
|
||||
|
||||
latinIMELegacy.setInputView(legacyInputView)
|
||||
@ -447,15 +309,14 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
override fun onFinishInputView(finishingInput: Boolean) {
|
||||
super.onFinishInputView(finishingInput)
|
||||
latinIMELegacy.onFinishInputView(finishingInput)
|
||||
|
||||
closeActionWindow()
|
||||
uixManager.onInputFinishing()
|
||||
}
|
||||
|
||||
override fun onFinishInput() {
|
||||
super.onFinishInput()
|
||||
latinIMELegacy.onFinishInput()
|
||||
|
||||
closeActionWindow()
|
||||
uixManager.onInputFinishing()
|
||||
languageModelFacilitator.saveHistoryLog()
|
||||
}
|
||||
|
||||
@ -475,7 +336,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
super.onWindowHidden()
|
||||
latinIMELegacy.onWindowHidden()
|
||||
|
||||
closeActionWindow()
|
||||
uixManager.onInputFinishing()
|
||||
}
|
||||
|
||||
override fun onUpdateSelection(
|
||||
@ -525,12 +386,14 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
}
|
||||
|
||||
override fun onComputeInsets(outInsets: Insets?) {
|
||||
val composeView = uixManager.getComposeView()
|
||||
|
||||
// This method may be called before {@link #setInputView(View)}.
|
||||
if (legacyInputView == null || composeView == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val inputHeight: Int = composeView!!.height
|
||||
val inputHeight: Int = composeView.height
|
||||
if (latinIMELegacy.isImeSuppressedByHardwareKeyboard && !legacyInputView!!.isShown) {
|
||||
// If there is a hardware keyboard and a visible software keyboard view has been hidden,
|
||||
// no visual element will be shown on the screen.
|
||||
@ -545,7 +408,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
|
||||
val touchLeft = 0
|
||||
val touchTop = visibleTopY
|
||||
val touchRight = composeView!!.width
|
||||
val touchRight = composeView.width
|
||||
val touchBottom = inputHeight
|
||||
|
||||
latinIMELegacy.setInsets(outInsets!!.apply {
|
||||
@ -585,135 +448,27 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
}
|
||||
|
||||
override fun updateVisibility(shouldShowSuggestionsStrip: Boolean, fullscreenMode: Boolean) {
|
||||
this.shouldShowSuggestionStrip = shouldShowSuggestionsStrip
|
||||
setContent()
|
||||
uixManager.updateVisibility(shouldShowSuggestionsStrip, fullscreenMode)
|
||||
}
|
||||
|
||||
override fun setSuggestions(suggestedWords: SuggestedWords?, rtlSubtype: Boolean) {
|
||||
this.suggestedWords = suggestedWords
|
||||
setContent()
|
||||
uixManager.setSuggestions(suggestedWords, rtlSubtype)
|
||||
}
|
||||
|
||||
override fun maybeShowImportantNoticeTitle(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun cleanUpPersistentStates() {
|
||||
println("Cleaning up persistent states")
|
||||
for((key, value) in persistentStates.entries) {
|
||||
if(currWindowAction != key) {
|
||||
lifecycleScope.launch { value?.cleanUp() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
super.onLowMemory()
|
||||
cleanUpPersistentStates()
|
||||
uixManager.cleanUpPersistentStates()
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
cleanUpPersistentStates()
|
||||
uixManager.cleanUpPersistentStates()
|
||||
}
|
||||
|
||||
override fun getContext(): Context {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun getLifecycleScope(): LifecycleCoroutineScope {
|
||||
return lifecycleScope
|
||||
}
|
||||
|
||||
override fun triggerContentUpdate() {
|
||||
setContent()
|
||||
}
|
||||
|
||||
private class LatinIMEActionInputTransaction(
|
||||
private val latinIME: LatinIME,
|
||||
shouldApplySpace: Boolean
|
||||
): ActionInputTransaction {
|
||||
private val isSpaceNecessary: Boolean
|
||||
init {
|
||||
val priorText = latinIME.latinIMELegacy.mInputLogic.mConnection.getTextBeforeCursor(1, 0)
|
||||
isSpaceNecessary = shouldApplySpace && !priorText.isNullOrEmpty() && !priorText.last().isWhitespace()
|
||||
}
|
||||
|
||||
private fun transformText(text: String): String {
|
||||
return if(isSpaceNecessary) { " $text" } else { text }
|
||||
}
|
||||
|
||||
override fun updatePartial(text: String) {
|
||||
latinIME.latinIMELegacy.mInputLogic.mConnection.setComposingText(
|
||||
transformText(text),
|
||||
1
|
||||
)
|
||||
}
|
||||
|
||||
override fun commit(text: String) {
|
||||
latinIME.latinIMELegacy.mInputLogic.mConnection.commitText(
|
||||
transformText(text),
|
||||
1
|
||||
)
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
// TODO: Do we want to leave the composing text as-is, or delete it?
|
||||
latinIME.latinIMELegacy.mInputLogic.mConnection.finishComposingText()
|
||||
}
|
||||
}
|
||||
|
||||
override fun createInputTransaction(applySpaceIfNeeded: Boolean): ActionInputTransaction {
|
||||
return LatinIMEActionInputTransaction(this, applySpaceIfNeeded)
|
||||
}
|
||||
|
||||
override fun typeText(v: String) {
|
||||
latinIMELegacy.onTextInput(v)
|
||||
}
|
||||
|
||||
override fun backspace(amount: Int) {
|
||||
latinIMELegacy.onCodeInput(CODE_DELETE, NOT_A_COORDINATE, NOT_A_COORDINATE, false)
|
||||
}
|
||||
|
||||
override fun closeActionWindow() {
|
||||
if(currWindowActionWindow == null) return
|
||||
returnBackToMainKeyboardViewFromAction()
|
||||
}
|
||||
|
||||
override fun triggerSystemVoiceInput() {
|
||||
latinIMELegacy.onCodeInput(
|
||||
Constants.CODE_SHORTCUT,
|
||||
Constants.SUGGESTION_STRIP_COORDINATE,
|
||||
Constants.SUGGESTION_STRIP_COORDINATE,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
private var hasThemeChanged: Boolean = false
|
||||
override fun updateTheme(newTheme: ThemeOption) {
|
||||
assert(newTheme.available(this))
|
||||
|
||||
if (activeThemeOption != newTheme) {
|
||||
activeThemeOption = newTheme
|
||||
updateDrawableProvider(newTheme.obtainColors(this))
|
||||
deferSetSetting(THEME_KEY, newTheme.key)
|
||||
|
||||
hasThemeChanged = true
|
||||
if(!isActionWindowOpen()) {
|
||||
recreateKeyboard()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun sendCodePointEvent(codePoint: Int) {
|
||||
latinIMELegacy.onCodeInput(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE, false)
|
||||
}
|
||||
|
||||
override fun sendKeyEvent(keyCode: Int, metaState: Int) {
|
||||
latinIMELegacy.mInputLogic.sendDownUpKeyEvent(keyCode, metaState)
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest {
|
||||
return createInlineSuggestionsRequest(this, this.activeColorScheme)
|
||||
@ -721,12 +476,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
override fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean {
|
||||
inlineSuggestions = response.inlineSuggestions.map {
|
||||
inflateInlineSuggestion(it)
|
||||
}
|
||||
setContent()
|
||||
|
||||
return true
|
||||
return uixManager.onInlineSuggestionsResponse(response)
|
||||
}
|
||||
|
||||
fun postUpdateSuggestionStrip(inputStyle: Int) {
|
||||
|
332
java/src/org/futo/inputmethod/latin/uix/UixManager.kt
Normal file
332
java/src/org/futo/inputmethod/latin/uix/UixManager.kt
Normal file
@ -0,0 +1,332 @@
|
||||
package org.futo.inputmethod.latin.uix
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InlineSuggestionsResponse
|
||||
import androidx.annotation.RequiresApi
|
||||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.futo.inputmethod.latin.LatinIME
|
||||
import org.futo.inputmethod.latin.R
|
||||
import org.futo.inputmethod.latin.SuggestedWords
|
||||
import org.futo.inputmethod.latin.common.Constants
|
||||
import org.futo.inputmethod.latin.inputlogic.InputLogic
|
||||
import org.futo.inputmethod.latin.suggestions.SuggestionStripView
|
||||
import org.futo.inputmethod.latin.uix.theme.ThemeOption
|
||||
import org.futo.inputmethod.latin.uix.theme.Typography
|
||||
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
|
||||
|
||||
private class LatinIMEActionInputTransaction(
|
||||
private val inputLogic: InputLogic,
|
||||
shouldApplySpace: Boolean
|
||||
): ActionInputTransaction {
|
||||
private val isSpaceNecessary: Boolean
|
||||
init {
|
||||
val priorText = inputLogic.mConnection.getTextBeforeCursor(1, 0)
|
||||
isSpaceNecessary = shouldApplySpace && !priorText.isNullOrEmpty() && !priorText.last().isWhitespace()
|
||||
}
|
||||
|
||||
private fun transformText(text: String): String {
|
||||
return if(isSpaceNecessary) { " $text" } else { text }
|
||||
}
|
||||
|
||||
override fun updatePartial(text: String) {
|
||||
inputLogic.mConnection.setComposingText(
|
||||
transformText(text),
|
||||
1
|
||||
)
|
||||
}
|
||||
|
||||
override fun commit(text: String) {
|
||||
inputLogic.mConnection.commitText(
|
||||
transformText(text),
|
||||
1
|
||||
)
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
inputLogic.mConnection.finishComposingText()
|
||||
}
|
||||
}
|
||||
|
||||
class UixActionKeyboardManager(val uixManager: UixManager, val latinIME: LatinIME) : KeyboardManagerForAction {
|
||||
override fun getContext(): Context {
|
||||
return latinIME
|
||||
}
|
||||
|
||||
override fun getLifecycleScope(): LifecycleCoroutineScope {
|
||||
return latinIME.lifecycleScope
|
||||
}
|
||||
|
||||
override fun triggerContentUpdate() {
|
||||
uixManager.setContent()
|
||||
}
|
||||
|
||||
override fun createInputTransaction(applySpaceIfNeeded: Boolean): ActionInputTransaction {
|
||||
return LatinIMEActionInputTransaction(latinIME.inputLogic, applySpaceIfNeeded)
|
||||
}
|
||||
|
||||
override fun typeText(v: String) {
|
||||
latinIME.latinIMELegacy.onTextInput(v)
|
||||
}
|
||||
|
||||
override fun backspace(amount: Int) {
|
||||
latinIME.latinIMELegacy.onCodeInput(
|
||||
Constants.CODE_DELETE,
|
||||
Constants.NOT_A_COORDINATE,
|
||||
Constants.NOT_A_COORDINATE, false)
|
||||
}
|
||||
|
||||
override fun closeActionWindow() {
|
||||
if(uixManager.currWindowActionWindow == null) return
|
||||
uixManager.returnBackToMainKeyboardViewFromAction()
|
||||
}
|
||||
|
||||
override fun triggerSystemVoiceInput() {
|
||||
latinIME.latinIMELegacy.onCodeInput(
|
||||
Constants.CODE_SHORTCUT,
|
||||
Constants.SUGGESTION_STRIP_COORDINATE,
|
||||
Constants.SUGGESTION_STRIP_COORDINATE,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
override fun updateTheme(newTheme: ThemeOption) {
|
||||
latinIME.updateTheme(newTheme)
|
||||
}
|
||||
|
||||
override fun sendCodePointEvent(codePoint: Int) {
|
||||
latinIME.latinIMELegacy.onCodeInput(codePoint,
|
||||
Constants.NOT_A_COORDINATE,
|
||||
Constants.NOT_A_COORDINATE, false)
|
||||
}
|
||||
|
||||
override fun sendKeyEvent(keyCode: Int, metaState: Int) {
|
||||
latinIME.inputLogic.sendDownUpKeyEvent(keyCode, metaState)
|
||||
}
|
||||
}
|
||||
|
||||
class UixManager(private val latinIME: LatinIME) {
|
||||
private var shouldShowSuggestionStrip: Boolean = true
|
||||
private var suggestedWords: SuggestedWords? = null
|
||||
|
||||
private var composeView: ComposeView? = null
|
||||
|
||||
private var currWindowAction: Action? = null
|
||||
private var persistentStates: HashMap<Action, PersistentActionState?> = hashMapOf()
|
||||
|
||||
private var inlineSuggestions: List<MutableState<View?>> = listOf()
|
||||
private val keyboardManagerForAction = UixActionKeyboardManager(this, latinIME)
|
||||
|
||||
var currWindowActionWindow: ActionWindow? = null
|
||||
val isActionWindowOpen get() = currWindowActionWindow != null
|
||||
|
||||
private fun onActionActivated(action: Action) {
|
||||
latinIME.inputLogic.finishInput()
|
||||
|
||||
if (action.windowImpl != null) {
|
||||
enterActionWindowView(action)
|
||||
} else if (action.simplePressImpl != null) {
|
||||
action.simplePressImpl.invoke(keyboardManagerForAction, persistentStates[action])
|
||||
} else {
|
||||
throw IllegalStateException("An action must have either a window implementation or a simple press implementation")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainKeyboardViewWithActionBar() {
|
||||
Column {
|
||||
// Don't show suggested words when it's not meant to be shown
|
||||
val suggestedWordsOrNull = if(shouldShowSuggestionStrip) {
|
||||
suggestedWords
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
ActionBar(
|
||||
suggestedWordsOrNull,
|
||||
latinIME.latinIMELegacy as SuggestionStripView.Listener,
|
||||
inlineSuggestions = inlineSuggestions,
|
||||
onActionActivated = { onActionActivated(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun enterActionWindowView(action: Action) {
|
||||
assert(action.windowImpl != null)
|
||||
|
||||
//latinIMELegacy.mKeyboardSwitcher.saveKeyboardState()
|
||||
|
||||
currWindowAction = action
|
||||
|
||||
if (persistentStates[action] == null) {
|
||||
persistentStates[action] = action.persistentState?.let { it(keyboardManagerForAction) }
|
||||
}
|
||||
|
||||
currWindowActionWindow = action.windowImpl?.let { it(keyboardManagerForAction, persistentStates[action]) }
|
||||
|
||||
setContent()
|
||||
}
|
||||
|
||||
fun returnBackToMainKeyboardViewFromAction() {
|
||||
if(currWindowActionWindow == null) return
|
||||
|
||||
currWindowActionWindow!!.close()
|
||||
|
||||
currWindowAction = null
|
||||
currWindowActionWindow = null
|
||||
|
||||
latinIME.onKeyboardShown()
|
||||
|
||||
setContent()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActionViewWithHeader(windowImpl: ActionWindow) {
|
||||
Column {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(40.dp), color = MaterialTheme.colorScheme.background
|
||||
)
|
||||
{
|
||||
Row {
|
||||
IconButton(onClick = {
|
||||
returnBackToMainKeyboardViewFromAction()
|
||||
}) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.arrow_left_26),
|
||||
contentDescription = "Back"
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
windowImpl.windowName(),
|
||||
style = Typography.titleMedium,
|
||||
modifier = Modifier.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(with(LocalDensity.current) { latinIME.getInputViewHeight().toDp() })
|
||||
) {
|
||||
windowImpl.WindowContents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setContent() {
|
||||
composeView?.setContent {
|
||||
UixThemeWrapper(latinIME.colorScheme) {
|
||||
Column {
|
||||
Spacer(modifier = Modifier.weight(1.0f))
|
||||
Surface(modifier = Modifier.onSizeChanged {
|
||||
latinIME.updateTouchableHeight(it.height)
|
||||
}) {
|
||||
Column {
|
||||
when {
|
||||
isActionWindowOpen -> ActionViewWithHeader(
|
||||
currWindowActionWindow!!
|
||||
)
|
||||
|
||||
else -> MainKeyboardViewWithActionBar()
|
||||
}
|
||||
|
||||
latinIME.LegacyKeyboardView(hidden = isActionWindowOpen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createComposeView(): View {
|
||||
if(composeView != null) {
|
||||
composeView = null
|
||||
//throw IllegalStateException("Attempted to create compose view, when one is already created!")
|
||||
}
|
||||
|
||||
composeView = ComposeView(latinIME).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setParentCompositionContext(null)
|
||||
|
||||
latinIME.setOwners()
|
||||
}
|
||||
|
||||
setContent()
|
||||
|
||||
return composeView!!
|
||||
}
|
||||
|
||||
fun getComposeView(): View? {
|
||||
return composeView
|
||||
}
|
||||
|
||||
fun onColorSchemeChanged() {
|
||||
setContent()
|
||||
}
|
||||
|
||||
fun onInputFinishing() {
|
||||
closeActionWindow()
|
||||
}
|
||||
|
||||
fun cleanUpPersistentStates() {
|
||||
println("Cleaning up persistent states")
|
||||
for((key, value) in persistentStates.entries) {
|
||||
if(currWindowAction != key) {
|
||||
latinIME.lifecycleScope.launch { value?.cleanUp() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun closeActionWindow() {
|
||||
if(currWindowActionWindow == null) return
|
||||
returnBackToMainKeyboardViewFromAction()
|
||||
}
|
||||
|
||||
|
||||
fun updateVisibility(shouldShowSuggestionsStrip: Boolean, fullscreenMode: Boolean) {
|
||||
this.shouldShowSuggestionStrip = shouldShowSuggestionsStrip
|
||||
setContent()
|
||||
}
|
||||
|
||||
fun setSuggestions(suggestedWords: SuggestedWords?, rtlSubtype: Boolean) {
|
||||
this.suggestedWords = suggestedWords
|
||||
setContent()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean {
|
||||
inlineSuggestions = response.inlineSuggestions.map {
|
||||
latinIME.inflateInlineSuggestion(it)
|
||||
}
|
||||
setContent()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user