Move UIX management to separate class

This commit is contained in:
Aleksandras Kostarevas 2024-01-09 14:26:54 +02:00
parent adfab75086
commit fd905bcabd
2 changed files with 398 additions and 316 deletions

View File

@ -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) {

View 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
}
}