From b75933b32161b9e5f4e31dbec8b3eda781a34636 Mon Sep 17 00:00:00 2001 From: Aleksandras Kostarevas Date: Sun, 7 Jan 2024 14:26:09 +0200 Subject: [PATCH 01/10] Update theming for more keys popup --- java/res/values/attrs.xml | 1 + java/src/org/futo/inputmethod/keyboard/Key.java | 4 ++++ .../org/futo/inputmethod/keyboard/KeyboardView.java | 5 +++++ .../keyboard/internal/KeyDrawParams.java | 3 +++ .../keyboard/internal/KeyVisualAttributes.java | 5 ++++- .../inputmethod/latin/uix/BasicThemeProvider.kt | 13 ++++++++++--- .../inputmethod/latin/uix/DynamicThemeProvider.kt | 1 + 7 files changed, 28 insertions(+), 4 deletions(-) diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml index 38bb14f92..68c620e88 100644 --- a/java/res/values/attrs.xml +++ b/java/res/values/attrs.xml @@ -421,6 +421,7 @@ + diff --git a/java/src/org/futo/inputmethod/keyboard/Key.java b/java/src/org/futo/inputmethod/keyboard/Key.java index 683e60245..9d19098b3 100644 --- a/java/src/org/futo/inputmethod/keyboard/Key.java +++ b/java/src/org/futo/inputmethod/keyboard/Key.java @@ -665,6 +665,10 @@ public class Key implements Comparable { if ((mLabelFlags & LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR) != 0) { return params.mFunctionalTextColor; } + if (mPressed) { + return params.mPressedTextColor; + } + return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor; } diff --git a/java/src/org/futo/inputmethod/keyboard/KeyboardView.java b/java/src/org/futo/inputmethod/keyboard/KeyboardView.java index e95878ec5..8418830e6 100644 --- a/java/src/org/futo/inputmethod/keyboard/KeyboardView.java +++ b/java/src/org/futo/inputmethod/keyboard/KeyboardView.java @@ -176,6 +176,11 @@ public class KeyboardView extends View { R.styleable.Keyboard_Key, defStyle, R.style.KeyboardView); mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0); mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr, mDrawableProvider); + + if(isMoreKeys && mKeyVisualAttributes != null) { + mKeyVisualAttributes.mTextColor = mDrawableProvider.getMoreKeysTextColor(); + } + keyAttr.recycle(); mPaint.setAntiAlias(true); diff --git a/java/src/org/futo/inputmethod/keyboard/internal/KeyDrawParams.java b/java/src/org/futo/inputmethod/keyboard/internal/KeyDrawParams.java index b3a913baa..8447aaae4 100644 --- a/java/src/org/futo/inputmethod/keyboard/internal/KeyDrawParams.java +++ b/java/src/org/futo/inputmethod/keyboard/internal/KeyDrawParams.java @@ -37,6 +37,7 @@ public final class KeyDrawParams { public int mTextColor; public int mTextInactivatedColor; + public int mPressedTextColor; public int mTextShadowColor; public int mFunctionalTextColor; public int mHintLetterColor; @@ -66,6 +67,7 @@ public final class KeyDrawParams { mTextColor = copyFrom.mTextColor; mTextInactivatedColor = copyFrom.mTextInactivatedColor; + mPressedTextColor = copyFrom.mPressedTextColor; mTextShadowColor = copyFrom.mTextShadowColor; mFunctionalTextColor = copyFrom.mFunctionalTextColor; mHintLetterColor = copyFrom.mHintLetterColor; @@ -103,6 +105,7 @@ public final class KeyDrawParams { mTextColor = selectColor(attr.mTextColor, mTextColor); mTextInactivatedColor = selectColor(attr.mTextInactivatedColor, mTextInactivatedColor); + mPressedTextColor = selectColor(attr.mPressedTextColor, mPressedTextColor); mTextShadowColor = selectColor(attr.mTextShadowColor, mTextShadowColor); mFunctionalTextColor = selectColor(attr.mFunctionalTextColor, mFunctionalTextColor); mHintLetterColor = selectColor(attr.mHintLetterColor, mHintLetterColor); diff --git a/java/src/org/futo/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/org/futo/inputmethod/keyboard/internal/KeyVisualAttributes.java index dd5e8704c..5aaf75aad 100644 --- a/java/src/org/futo/inputmethod/keyboard/internal/KeyVisualAttributes.java +++ b/java/src/org/futo/inputmethod/keyboard/internal/KeyVisualAttributes.java @@ -41,8 +41,9 @@ public final class KeyVisualAttributes { public final float mHintLabelRatio; public final float mPreviewTextRatio; - public final int mTextColor; + public int mTextColor; public final int mTextInactivatedColor; + public final int mPressedTextColor; public final int mTextShadowColor; public final int mFunctionalTextColor; public final int mHintLetterColor; @@ -130,6 +131,8 @@ public final class KeyVisualAttributes { R.styleable.Keyboard_Key_keyTextColor, 0, keyAttr, provider); mTextInactivatedColor = DynamicThemeProvider.Companion.getColorOrDefault( R.styleable.Keyboard_Key_keyTextInactivatedColor, 0, keyAttr, provider); + mPressedTextColor = DynamicThemeProvider.Companion.getColorOrDefault( + R.styleable.Keyboard_Key_keyPressedTextColor, 0, keyAttr, provider); mTextShadowColor = DynamicThemeProvider.Companion.getColorOrDefault( R.styleable.Keyboard_Key_keyTextShadowColor, 0, keyAttr, provider); mFunctionalTextColor = DynamicThemeProvider.Companion.getColorOrDefault( diff --git a/java/src/org/futo/inputmethod/latin/uix/BasicThemeProvider.kt b/java/src/org/futo/inputmethod/latin/uix/BasicThemeProvider.kt index cc18d61d3..2d4359cde 100644 --- a/java/src/org/futo/inputmethod/latin/uix/BasicThemeProvider.kt +++ b/java/src/org/futo/inputmethod/latin/uix/BasicThemeProvider.kt @@ -31,6 +31,7 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch override val keyFeedback: Drawable + override val moreKeysTextColor: Int override val moreKeysKeyboardBackground: Drawable override val popupKey: Drawable @@ -107,6 +108,10 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch val surface = colorScheme.background.toArgb() val outline = colorScheme.outline.toArgb() + val primaryContainer = colorScheme.primaryContainer.toArgb() + val onPrimaryContainer = colorScheme.onPrimaryContainer.toArgb() + + val onPrimary = colorScheme.onPrimary.toArgb() val onSecondary = colorScheme.onSecondary.toArgb() val onBackground = colorScheme.onBackground.toArgb() val onBackgroundHalf = colorScheme.onBackground.copy(alpha = 0.5f).toArgb() @@ -115,6 +120,7 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch colors[R.styleable.Keyboard_Key_keyTextColor] = onBackground colors[R.styleable.Keyboard_Key_keyTextInactivatedColor] = onBackgroundHalf + colors[R.styleable.Keyboard_Key_keyPressedTextColor] = onPrimary colors[R.styleable.Keyboard_Key_keyTextShadowColor] = 0 colors[R.styleable.Keyboard_Key_functionalTextColor] = onBackground colors[R.styleable.Keyboard_Key_keyHintLetterColor] = onBackgroundHalf @@ -208,10 +214,11 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch setPadding(0, 0, 0, dp(50.dp).roundToInt()) } - moreKeysKeyboardBackground = coloredRoundedRectangle(surface, dp(8.dp)) + moreKeysTextColor = onPrimaryContainer + moreKeysKeyboardBackground = coloredRoundedRectangle(primaryContainer, dp(8.dp)) popupKey = StateListDrawable().apply { - addStateWithHighlightLayerOnPressed(highlight, intArrayOf(), - coloredRoundedRectangle(surface, dp(8.dp)) + addStateWithHighlightLayerOnPressed(primary, intArrayOf(), + coloredRoundedRectangle(primaryContainer, dp(8.dp)) ) } } diff --git a/java/src/org/futo/inputmethod/latin/uix/DynamicThemeProvider.kt b/java/src/org/futo/inputmethod/latin/uix/DynamicThemeProvider.kt index b090921c2..8ab4a5b2c 100644 --- a/java/src/org/futo/inputmethod/latin/uix/DynamicThemeProvider.kt +++ b/java/src/org/futo/inputmethod/latin/uix/DynamicThemeProvider.kt @@ -13,6 +13,7 @@ interface DynamicThemeProvider { val keyFeedback: Drawable + val moreKeysTextColor: Int val moreKeysKeyboardBackground: Drawable val popupKey: Drawable From e5547458696a85fea893e5fd99eccd54ff56ee97 Mon Sep 17 00:00:00 2001 From: Aleksandras Kostarevas Date: Sun, 7 Jan 2024 14:27:12 +0200 Subject: [PATCH 02/10] Add popup symbols for qwerty --- java/res/values/themes-lxx-dark.xml | 1 + java/res/values/themes-lxx-light.xml | 1 + java/res/xml/rowkeys_qwerty2_left5.xml | 12 +++++++++++- java/res/xml/rowkeys_qwerty2_right4.xml | 8 ++++++++ java/res/xml/rowkeys_qwerty3_left4.xml | 8 ++++++++ java/res/xml/rowkeys_qwerty3_right3.xml | 10 ++++++++-- .../keyboard/internal/KeyboardTextsTable.java | 2 +- 7 files changed, 38 insertions(+), 4 deletions(-) diff --git a/java/res/values/themes-lxx-dark.xml b/java/res/values/themes-lxx-dark.xml index 2b53e747f..126d24aea 100644 --- a/java/res/values/themes-lxx-dark.xml +++ b/java/res/values/themes-lxx-dark.xml @@ -48,6 +48,7 @@ @drawable/btn_keyboard_spacebar_lxx_dark @color/key_text_color_lxx_dark @color/key_functional_text_color_lxx_dark + @color/key_text_color_lxx_dark @color/key_text_color_lxx_dark @color/key_hint_letter_color_lxx_dark @color/key_text_inactive_color_lxx_dark diff --git a/java/res/values/themes-lxx-light.xml b/java/res/values/themes-lxx-light.xml index aac14fa41..f11e45ff7 100644 --- a/java/res/values/themes-lxx-light.xml +++ b/java/res/values/themes-lxx-light.xml @@ -48,6 +48,7 @@ @drawable/btn_keyboard_spacebar_lxx_light @color/key_text_color_lxx_light @color/key_text_inactive_color_lxx_light + @color/key_text_color_lxx_light @color/key_functional_text_color_lxx_light @color/key_hint_letter_color_lxx_light @color/key_text_inactive_color_lxx_light diff --git a/java/res/xml/rowkeys_qwerty2_left5.xml b/java/res/xml/rowkeys_qwerty2_left5.xml index 540b29b49..cfd1a500b 100644 --- a/java/res/xml/rowkeys_qwerty2_left5.xml +++ b/java/res/xml/rowkeys_qwerty2_left5.xml @@ -23,16 +23,26 @@ > + latin:keySpec="f" + latin:keyHintLabel="%" + latin:additionalMoreKeys="%" /> diff --git a/java/res/xml/rowkeys_qwerty2_right4.xml b/java/res/xml/rowkeys_qwerty2_right4.xml index 685c5b81d..555b9e344 100644 --- a/java/res/xml/rowkeys_qwerty2_right4.xml +++ b/java/res/xml/rowkeys_qwerty2_right4.xml @@ -23,14 +23,22 @@ > diff --git a/java/res/xml/rowkeys_qwerty3_left4.xml b/java/res/xml/rowkeys_qwerty3_left4.xml index 09de5a7e5..d263eeb2a 100644 --- a/java/res/xml/rowkeys_qwerty3_left4.xml +++ b/java/res/xml/rowkeys_qwerty3_left4.xml @@ -23,14 +23,22 @@ > diff --git a/java/res/xml/rowkeys_qwerty3_right3.xml b/java/res/xml/rowkeys_qwerty3_right3.xml index ccacb2cf2..dcf1f4ad4 100644 --- a/java/res/xml/rowkeys_qwerty3_right3.xml +++ b/java/res/xml/rowkeys_qwerty3_right3.xml @@ -22,10 +22,16 @@ xmlns:latin="http://schemas.android.com/apk/res/org.futo.inputmethod.latin" > + latin:keySpec="b" + latin:keyHintLabel=";" + latin:additionalMoreKeys=";" /> + latin:keySpec="m" + latin:keyHintLabel="?" + latin:additionalMoreKeys="?,/" /> diff --git a/java/src/org/futo/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/org/futo/inputmethod/keyboard/internal/KeyboardTextsTable.java index 96f606605..3a8018850 100644 --- a/java/src/org/futo/inputmethod/keyboard/internal/KeyboardTextsTable.java +++ b/java/src/org/futo/inputmethod/keyboard/internal/KeyboardTextsTable.java @@ -302,7 +302,7 @@ public final class KeyboardTextsTable { /* ~ additional_morekeys_symbols_0 */ /* morekeys_tablet_period */ "!text/morekeys_tablet_punctuation", /* morekeys_nordic_row2_11 */ EMPTY, - /* morekeys_punctuation */ "!autoColumnOrder!8,\\,,?,!,#,!text/keyspec_right_parenthesis,!text/keyspec_left_parenthesis,/,;,',@,:,-,\",+,\\%,&", + /* morekeys_punctuation */ "_,\\\\,|,=", /* keyspec_tablet_comma */ ",", // Period key /* keyspec_period */ ".", From 9cccdcb79d5cea48daa35457a20922a26c986976 Mon Sep 17 00:00:00 2001 From: Aleksandras Kostarevas Date: Sun, 7 Jan 2024 14:29:54 +0200 Subject: [PATCH 03/10] Update input logic for actions --- java/src/org/futo/inputmethod/latin/LatinIME.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/java/src/org/futo/inputmethod/latin/LatinIME.kt b/java/src/org/futo/inputmethod/latin/LatinIME.kt index fa9d29185..10243c55c 100644 --- a/java/src/org/futo/inputmethod/latin/LatinIME.kt +++ b/java/src/org/futo/inputmethod/latin/LatinIME.kt @@ -59,6 +59,8 @@ 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 @@ -664,11 +666,11 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save } override fun typeText(v: String) { - latinIMELegacy.mInputLogic.mConnection.commitText(v, 1) + latinIMELegacy.onTextInput(v) } override fun backspace(amount: Int) { - latinIMELegacy.mInputLogic.mConnection.deleteTextBeforeCursor(amount) + latinIMELegacy.onCodeInput(CODE_DELETE, NOT_A_COORDINATE, NOT_A_COORDINATE, false) } override fun closeActionWindow() { From e2999ada34618a08571a0853d8f5ec7b7029398b Mon Sep 17 00:00:00 2001 From: Aleksandras Kostarevas Date: Sun, 7 Jan 2024 16:20:20 +0200 Subject: [PATCH 04/10] Add undo, redo, paste actions --- java/res/drawable/clipboard.xml | 20 +++++++++++++++ java/res/drawable/redo.xml | 20 +++++++++++++++ java/res/drawable/undo.xml | 20 +++++++++++++++ java/res/values/strings-uix.xml | 3 +++ .../org/futo/inputmethod/latin/LatinIME.kt | 15 ++++++++++- .../latin/inputlogic/InputLogic.java | 14 +++++------ .../org/futo/inputmethod/latin/uix/Action.kt | 3 +++ .../futo/inputmethod/latin/uix/ActionBar.kt | 25 ++++++++++++------- .../latin/uix/actions/ClipboardAction.kt | 14 +++++++++++ .../latin/uix/actions/EmojiAction.kt | 5 ++-- .../latin/uix/actions/UndoRedoActions.kt | 22 ++++++++++++++++ 11 files changed, 142 insertions(+), 19 deletions(-) create mode 100644 java/res/drawable/clipboard.xml create mode 100644 java/res/drawable/redo.xml create mode 100644 java/res/drawable/undo.xml create mode 100644 java/src/org/futo/inputmethod/latin/uix/actions/ClipboardAction.kt create mode 100644 java/src/org/futo/inputmethod/latin/uix/actions/UndoRedoActions.kt diff --git a/java/res/drawable/clipboard.xml b/java/res/drawable/clipboard.xml new file mode 100644 index 000000000..e4cf2e78d --- /dev/null +++ b/java/res/drawable/clipboard.xml @@ -0,0 +1,20 @@ + + + + diff --git a/java/res/drawable/redo.xml b/java/res/drawable/redo.xml new file mode 100644 index 000000000..290274cdb --- /dev/null +++ b/java/res/drawable/redo.xml @@ -0,0 +1,20 @@ + + + + diff --git a/java/res/drawable/undo.xml b/java/res/drawable/undo.xml new file mode 100644 index 000000000..8ad21710b --- /dev/null +++ b/java/res/drawable/undo.xml @@ -0,0 +1,20 @@ + + + + diff --git a/java/res/values/strings-uix.xml b/java/res/values/strings-uix.xml index 579c94cef..ebd092013 100644 --- a/java/res/values/strings-uix.xml +++ b/java/res/values/strings-uix.xml @@ -2,6 +2,9 @@ Voice Input Theme Switcher + Paste from Clipboard + Undo + Redo AMOLED Dark Purple AOSP Material Dark diff --git a/java/src/org/futo/inputmethod/latin/LatinIME.kt b/java/src/org/futo/inputmethod/latin/LatinIME.kt index 10243c55c..c0e8827e0 100644 --- a/java/src/org/futo/inputmethod/latin/LatinIME.kt +++ b/java/src/org/futo/inputmethod/latin/LatinIME.kt @@ -5,6 +5,8 @@ import android.content.res.Configuration import android.inputmethodservice.InputMethodService import android.os.Build import android.os.Bundle +import android.os.SystemClock +import android.view.KeyCharacterMap import android.view.KeyEvent import android.view.View import android.view.inputmethod.CompletionInfo @@ -277,7 +279,9 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save @Composable private fun LegacyKeyboardView(hidden: Boolean) { val modifier = if(hidden) { - Modifier.clipToBounds().size(0.dp) + Modifier + .clipToBounds() + .size(0.dp) } else { Modifier.onSizeChanged { inputViewHeight = it.height @@ -703,6 +707,15 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save } } + 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) diff --git a/java/src/org/futo/inputmethod/latin/inputlogic/InputLogic.java b/java/src/org/futo/inputmethod/latin/inputlogic/InputLogic.java index 41ff7f60b..96a3fcf11 100644 --- a/java/src/org/futo/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/org/futo/inputmethod/latin/inputlogic/InputLogic.java @@ -1132,7 +1132,7 @@ public final class InputLogic { // As for the case where we don't know the cursor position, it can happen // because of bugs in the framework. But the framework should know, so the next // best thing is to leave it to whatever it thinks is best. - sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL); + sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL, 0); int totalDeletedLength = 1; if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) { // If this is an accelerated (i.e., double) deletion, then we need to @@ -1140,7 +1140,7 @@ public final class InputLogic { // the previous word, and will lose it after next deletion. hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted( inputTransaction.mSettingsValues, currentKeyboardScriptId); - sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL); + sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL, 0); totalDeletedLength++; } StatsUtils.onBackspacePressed(totalDeletedLength); @@ -2021,13 +2021,13 @@ public final class InputLogic { * * @param keyCode the key code to send inside the key event. */ - private void sendDownUpKeyEvent(final int keyCode) { + public void sendDownUpKeyEvent(final int keyCode, final int metaState) { final long eventTime = SystemClock.uptimeMillis(); mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime, - KeyEvent.ACTION_DOWN, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, + KeyEvent.ACTION_DOWN, keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE)); mConnection.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, - KeyEvent.ACTION_UP, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, + KeyEvent.ACTION_UP, keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE)); } @@ -2045,7 +2045,7 @@ public final class InputLogic { // TODO: Remove this special handling of digit letters. // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}. if (codePoint >= '0' && codePoint <= '9') { - sendDownUpKeyEvent(codePoint - '0' + KeyEvent.KEYCODE_0); + sendDownUpKeyEvent(codePoint - '0' + KeyEvent.KEYCODE_0, 0); return; } @@ -2055,7 +2055,7 @@ public final class InputLogic { // a hardware keyboard event on pressing enter or delete. This is bad for many // reasons (there are race conditions with commits) but some applications are // relying on this behavior so we continue to support it for older apps. - sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER); + sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER, 0); } else { mConnection.commitText(StringUtils.newSingleCodePointString(codePoint), 1); } diff --git a/java/src/org/futo/inputmethod/latin/uix/Action.kt b/java/src/org/futo/inputmethod/latin/uix/Action.kt index aa21b1bfd..20b606963 100644 --- a/java/src/org/futo/inputmethod/latin/uix/Action.kt +++ b/java/src/org/futo/inputmethod/latin/uix/Action.kt @@ -29,6 +29,9 @@ interface KeyboardManagerForAction { fun triggerSystemVoiceInput() fun updateTheme(newTheme: ThemeOption) + + fun sendCodePointEvent(codePoint: Int) + fun sendKeyEvent(keyCode: Int, metaState: Int) } interface ActionWindow { diff --git a/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt index df22b8249..8ffbe8a81 100644 --- a/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt +++ b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ColorScheme import androidx.compose.material3.Icon @@ -60,8 +61,11 @@ import org.futo.inputmethod.latin.SuggestedWords import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo.KIND_TYPED import org.futo.inputmethod.latin.suggestions.SuggestionStripView +import org.futo.inputmethod.latin.uix.actions.ClipboardAction import org.futo.inputmethod.latin.uix.actions.EmojiAction +import org.futo.inputmethod.latin.uix.actions.RedoAction import org.futo.inputmethod.latin.uix.actions.ThemeAction +import org.futo.inputmethod.latin.uix.actions.UndoAction import org.futo.inputmethod.latin.uix.actions.VoiceInputAction import org.futo.inputmethod.latin.uix.theme.DarkColorScheme import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper @@ -287,7 +291,7 @@ fun ActionItem(action: Action, onSelect: (Action) -> Unit) { cornerRadius = CornerRadius(radius, radius) ) } - .width(64.dp) + .width(50.dp) .fillMaxHeight(), colors = IconButtonDefaults.iconButtonColors(contentColor = contentCol) ) { @@ -317,12 +321,9 @@ fun RowScope.ActionItems(onSelect: (Action) -> Unit) { ActionItem(EmojiAction, onSelect) ActionItem(VoiceInputAction, onSelect) ActionItem(ThemeAction, onSelect) - - Box(modifier = Modifier - .fillMaxHeight() - .weight(1.0f)) { - - } + ActionItem(UndoAction, onSelect) + ActionItem(RedoAction, onSelect) + ActionItem(ClipboardAction, onSelect) } @@ -386,7 +387,11 @@ fun ActionBar( ExpandActionsButton(isActionsOpen.value) { isActionsOpen.value = !isActionsOpen.value } if(isActionsOpen.value) { - ActionItems(onActionActivated) + LazyRow { + item { + ActionItems(onActionActivated) + } + } } else if(inlineSuggestions.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { InlineSuggestions(inlineSuggestions) } else if(words != null) { @@ -399,7 +404,9 @@ fun ActionBar( Spacer(modifier = Modifier.weight(1.0f)) } - ActionItemSmall(VoiceInputAction, onActionActivated) + if(!isActionsOpen.value) { + ActionItemSmall(VoiceInputAction, onActionActivated) + } } } } diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/ClipboardAction.kt b/java/src/org/futo/inputmethod/latin/uix/actions/ClipboardAction.kt new file mode 100644 index 000000000..07ee7f3f1 --- /dev/null +++ b/java/src/org/futo/inputmethod/latin/uix/actions/ClipboardAction.kt @@ -0,0 +1,14 @@ +package org.futo.inputmethod.latin.uix.actions + +import android.view.KeyEvent +import org.futo.inputmethod.latin.R +import org.futo.inputmethod.latin.uix.Action + +val ClipboardAction = Action( + icon = R.drawable.clipboard, + name = R.string.clipboard_action_title, + simplePressImpl = { manager, _ -> + manager.sendKeyEvent(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON) + }, + windowImpl = null, +) \ No newline at end of file diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt b/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt index d23637fda..8cf36d093 100644 --- a/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt +++ b/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt @@ -55,6 +55,7 @@ import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import org.futo.inputmethod.latin.R +import org.futo.inputmethod.latin.common.Constants import org.futo.inputmethod.latin.uix.Action import org.futo.inputmethod.latin.uix.ActionWindow import org.futo.inputmethod.latin.uix.PersistentActionState @@ -293,9 +294,9 @@ val EmojiAction = Action( }, onExit = { manager.closeActionWindow() }, onSpace = { - manager.typeText(" ") + manager.sendCodePointEvent(Constants.CODE_SPACE) }, onBackspace = { - manager.backspace(1) + manager.sendCodePointEvent(Constants.CODE_DELETE) }, bitmaps = state.bitmaps, emojis = emojis) } } diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/UndoRedoActions.kt b/java/src/org/futo/inputmethod/latin/uix/actions/UndoRedoActions.kt new file mode 100644 index 000000000..09742728a --- /dev/null +++ b/java/src/org/futo/inputmethod/latin/uix/actions/UndoRedoActions.kt @@ -0,0 +1,22 @@ +package org.futo.inputmethod.latin.uix.actions + +import android.view.KeyEvent +import org.futo.inputmethod.latin.R +import org.futo.inputmethod.latin.uix.Action + +val UndoAction = Action( + icon = R.drawable.undo, + name = R.string.undo_action_title, + simplePressImpl = { manager, _ -> + manager.sendKeyEvent(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON) + }, + windowImpl = null, +) +val RedoAction = Action( + icon = R.drawable.redo, + name = R.string.redo_action_title, + simplePressImpl = { manager, _ -> + manager.sendKeyEvent(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON) + }, + windowImpl = null, +) \ No newline at end of file From 7f17d66072781c61f9cad0adda60c459de4d8873 Mon Sep 17 00:00:00 2001 From: Aleksandras Kostarevas Date: Sun, 7 Jan 2024 16:22:17 +0200 Subject: [PATCH 05/10] Add sliding cursor/deletion --- .../keyboard/KeyboardActionListener.java | 17 +++++- .../inputmethod/keyboard/PointerTracker.java | 49 ++++++++++++++++ .../inputmethod/latin/LatinIMELegacy.java | 56 ++++++++++++++++++- .../latin/RichInputConnection.java | 37 ++++++++++++ 4 files changed, 153 insertions(+), 6 deletions(-) diff --git a/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java b/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java index a8e75de45..1bb2ed695 100644 --- a/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java +++ b/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java @@ -101,6 +101,11 @@ public interface KeyboardActionListener { */ public boolean onCustomRequest(int requestCode); + public void onMovePointer(int steps); + public void onMoveDeletePointer(int steps); + public void onUpWithDeletePointerActive(); + public void onUpWithPointerActive(); + public static final KeyboardActionListener EMPTY_LISTENER = new Adapter(); public static class Adapter implements KeyboardActionListener { @@ -125,8 +130,14 @@ public interface KeyboardActionListener { @Override public void onFinishSlidingInput() {} @Override - public boolean onCustomRequest(int requestCode) { - return false; - } + public boolean onCustomRequest(int requestCode) { return false; } + @Override + public void onMovePointer(int steps) {} + @Override + public void onMoveDeletePointer(int steps) {} + @Override + public void onUpWithDeletePointerActive() {} + @Override + public void onUpWithPointerActive() {} } } diff --git a/java/src/org/futo/inputmethod/keyboard/PointerTracker.java b/java/src/org/futo/inputmethod/keyboard/PointerTracker.java index d19d91ad0..4e59e44ba 100644 --- a/java/src/org/futo/inputmethod/keyboard/PointerTracker.java +++ b/java/src/org/futo/inputmethod/keyboard/PointerTracker.java @@ -85,6 +85,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element, // Parameters for pointer handling. private static PointerTrackerParams sParams; + private static final int sPointerStep = (int)(16.0 * Resources.getSystem().getDisplayMetrics().density); + private static GestureStrokeRecognitionParams sGestureStrokeRecognitionParams; private static GestureStrokeDrawingParams sGestureStrokeDrawingParams; private static boolean sNeedsPhantomSuddenMoveEventHack; @@ -128,6 +130,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element, private int mLastX; private int mLastY; + private int mStartX; + private int mStartY; + private long mStartTime; + private boolean mCursorMoved = false; + // true if keyboard layout has been changed. private boolean mKeyboardLayoutHasBeenChanged; @@ -691,6 +698,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element, startRepeatKey(key); startLongPressTimer(key); setPressedKeyGraphics(key, eventTime); + + mStartX = x; + mStartY = y; + mStartTime = System.currentTimeMillis(); } } @@ -892,6 +903,29 @@ public final class PointerTracker implements PointerTrackerQueue.Element, final int lastX = mLastX; final int lastY = mLastY; final Key oldKey = mCurrentKey; + + if (oldKey != null && oldKey.getCode() == Constants.CODE_SPACE) { + int steps = (x - mStartX) / sPointerStep; + final int swipeIgnoreTime = Settings.getInstance().getCurrent().mKeyLongpressTimeout / MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT; + if (steps != 0 && mStartTime + swipeIgnoreTime < System.currentTimeMillis()) { + mCursorMoved = true; + mStartX += steps * sPointerStep; + sListener.onMovePointer(steps); + } + return; + } + + if (oldKey != null && oldKey.getCode() == Constants.CODE_DELETE) { + int steps = (x - mStartX) / sPointerStep; + if (steps != 0) { + sTimerProxy.cancelKeyTimersOf(this); + mCursorMoved = true; + mStartX += steps * sPointerStep; + sListener.onMoveDeletePointer(steps); + } + return; + } + final Key newKey = onMoveKey(x, y); if (sGestureEnabler.shouldHandleGesture()) { @@ -966,6 +1000,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element, // Release the last pressed key. setReleasedKeyGraphics(currentKey, true /* withAnimation */); + if(mCursorMoved && currentKey.getCode() == Constants.CODE_DELETE) { + sListener.onUpWithDeletePointerActive(); + } + + if(mCursorMoved) { + sListener.onUpWithPointerActive(); + } + if (isShowingMoreKeysPanel()) { if (!mIsTrackingForActionDisabled) { final int translatedX = mMoreKeysPanel.translateX(x); @@ -988,6 +1030,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element, return; } + if (mCursorMoved) { + mCursorMoved = false; + return; + } if (mIsTrackingForActionDisabled) { return; } @@ -1018,6 +1064,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element, if (isShowingMoreKeysPanel()) { return; } + if (mCursorMoved) { + return; + } final Key key = getKey(); if (key == null) { return; diff --git a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java index 74f123d34..a6dfe0782 100644 --- a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java +++ b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java @@ -41,6 +41,7 @@ import android.os.IBinder; import android.os.Message; import android.preference.PreferenceManager; import android.text.InputType; +import android.text.TextUtils; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; @@ -158,6 +159,7 @@ public class LatinIMELegacy implements KeyboardActionListener, private static final String SCHEME_PACKAGE = "package"; final Settings mSettings; + private Locale mLocale; final DictionaryFacilitator mDictionaryFacilitator = DictionaryFacilitatorProvider.getDictionaryFacilitator( false /* isNeededForSpellChecking */); @@ -671,18 +673,18 @@ public class LatinIMELegacy implements KeyboardActionListener, // Has to be package-visible for unit tests @UsedForTesting void loadSettings() { - final Locale locale = mRichImm.getCurrentSubtypeLocale(); + mLocale = mRichImm.getCurrentSubtypeLocale(); final EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo(); final InputAttributes inputAttributes = new InputAttributes( editorInfo, mInputMethodService.isFullscreenMode(), mInputMethodService.getPackageName()); - mSettings.loadSettings(mInputMethodService, locale, inputAttributes); + mSettings.loadSettings(mInputMethodService, mLocale, inputAttributes); final SettingsValues currentSettingsValues = mSettings.getCurrent(); AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues); // This method is called on startup and language switch, before the new layout has // been displayed. Opening dictionaries never affects responsivity as dictionaries are // asynchronously loaded. if (!mHandler.hasPendingReopenDictionaries()) { - resetDictionaryFacilitator(locale); + resetDictionaryFacilitator(mLocale); } refreshPersonalizationDictionarySession(currentSettingsValues); resetDictionaryFacilitatorIfNecessary(); @@ -1365,6 +1367,54 @@ public class LatinIMELegacy implements KeyboardActionListener, return false; } + @Override + public void onMovePointer(int steps) { + if (mInputLogic.mConnection.hasCursorPosition()) { + if (TextUtils.getLayoutDirectionFromLocale(mLocale) == View.LAYOUT_DIRECTION_RTL) + steps = -steps; + + steps = mInputLogic.mConnection.getUnicodeSteps(steps, true); + final int end = mInputLogic.mConnection.getExpectedSelectionEnd() + steps; + final int start = mInputLogic.mConnection.hasSelection() ? mInputLogic.mConnection.getExpectedSelectionStart() : end; + + mInputLogic.finishInput(); + mInputLogic.mConnection.setSelection(start, end); + } else { + for (; steps < 0; steps++) + mInputLogic.sendDownUpKeyEvent(KeyEvent.KEYCODE_DPAD_LEFT, 0); + for (; steps > 0; steps--) + mInputLogic.sendDownUpKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, 0); + } + } + + @Override + public void onMoveDeletePointer(int steps) { + if (mInputLogic.mConnection.hasCursorPosition()) { + steps = mInputLogic.mConnection.getUnicodeSteps(steps, false); + final int end = mInputLogic.mConnection.getExpectedSelectionEnd(); + final int start = mInputLogic.mConnection.getExpectedSelectionStart() + steps; + if (start > end) + return; + + mInputLogic.finishInput(); + mInputLogic.mConnection.setSelection(start, end); + } else { + for (; steps < 0; steps++) + mInputLogic.sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL, 0); + } + } + + @Override + public void onUpWithDeletePointerActive() { + if (mInputLogic.mConnection.hasSelection()) + mInputLogic.sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL, 0); + } + + @Override + public void onUpWithPointerActive() { + mInputLogic.restartSuggestionsOnWordTouchedByCursor(mSettings.getCurrent(), false, mKeyboardSwitcher.getCurrentKeyboardScriptId()); + } + private boolean isShowingOptionDialog() { return mOptionsDialog != null && mOptionsDialog.isShowing(); } diff --git a/java/src/org/futo/inputmethod/latin/RichInputConnection.java b/java/src/org/futo/inputmethod/latin/RichInputConnection.java index 5c07d7e4d..6e719a1dc 100644 --- a/java/src/org/futo/inputmethod/latin/RichInputConnection.java +++ b/java/src/org/futo/inputmethod/latin/RichInputConnection.java @@ -1044,4 +1044,41 @@ public final class RichInputConnection implements PrivateCommandPerformer { return InputConnectionCompatUtils.requestCursorUpdates( mIC, enableMonitor, requestImmediateCallback); } + + public boolean hasCursorPosition() { + return mExpectedSelStart != INVALID_CURSOR_POSITION && mExpectedSelEnd != INVALID_CURSOR_POSITION; + } + + /** + * Some chars, such as emoji consist of 2 chars (surrogate pairs). We should treat them as one character. + */ + public int getUnicodeSteps(int chars, boolean rightSidePointer) { + int steps = 0; + if (chars < 0) { + CharSequence charsBeforeCursor = rightSidePointer && hasSelection() ? + getSelectedText(0) : + getTextBeforeCursor(-chars * 2, 0); + if (charsBeforeCursor != null) { + for (int i = charsBeforeCursor.length() - 1; i >= 0 && chars < 0; i--, chars++, steps--) { + if (Character.isSurrogate(charsBeforeCursor.charAt(i))) { + steps--; + i--; + } + } + } + } else if (chars > 0) { + CharSequence charsAfterCursor = !rightSidePointer && hasSelection() ? + getSelectedText(0) : + getTextAfterCursor(chars * 2, 0); + if (charsAfterCursor != null) { + for (int i = 0; i < charsAfterCursor.length() && chars > 0; i++, chars--, steps++) { + if (Character.isSurrogate(charsAfterCursor.charAt(i))) { + steps++; + i++; + } + } + } + } + return steps; + } } From b237e49ece89854e889398fa7ae451716f87e01c Mon Sep 17 00:00:00 2001 From: Aleksandras Kostarevas Date: Mon, 8 Jan 2024 20:23:54 +0200 Subject: [PATCH 06/10] Add basic text edit mode --- java/res/drawable/arrow_down.xml | 20 ++ java/res/drawable/arrow_left.xml | 16 +- java/res/drawable/arrow_left_26.xml | 20 ++ java/res/drawable/arrow_right.xml | 20 ++ java/res/drawable/arrow_up.xml | 20 ++ java/res/drawable/edit_text.xml | 20 ++ java/res/values/strings-uix.xml | 2 + java/res/values/strings.xml | 1 - .../org/futo/inputmethod/latin/LatinIME.kt | 4 +- .../futo/inputmethod/latin/uix/ActionBar.kt | 2 + .../latin/uix/actions/EmojiAction.kt | 4 +- .../latin/uix/actions/TextEditAction.kt | 310 ++++++++++++++++++ 12 files changed, 425 insertions(+), 14 deletions(-) create mode 100644 java/res/drawable/arrow_down.xml create mode 100644 java/res/drawable/arrow_left_26.xml create mode 100644 java/res/drawable/arrow_right.xml create mode 100644 java/res/drawable/arrow_up.xml create mode 100644 java/res/drawable/edit_text.xml create mode 100644 java/src/org/futo/inputmethod/latin/uix/actions/TextEditAction.kt diff --git a/java/res/drawable/arrow_down.xml b/java/res/drawable/arrow_down.xml new file mode 100644 index 000000000..9c8ef5e6c --- /dev/null +++ b/java/res/drawable/arrow_down.xml @@ -0,0 +1,20 @@ + + + + diff --git a/java/res/drawable/arrow_left.xml b/java/res/drawable/arrow_left.xml index f53ed4d37..51868088b 100644 --- a/java/res/drawable/arrow_left.xml +++ b/java/res/drawable/arrow_left.xml @@ -1,20 +1,20 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> diff --git a/java/res/drawable/arrow_left_26.xml b/java/res/drawable/arrow_left_26.xml new file mode 100644 index 000000000..f53ed4d37 --- /dev/null +++ b/java/res/drawable/arrow_left_26.xml @@ -0,0 +1,20 @@ + + + + diff --git a/java/res/drawable/arrow_right.xml b/java/res/drawable/arrow_right.xml new file mode 100644 index 000000000..5109cd87b --- /dev/null +++ b/java/res/drawable/arrow_right.xml @@ -0,0 +1,20 @@ + + + + diff --git a/java/res/drawable/arrow_up.xml b/java/res/drawable/arrow_up.xml new file mode 100644 index 000000000..8d338651a --- /dev/null +++ b/java/res/drawable/arrow_up.xml @@ -0,0 +1,20 @@ + + + + diff --git a/java/res/drawable/edit_text.xml b/java/res/drawable/edit_text.xml new file mode 100644 index 000000000..f2d53814c --- /dev/null +++ b/java/res/drawable/edit_text.xml @@ -0,0 +1,20 @@ + + + + diff --git a/java/res/values/strings-uix.xml b/java/res/values/strings-uix.xml index ebd092013..ae8682877 100644 --- a/java/res/values/strings-uix.xml +++ b/java/res/values/strings-uix.xml @@ -2,9 +2,11 @@ Voice Input Theme Switcher + Emojis Paste from Clipboard Undo Redo + Text Editor AMOLED Dark Purple AOSP Material Dark diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml index d3a7f826c..e7b51e048 100644 --- a/java/res/values/strings.xml +++ b/java/res/values/strings.xml @@ -573,5 +573,4 @@ Tip: You can download and remove dictionaries by going to <b>Languages & This resource is copied from packages/apps/Settings/res/values/strings.xml --> \u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ - Emojis diff --git a/java/src/org/futo/inputmethod/latin/LatinIME.kt b/java/src/org/futo/inputmethod/latin/LatinIME.kt index c0e8827e0..2f0310894 100644 --- a/java/src/org/futo/inputmethod/latin/LatinIME.kt +++ b/java/src/org/futo/inputmethod/latin/LatinIME.kt @@ -5,8 +5,6 @@ import android.content.res.Configuration import android.inputmethodservice.InputMethodService import android.os.Build import android.os.Bundle -import android.os.SystemClock -import android.view.KeyCharacterMap import android.view.KeyEvent import android.view.View import android.view.inputmethod.CompletionInfo @@ -359,7 +357,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save returnBackToMainKeyboardViewFromAction() }) { Icon( - painter = painterResource(id = R.drawable.arrow_left), + painter = painterResource(id = R.drawable.arrow_left_26), contentDescription = "Back" ) } diff --git a/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt index 8ffbe8a81..b81164b3f 100644 --- a/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt +++ b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt @@ -64,6 +64,7 @@ import org.futo.inputmethod.latin.suggestions.SuggestionStripView import org.futo.inputmethod.latin.uix.actions.ClipboardAction import org.futo.inputmethod.latin.uix.actions.EmojiAction import org.futo.inputmethod.latin.uix.actions.RedoAction +import org.futo.inputmethod.latin.uix.actions.TextEditAction import org.futo.inputmethod.latin.uix.actions.ThemeAction import org.futo.inputmethod.latin.uix.actions.UndoAction import org.futo.inputmethod.latin.uix.actions.VoiceInputAction @@ -324,6 +325,7 @@ fun RowScope.ActionItems(onSelect: (Action) -> Unit) { ActionItem(UndoAction, onSelect) ActionItem(RedoAction, onSelect) ActionItem(ClipboardAction, onSelect) + ActionItem(TextEditAction, onSelect) } diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt b/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt index 8cf36d093..174a69295 100644 --- a/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt +++ b/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt @@ -267,7 +267,7 @@ class PersistentEmojiState: PersistentActionState { val EmojiAction = Action( icon = R.drawable.smile, - name = R.string.title_emojis, + name = R.string.emoji_action_title, simplePressImpl = null, persistentState = { manager -> val state = PersistentEmojiState() @@ -283,7 +283,7 @@ val EmojiAction = Action( object : ActionWindow { @Composable override fun windowName(): String { - return stringResource(R.string.title_emojis) + return stringResource(R.string.emoji_action_title) } @Composable diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/TextEditAction.kt b/java/src/org/futo/inputmethod/latin/uix/actions/TextEditAction.kt new file mode 100644 index 000000000..1dc1bb0cd --- /dev/null +++ b/java/src/org/futo/inputmethod/latin/uix/actions/TextEditAction.kt @@ -0,0 +1,310 @@ +package org.futo.inputmethod.latin.uix.actions + +import android.view.KeyEvent +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +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.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.delay +import org.futo.inputmethod.latin.R +import org.futo.inputmethod.latin.uix.Action +import org.futo.inputmethod.latin.uix.ActionWindow + +@Composable +fun IconWithColor(@DrawableRes iconId: Int, iconColor: Color, modifier: Modifier = Modifier) { + val icon = painterResource(id = iconId) + + Canvas(modifier = modifier) { + 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 + ) + ) + } + } + } +} + +@Composable +fun RepeatableActionKey( + onTrigger: () -> Unit, + modifier: Modifier = Modifier, + contents: @Composable () -> Unit +) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + + LaunchedEffect(isPressed) { + if(isPressed) { + onTrigger() + delay(670L) + while(isPressed) { + onTrigger() + delay(50L) + } + } + } + + Surface( + modifier = modifier.clickable( + interactionSource = interactionSource, + indication = null, + onClick = { } + ), + color = MaterialTheme.colorScheme.primary + ) { + contents() + } + +} + +@Composable +@Preview(showBackground = true) +fun TextEditScreen(onCodePoint: (Int) -> Unit = { _ -> }, onEvent: (Int, Int) -> Unit = { _, _ -> }) { + var shiftState by remember { mutableStateOf(false) } + var ctrlState by remember { mutableStateOf(false) } + + val metaState = 0 or + (if(shiftState) { KeyEvent.META_SHIFT_ON } else { 0 }) or + (if(ctrlState) { KeyEvent.META_CTRL_ON } else { 0 }) + + + val buttonColorsUntoggled = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + contentColor = MaterialTheme.colorScheme.onSecondaryContainer + ) + + val buttonColorsToggled = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.secondary, + contentColor = MaterialTheme.colorScheme.onSecondary + ) + + val sendEvent = { keycode: Int -> onEvent(keycode, metaState) } + + val keySize = 48.dp + + Column { + Row(modifier = Modifier.fillMaxWidth()) { + Spacer(modifier = Modifier.weight(1.0f)) + + Box( + modifier = Modifier + .height(keySize * 2) + .width(keySize * 3) + ) { + RepeatableActionKey( + modifier = Modifier + .align(Alignment.TopStart) + .width(keySize) + .height(keySize), + onTrigger = { sendEvent(KeyEvent.KEYCODE_INSERT) } + ) { + Text("Ins", modifier = Modifier.padding(4.dp)) + } + + RepeatableActionKey( + modifier = Modifier + .align(Alignment.BottomStart) + .width(keySize) + .height(keySize), + onTrigger = { sendEvent(KeyEvent.KEYCODE_DEL) } + ) { + Text("Del", modifier = Modifier.padding(4.dp)) + } + + RepeatableActionKey( + modifier = Modifier + .align(Alignment.TopCenter) + .width(keySize) + .height(keySize), + onTrigger = { sendEvent(KeyEvent.KEYCODE_MOVE_HOME) } + ) { + Text("Home", modifier = Modifier.padding(4.dp)) + } + + RepeatableActionKey( + modifier = Modifier + .align(Alignment.BottomCenter) + .width(keySize) + .height(keySize), + onTrigger = { sendEvent(KeyEvent.KEYCODE_MOVE_END) } + ) { + Text("End", modifier = Modifier.padding(4.dp)) + } + + RepeatableActionKey( + modifier = Modifier + .align(Alignment.TopEnd) + .width(keySize) + .height(keySize), + onTrigger = { sendEvent(KeyEvent.KEYCODE_PAGE_UP) } + ) { + Text("PgUp", modifier = Modifier.padding(4.dp)) + } + + RepeatableActionKey( + modifier = Modifier + .align(Alignment.BottomEnd) + .width(keySize) + .height(keySize), + onTrigger = { sendEvent(KeyEvent.KEYCODE_PAGE_DOWN) } + ) { + Text("PgDn", modifier = Modifier.padding(4.dp)) + } + } + + } + + Spacer(modifier = Modifier.height(32.dp)) + Row(modifier = Modifier.fillMaxWidth()) { + Box(modifier = Modifier.height(92.dp)) { + Button( + onClick = { shiftState = !shiftState }, + colors = if (shiftState) { + buttonColorsToggled + } else { + buttonColorsUntoggled + }, + modifier = Modifier.align(Alignment.TopStart).width(keySize * 3) + ) { + Row { + Text("Shift") + } + } + Button( + onClick = { ctrlState = !ctrlState }, + colors = if (ctrlState) { + buttonColorsToggled + } else { + buttonColorsUntoggled + }, + modifier = Modifier.align(Alignment.BottomStart).width(keySize * 2) + ) { + Text("Ctrl") + } + } + + Spacer(modifier = Modifier.weight(1.0f)) + + + + + Box( + modifier = Modifier + .height(keySize * 2) + .width(keySize * 3) + ) { + RepeatableActionKey( + modifier = Modifier + .align(Alignment.TopCenter) + .width(keySize) + .height(keySize), + onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_UP) } + ) { + IconWithColor( + iconId = R.drawable.arrow_up, + iconColor = MaterialTheme.colorScheme.onPrimary + ) + } + + + RepeatableActionKey( + modifier = Modifier + .align(Alignment.BottomCenter) + .width(keySize) + .height(keySize), + onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_DOWN) } + ) { + IconWithColor( + iconId = R.drawable.arrow_down, + iconColor = MaterialTheme.colorScheme.onPrimary + ) + } + + RepeatableActionKey( + modifier = Modifier + .align(Alignment.BottomStart) + .width(keySize) + .height(keySize), + onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_LEFT) } + ) { + IconWithColor( + iconId = R.drawable.arrow_left, + iconColor = MaterialTheme.colorScheme.onPrimary + ) + } + + RepeatableActionKey( + modifier = Modifier + .align(Alignment.BottomEnd) + .width(keySize) + .height(keySize), + onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_RIGHT) } + ) { + IconWithColor( + iconId = R.drawable.arrow_right, + iconColor = MaterialTheme.colorScheme.onPrimary + ) + } + } + } + } +} + +val TextEditAction = Action( + icon = R.drawable.edit_text, + name = R.string.text_edit_action_title, + simplePressImpl = null, + persistentState = null, + windowImpl = { manager, persistentState -> + object : ActionWindow { + @Composable + override fun windowName(): String { + return stringResource(R.string.text_edit_action_title) + } + + @Composable + override fun WindowContents() { + TextEditScreen(onCodePoint = { a -> manager.sendCodePointEvent(a)}, onEvent = { a, b -> manager.sendKeyEvent(a, b) }) + } + + override fun close() { + } + } + } +) \ No newline at end of file From adfab750862e6cf89141bddfe96a47f90e8df389 Mon Sep 17 00:00:00 2001 From: Aleksandras Kostarevas Date: Tue, 9 Jan 2024 10:56:14 +0200 Subject: [PATCH 07/10] Update text edit mode --- java/res/drawable/copy.xml | 20 + java/res/drawable/ctrl.xml | 27 + java/res/drawable/cut.xml | 41 ++ .../latin/uix/actions/TextEditAction.kt | 462 ++++++++++-------- 4 files changed, 348 insertions(+), 202 deletions(-) create mode 100644 java/res/drawable/copy.xml create mode 100644 java/res/drawable/ctrl.xml create mode 100644 java/res/drawable/cut.xml diff --git a/java/res/drawable/copy.xml b/java/res/drawable/copy.xml new file mode 100644 index 000000000..be18c41bb --- /dev/null +++ b/java/res/drawable/copy.xml @@ -0,0 +1,20 @@ + + + + diff --git a/java/res/drawable/ctrl.xml b/java/res/drawable/ctrl.xml new file mode 100644 index 000000000..9c0a06d18 --- /dev/null +++ b/java/res/drawable/ctrl.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/java/res/drawable/cut.xml b/java/res/drawable/cut.xml new file mode 100644 index 000000000..3a734cc16 --- /dev/null +++ b/java/res/drawable/cut.xml @@ -0,0 +1,41 @@ + + + + + + + diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/TextEditAction.kt b/java/src/org/futo/inputmethod/latin/uix/actions/TextEditAction.kt index 1dc1bb0cd..a56637e51 100644 --- a/java/src/org/futo/inputmethod/latin/uix/actions/TextEditAction.kt +++ b/java/src/org/futo/inputmethod/latin/uix/actions/TextEditAction.kt @@ -3,29 +3,26 @@ package org.futo.inputmethod.latin.uix.actions import android.view.KeyEvent import androidx.annotation.DrawableRes import androidx.compose.foundation.Canvas +import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState -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.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState 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.graphics.Color import androidx.compose.ui.graphics.ColorFilter @@ -36,6 +33,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.coroutines.delay import org.futo.inputmethod.latin.R +import org.futo.inputmethod.latin.common.Constants import org.futo.inputmethod.latin.uix.Action import org.futo.inputmethod.latin.uix.ActionWindow @@ -61,9 +59,43 @@ fun IconWithColor(@DrawableRes iconId: Int, iconColor: Color, modifier: Modifier } @Composable -fun RepeatableActionKey( +fun TogglableKey( + onToggle: (Boolean) -> Unit, + toggled: Boolean, + modifier: Modifier = Modifier, + contents: @Composable (color: Color) -> Unit +) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + + LaunchedEffect(isPressed) { + if(isPressed) { + onToggle(!toggled) + } + } + + Surface( + modifier = modifier + .padding(4.dp) + .clickable( + interactionSource = interactionSource, + indication = LocalIndication.current, + onClick = { } + ), + shape = RoundedCornerShape(8.dp), + color = if(toggled) { MaterialTheme.colorScheme.secondary } else { MaterialTheme.colorScheme.secondaryContainer } + ) { + contents(if(toggled) { MaterialTheme.colorScheme.onSecondary } else { MaterialTheme.colorScheme.onSecondaryContainer }) + } + +} + +@Composable +fun ActionKey( onTrigger: () -> Unit, modifier: Modifier = Modifier, + repeatable: Boolean = true, + color: Color = MaterialTheme.colorScheme.primary, contents: @Composable () -> Unit ) { val interactionSource = remember { MutableInteractionSource() } @@ -72,217 +104,235 @@ fun RepeatableActionKey( LaunchedEffect(isPressed) { if(isPressed) { onTrigger() - delay(670L) - while(isPressed) { - onTrigger() - delay(50L) + if(repeatable) { + delay(670L) + while (isPressed) { + onTrigger() + delay(50L) + } } } } Surface( - modifier = modifier.clickable( - interactionSource = interactionSource, - indication = null, - onClick = { } - ), - color = MaterialTheme.colorScheme.primary + modifier = modifier + .padding(4.dp) + .clickable( + interactionSource = interactionSource, + indication = LocalIndication.current, + onClick = { } + ), + shape = RoundedCornerShape(8.dp), + color = color ) { contents() } - } @Composable -@Preview(showBackground = true) -fun TextEditScreen(onCodePoint: (Int) -> Unit = { _ -> }, onEvent: (Int, Int) -> Unit = { _, _ -> }) { - var shiftState by remember { mutableStateOf(false) } - var ctrlState by remember { mutableStateOf(false) } +fun ArrowKeys(modifier: Modifier, sendEvent: (Int) -> Unit) { + Row(modifier = modifier) { + ActionKey( + modifier = Modifier + .weight(1.0f) + .fillMaxHeight(), + onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_LEFT) } + ) { + IconWithColor( + iconId = R.drawable.arrow_left, + iconColor = MaterialTheme.colorScheme.onPrimary + ) + } + + Column(modifier = Modifier + .weight(1.0f) + .fillMaxHeight()) { + ActionKey( + modifier = Modifier + .weight(1.0f) + .fillMaxWidth(), + onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_UP) } + ) { + IconWithColor( + iconId = R.drawable.arrow_up, + iconColor = MaterialTheme.colorScheme.onPrimary + ) + } + + + ActionKey( + modifier = Modifier + .weight(1.0f) + .fillMaxWidth(), + onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_DOWN) } + ) { + IconWithColor( + iconId = R.drawable.arrow_down, + iconColor = MaterialTheme.colorScheme.onPrimary + ) + } + } + + ActionKey( + modifier = Modifier + .weight(1.0f) + .fillMaxHeight(), + onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_RIGHT) } + ) { + IconWithColor( + iconId = R.drawable.arrow_right, + iconColor = MaterialTheme.colorScheme.onPrimary + ) + } + } +} + +@Composable +fun CtrlShiftMetaKeys(modifier: Modifier, ctrlState: MutableState, shiftState: MutableState) { + Row(modifier = modifier) { + TogglableKey( + onToggle = { ctrlState.value = it }, + toggled = ctrlState.value, + modifier = Modifier + .weight(1.0f) + .fillMaxHeight() + ) { + IconWithColor( + iconId = R.drawable.ctrl, + iconColor = it + ) + } + TogglableKey( + onToggle = { shiftState.value = it }, + toggled = shiftState.value, + modifier = Modifier + .weight(1.0f) + .fillMaxHeight() + ) { + IconWithColor( + iconId = R.drawable.shift, + iconColor = it + ) + } + } +} + +@Composable +fun SideKeys(modifier: Modifier, onEvent: (Int, Int) -> Unit, onCodePoint: (Int) -> Unit) { + Column(modifier = modifier) { + ActionKey( + modifier = Modifier + .weight(1.0f) + .fillMaxWidth(), + repeatable = false, + color = MaterialTheme.colorScheme.primaryContainer, + onTrigger = { onEvent(KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_ON) } + ) { + IconWithColor( + iconId = R.drawable.copy, + iconColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + + ActionKey( + modifier = Modifier + .weight(1.0f) + .fillMaxWidth(), + repeatable = false, + color = MaterialTheme.colorScheme.primaryContainer, + onTrigger = { onEvent(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON) } + ) { + IconWithColor( + iconId = R.drawable.clipboard, + iconColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + + ActionKey( + modifier = Modifier + .weight(1.0f) + .fillMaxWidth(), + repeatable = true, + color = MaterialTheme.colorScheme.primaryContainer, + onTrigger = { onCodePoint(Constants.CODE_DELETE) } + ) { + IconWithColor( + iconId = R.drawable.delete, + iconColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + + + Row(modifier = Modifier + .weight(1.0f) + .fillMaxWidth()) { + ActionKey( + modifier = Modifier + .weight(1.0f) + .fillMaxHeight(), + repeatable = false, + color = MaterialTheme.colorScheme.primaryContainer, + onTrigger = { onEvent(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON) } + ) { + IconWithColor( + iconId = R.drawable.undo, + iconColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + + ActionKey( + modifier = Modifier + .weight(1.0f) + .fillMaxHeight(), + repeatable = false, + color = MaterialTheme.colorScheme.primaryContainer, + onTrigger = { onEvent(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON) } + ) { + IconWithColor( + iconId = R.drawable.redo, + iconColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + } +} + +@Composable +fun TextEditScreen(onCodePoint: (Int) -> Unit, onEvent: (Int, Int) -> Unit) { + val shiftState = remember { mutableStateOf(false) } + val ctrlState = remember { mutableStateOf(false) } val metaState = 0 or - (if(shiftState) { KeyEvent.META_SHIFT_ON } else { 0 }) or - (if(ctrlState) { KeyEvent.META_CTRL_ON } else { 0 }) - - - val buttonColorsUntoggled = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - contentColor = MaterialTheme.colorScheme.onSecondaryContainer - ) - - val buttonColorsToggled = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.secondary, - contentColor = MaterialTheme.colorScheme.onSecondary - ) + (if(shiftState.value) { KeyEvent.META_SHIFT_ON } else { 0 }) or + (if(ctrlState.value) { KeyEvent.META_CTRL_ON } else { 0 }) val sendEvent = { keycode: Int -> onEvent(keycode, metaState) } - val keySize = 48.dp - - Column { - Row(modifier = Modifier.fillMaxWidth()) { - Spacer(modifier = Modifier.weight(1.0f)) - - Box( + Row(modifier = Modifier.fillMaxSize()) { + Column(modifier = Modifier + .fillMaxHeight() + .weight(3.0f)) { + ArrowKeys( modifier = Modifier - .height(keySize * 2) - .width(keySize * 3) - ) { - RepeatableActionKey( - modifier = Modifier - .align(Alignment.TopStart) - .width(keySize) - .height(keySize), - onTrigger = { sendEvent(KeyEvent.KEYCODE_INSERT) } - ) { - Text("Ins", modifier = Modifier.padding(4.dp)) - } - - RepeatableActionKey( - modifier = Modifier - .align(Alignment.BottomStart) - .width(keySize) - .height(keySize), - onTrigger = { sendEvent(KeyEvent.KEYCODE_DEL) } - ) { - Text("Del", modifier = Modifier.padding(4.dp)) - } - - RepeatableActionKey( - modifier = Modifier - .align(Alignment.TopCenter) - .width(keySize) - .height(keySize), - onTrigger = { sendEvent(KeyEvent.KEYCODE_MOVE_HOME) } - ) { - Text("Home", modifier = Modifier.padding(4.dp)) - } - - RepeatableActionKey( - modifier = Modifier - .align(Alignment.BottomCenter) - .width(keySize) - .height(keySize), - onTrigger = { sendEvent(KeyEvent.KEYCODE_MOVE_END) } - ) { - Text("End", modifier = Modifier.padding(4.dp)) - } - - RepeatableActionKey( - modifier = Modifier - .align(Alignment.TopEnd) - .width(keySize) - .height(keySize), - onTrigger = { sendEvent(KeyEvent.KEYCODE_PAGE_UP) } - ) { - Text("PgUp", modifier = Modifier.padding(4.dp)) - } - - RepeatableActionKey( - modifier = Modifier - .align(Alignment.BottomEnd) - .width(keySize) - .height(keySize), - onTrigger = { sendEvent(KeyEvent.KEYCODE_PAGE_DOWN) } - ) { - Text("PgDn", modifier = Modifier.padding(4.dp)) - } - } - - } - - Spacer(modifier = Modifier.height(32.dp)) - Row(modifier = Modifier.fillMaxWidth()) { - Box(modifier = Modifier.height(92.dp)) { - Button( - onClick = { shiftState = !shiftState }, - colors = if (shiftState) { - buttonColorsToggled - } else { - buttonColorsUntoggled - }, - modifier = Modifier.align(Alignment.TopStart).width(keySize * 3) - ) { - Row { - Text("Shift") - } - } - Button( - onClick = { ctrlState = !ctrlState }, - colors = if (ctrlState) { - buttonColorsToggled - } else { - buttonColorsUntoggled - }, - modifier = Modifier.align(Alignment.BottomStart).width(keySize * 2) - ) { - Text("Ctrl") - } - } - - Spacer(modifier = Modifier.weight(1.0f)) - - - - - Box( + .weight(3.0f) + .fillMaxWidth(), + sendEvent = sendEvent + ) + CtrlShiftMetaKeys( modifier = Modifier - .height(keySize * 2) - .width(keySize * 3) - ) { - RepeatableActionKey( - modifier = Modifier - .align(Alignment.TopCenter) - .width(keySize) - .height(keySize), - onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_UP) } - ) { - IconWithColor( - iconId = R.drawable.arrow_up, - iconColor = MaterialTheme.colorScheme.onPrimary - ) - } - - - RepeatableActionKey( - modifier = Modifier - .align(Alignment.BottomCenter) - .width(keySize) - .height(keySize), - onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_DOWN) } - ) { - IconWithColor( - iconId = R.drawable.arrow_down, - iconColor = MaterialTheme.colorScheme.onPrimary - ) - } - - RepeatableActionKey( - modifier = Modifier - .align(Alignment.BottomStart) - .width(keySize) - .height(keySize), - onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_LEFT) } - ) { - IconWithColor( - iconId = R.drawable.arrow_left, - iconColor = MaterialTheme.colorScheme.onPrimary - ) - } - - RepeatableActionKey( - modifier = Modifier - .align(Alignment.BottomEnd) - .width(keySize) - .height(keySize), - onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_RIGHT) } - ) { - IconWithColor( - iconId = R.drawable.arrow_right, - iconColor = MaterialTheme.colorScheme.onPrimary - ) - } - } + .weight(1.0f) + .fillMaxWidth(), + ctrlState = ctrlState, + shiftState = shiftState + ) } + SideKeys( + modifier = Modifier + .fillMaxHeight() + .weight(1.0f), + onEvent = onEvent, + onCodePoint = onCodePoint + ) } } @@ -307,4 +357,12 @@ val TextEditAction = Action( } } } -) \ No newline at end of file +) + +@Composable +@Preview(showBackground = true) +fun TextEditScreenPreview() { + Surface(modifier = Modifier.height(256.dp)) { + TextEditScreen(onCodePoint = { }, onEvent = { _, _ -> }) + } +} \ No newline at end of file From fd905bcabd27b3522aa831e3d5f76179ef796ad3 Mon Sep 17 00:00:00 2001 From: Aleksandras Kostarevas Date: Tue, 9 Jan 2024 14:26:54 +0200 Subject: [PATCH 08/10] Move UIX management to separate class --- .../org/futo/inputmethod/latin/LatinIME.kt | 382 +++--------------- .../futo/inputmethod/latin/uix/UixManager.kt | 332 +++++++++++++++ 2 files changed, 398 insertions(+), 316 deletions(-) create mode 100644 java/src/org/futo/inputmethod/latin/uix/UixManager.kt diff --git a/java/src/org/futo/inputmethod/latin/LatinIME.kt b/java/src/org/futo/inputmethod/latin/LatinIME.kt index 2f0310894..38e6e44a6 100644 --- a/java/src/org/futo/inputmethod/latin/LatinIME.kt +++ b/java/src/org/futo/inputmethod/latin/LatinIME.kt @@ -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 = hashMapOf() - private fun isActionWindowOpen(): Boolean { - return currWindowActionWindow != null - } - - private var inlineSuggestions: List> = 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) { diff --git a/java/src/org/futo/inputmethod/latin/uix/UixManager.kt b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt new file mode 100644 index 000000000..ecfbe354a --- /dev/null +++ b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt @@ -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 = hashMapOf() + + private var inlineSuggestions: List> = 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 + } +} \ No newline at end of file From 25e66ec4479c0a68f9163a1cbd3f0c0136a80ff1 Mon Sep 17 00:00:00 2001 From: Aleksandras Kostarevas Date: Tue, 9 Jan 2024 17:26:05 +0200 Subject: [PATCH 09/10] Add ability to expand keyboard during action --- java/res/drawable/chevron_down.xml | 13 ++ java/res/drawable/close.xml | 20 +++ .../org/futo/inputmethod/latin/LatinIME.kt | 22 +++- .../org/futo/inputmethod/latin/uix/Action.kt | 4 +- .../futo/inputmethod/latin/uix/ActionBar.kt | 115 ++++++++++++++++++ .../futo/inputmethod/latin/uix/UixManager.kt | 88 ++++++++------ .../latin/uix/actions/EmojiAction.kt | 76 ++++++------ .../latin/uix/actions/TextEditAction.kt | 47 ++++--- .../latin/uix/actions/ThemeAction.kt | 39 +----- .../latin/uix/actions/VoiceInputAction.kt | 2 +- 10 files changed, 292 insertions(+), 134 deletions(-) create mode 100644 java/res/drawable/chevron_down.xml create mode 100644 java/res/drawable/close.xml diff --git a/java/res/drawable/chevron_down.xml b/java/res/drawable/chevron_down.xml new file mode 100644 index 000000000..746f6c06d --- /dev/null +++ b/java/res/drawable/chevron_down.xml @@ -0,0 +1,13 @@ + + + diff --git a/java/res/drawable/close.xml b/java/res/drawable/close.xml new file mode 100644 index 000000000..991e1ca0d --- /dev/null +++ b/java/res/drawable/close.xml @@ -0,0 +1,20 @@ + + + + diff --git a/java/src/org/futo/inputmethod/latin/LatinIME.kt b/java/src/org/futo/inputmethod/latin/LatinIME.kt index 38e6e44a6..d4ce31926 100644 --- a/java/src/org/futo/inputmethod/latin/LatinIME.kt +++ b/java/src/org/futo/inputmethod/latin/LatinIME.kt @@ -15,6 +15,7 @@ import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.size import androidx.compose.material3.ColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.key import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds @@ -169,7 +170,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save updateDrawableProvider(newTheme.obtainColors(this)) deferSetSetting(THEME_KEY, newTheme.key) - if(!uixManager.isActionWindowOpen) { + if(!uixManager.isMainKeyboardHidden) { recreateKeyboard() } else { pendingRecreateKeyboard = true @@ -180,10 +181,10 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save // 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() - } + //if(pendingRecreateKeyboard) { + // pendingRecreateKeyboard = false + // recreateKeyboard() + //} } @@ -252,6 +253,17 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save // shown, but resized to 0 if an action window is open @Composable internal fun LegacyKeyboardView(hidden: Boolean) { + LaunchedEffect(hidden) { + if(hidden) { + latinIMELegacy.mKeyboardSwitcher.saveKeyboardState() + } else { + if(pendingRecreateKeyboard) { + pendingRecreateKeyboard = false + recreateKeyboard() + } + } + } + val modifier = if(hidden) { Modifier .clipToBounds() diff --git a/java/src/org/futo/inputmethod/latin/uix/Action.kt b/java/src/org/futo/inputmethod/latin/uix/Action.kt index 20b606963..c23244ed5 100644 --- a/java/src/org/futo/inputmethod/latin/uix/Action.kt +++ b/java/src/org/futo/inputmethod/latin/uix/Action.kt @@ -39,7 +39,7 @@ interface ActionWindow { fun windowName(): String @Composable - fun WindowContents() + fun WindowContents(keyboardShown: Boolean) fun close() } @@ -57,6 +57,8 @@ interface PersistentActionState { data class Action( @DrawableRes val icon: Int, @StringRes val name: Int, + val canShowKeyboard: Boolean = false, + val windowImpl: ((KeyboardManagerForAction, PersistentActionState?) -> ActionWindow)?, val simplePressImpl: ((KeyboardManagerForAction, PersistentActionState?) -> Unit)?, val persistentState: ((KeyboardManagerForAction) -> PersistentActionState)? = null, diff --git a/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt index b81164b3f..0b6abfc03 100644 --- a/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt +++ b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt @@ -21,6 +21,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme @@ -69,6 +70,7 @@ import org.futo.inputmethod.latin.uix.actions.ThemeAction import org.futo.inputmethod.latin.uix.actions.UndoAction import org.futo.inputmethod.latin.uix.actions.VoiceInputAction import org.futo.inputmethod.latin.uix.theme.DarkColorScheme +import org.futo.inputmethod.latin.uix.theme.Typography import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper import java.lang.Integer.min import kotlin.math.ceil @@ -413,6 +415,107 @@ fun ActionBar( } } +@Composable +fun ActionWindowBar( + windowName: String, + canExpand: Boolean, + onBack: () -> Unit, + onExpand: () -> Unit +) { + Surface( + modifier = Modifier + .fillMaxWidth() + .height(40.dp), color = MaterialTheme.colorScheme.background + ) + { + Row { + IconButton(onClick = onBack) { + Icon( + painter = painterResource(id = R.drawable.arrow_left_26), + contentDescription = "Back" + ) + } + + Text( + windowName, + style = Typography.titleMedium, + modifier = Modifier.align(CenterVertically) + ) + + Spacer(modifier = Modifier.weight(1.0f)) + + if(canExpand) { + IconButton(onClick = onExpand) { + Icon( + painter = painterResource(id = R.drawable.arrow_up), + contentDescription = "Show Keyboard" + ) + } + } + } + } +} + +@Composable +fun CollapsibleSuggestionsBar( + onClose: () -> Unit, + onCollapse: () -> Unit, + words: SuggestedWords?, + suggestionStripListener: SuggestionStripView.Listener, + inlineSuggestions: List>, +) { + Surface(modifier = Modifier + .fillMaxWidth() + .height(40.dp), color = MaterialTheme.colorScheme.background) + { + Row { + val color = MaterialTheme.colorScheme.primary + + IconButton( + onClick = onClose, + modifier = Modifier + .width(42.dp) + .fillMaxHeight() + .drawBehind { + drawCircle(color = color, radius = size.width / 3.0f + 1.0f) + }, + + colors = IconButtonDefaults.iconButtonColors(contentColor = MaterialTheme.colorScheme.onPrimary) + ) { + Icon( + painter = painterResource(id = R.drawable.close), + contentDescription = "Close" + ) + } + + if(inlineSuggestions.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + InlineSuggestions(inlineSuggestions) + } else if(words != null) { + SuggestionItems(words) { + suggestionStripListener.pickSuggestionManually( + words.getInfo(it) + ) + } + } else { + Spacer(modifier = Modifier.weight(1.0f)) + } + + IconButton( + onClick = onCollapse, + modifier = Modifier + .width(42.dp) + .fillMaxHeight(), + colors = IconButtonDefaults.iconButtonColors(contentColor = MaterialTheme.colorScheme.onBackground) + ) { + Icon( + painter = painterResource(id = R.drawable.arrow_down), + contentDescription = "Collapse" + ) + } + } + } +} + @@ -500,6 +603,18 @@ fun PreviewExpandedActionBar(colorScheme: ColorScheme = DarkColorScheme) { } } +@Composable +@Preview +fun PreviewCollapsibleBar(colorScheme: ColorScheme = DarkColorScheme) { + CollapsibleSuggestionsBar( + onCollapse = { }, + onClose = { }, + words = exampleSuggestedWords, + suggestionStripListener = ExampleListener(), + inlineSuggestions = listOf() + ) +} + @Composable @Preview diff --git a/java/src/org/futo/inputmethod/latin/uix/UixManager.kt b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt index ecfbe354a..1e8463c92 100644 --- a/java/src/org/futo/inputmethod/latin/uix/UixManager.kt +++ b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt @@ -7,36 +7,26 @@ 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( @@ -141,8 +131,11 @@ class UixManager(private val latinIME: LatinIME) { private var inlineSuggestions: List> = listOf() private val keyboardManagerForAction = UixActionKeyboardManager(this, latinIME) + private var mainKeyboardHidden = false + var currWindowActionWindow: ActionWindow? = null - val isActionWindowOpen get() = currWindowActionWindow != null + + val isMainKeyboardHidden get() = mainKeyboardHidden private fun onActionActivated(action: Action) { latinIME.inputLogic.finishInput() @@ -178,7 +171,7 @@ class UixManager(private val latinIME: LatinIME) { private fun enterActionWindowView(action: Action) { assert(action.windowImpl != null) - //latinIMELegacy.mKeyboardSwitcher.saveKeyboardState() + mainKeyboardHidden = true currWindowAction = action @@ -199,43 +192,62 @@ class UixManager(private val latinIME: LatinIME) { currWindowAction = null currWindowActionWindow = null + mainKeyboardHidden = false + latinIME.onKeyboardShown() setContent() } + private fun toggleExpandAction() { + mainKeyboardHidden = !mainKeyboardHidden + if(!mainKeyboardHidden) { + latinIME.onKeyboardShown() + } + + setContent() + } + @Composable private fun ActionViewWithHeader(windowImpl: ActionWindow) { + val heightDiv = if(mainKeyboardHidden) { + 1 + } else { + 1.5 + } 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) - ) - } + if(mainKeyboardHidden) { + ActionWindowBar( + onBack = { returnBackToMainKeyboardViewFromAction() }, + canExpand = currWindowAction!!.canShowKeyboard, + onExpand = { toggleExpandAction() }, + windowName = windowImpl.windowName() + ) } Box(modifier = Modifier .fillMaxWidth() - .height(with(LocalDensity.current) { latinIME.getInputViewHeight().toDp() }) + .height(with(LocalDensity.current) { + (latinIME.getInputViewHeight().toFloat() / heightDiv.toFloat()).toDp() + }) ) { - windowImpl.WindowContents() + windowImpl.WindowContents(keyboardShown = !isMainKeyboardHidden) + } + + if(!mainKeyboardHidden) { + val suggestedWordsOrNull = if (shouldShowSuggestionStrip) { + suggestedWords + } else { + null + } + + CollapsibleSuggestionsBar( + onCollapse = { toggleExpandAction() }, + onClose = { returnBackToMainKeyboardViewFromAction() }, + words = suggestedWordsOrNull, + suggestionStripListener = latinIME.latinIMELegacy as SuggestionStripView.Listener, + inlineSuggestions = inlineSuggestions + ) } } } @@ -250,14 +262,14 @@ class UixManager(private val latinIME: LatinIME) { }) { Column { when { - isActionWindowOpen -> ActionViewWithHeader( + currWindowActionWindow != null -> ActionViewWithHeader( currWindowActionWindow!! ) else -> MainKeyboardViewWithActionBar() } - latinIME.LegacyKeyboardView(hidden = isActionWindowOpen) + latinIME.LegacyKeyboardView(hidden = isMainKeyboardHidden) } } } diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt b/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt index 174a69295..ec1691ef1 100644 --- a/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt +++ b/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt @@ -162,7 +162,7 @@ data class BitmapRecycler( } @Composable -fun EmojiGrid(onClick: (EmojiItem) -> Unit, onExit: () -> Unit, onBackspace: () -> Unit, onSpace: () -> Unit, bitmaps: BitmapRecycler, emojis: List) { +fun EmojiGrid(onClick: (EmojiItem) -> Unit, onExit: () -> Unit, onBackspace: () -> Unit, onSpace: () -> Unit, bitmaps: BitmapRecycler, emojis: List, keyboardShown: Boolean) { val context = LocalContext.current val spToDp = context.resources.displayMetrics.scaledDensity / context.resources.displayMetrics.density @@ -182,41 +182,48 @@ fun EmojiGrid(onClick: (EmojiItem) -> Unit, onExit: () -> Unit, onBackspace: () } } } - Surface(color = MaterialTheme.colorScheme.background, modifier = Modifier - .fillMaxWidth() - .height(48.dp)) { - Row(modifier = Modifier.padding(2.dp, 8.dp, 2.dp, 0.dp)) { - IconButton(onClick = { onExit() }) { - Text("ABC", fontSize = 14.sp) - } - Button(onClick = { onSpace() }, modifier = Modifier - .weight(1.0f) - .padding(8.dp, 2.dp), colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.33f), - contentColor = MaterialTheme.colorScheme.onBackground, - disabledContainerColor = MaterialTheme.colorScheme.outline, - disabledContentColor = MaterialTheme.colorScheme.onBackground, - ), shape = RoundedCornerShape(32.dp)) { - Text("") - } + if(!keyboardShown) { + Surface( + color = MaterialTheme.colorScheme.background, modifier = Modifier + .fillMaxWidth() + .height(48.dp) + ) { + Row(modifier = Modifier.padding(2.dp, 8.dp, 2.dp, 0.dp)) { + IconButton(onClick = { onExit() }) { + Text("ABC", fontSize = 14.sp) + } - IconButton(onClick = { onBackspace() }) { - val icon = painterResource(id = R.drawable.delete) - val iconColor = MaterialTheme.colorScheme.onBackground + Button( + onClick = { onSpace() }, modifier = Modifier + .weight(1.0f) + .padding(8.dp, 2.dp), colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.33f), + contentColor = MaterialTheme.colorScheme.onBackground, + disabledContainerColor = MaterialTheme.colorScheme.outline, + disabledContentColor = MaterialTheme.colorScheme.onBackground, + ), shape = RoundedCornerShape(32.dp) + ) { + Text("") + } - Canvas(modifier = Modifier.fillMaxSize()) { - 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 = androidx.compose.ui.graphics.ColorFilter.tint( - iconColor + IconButton(onClick = { onBackspace() }) { + val icon = painterResource(id = R.drawable.delete) + val iconColor = MaterialTheme.colorScheme.onBackground + + Canvas(modifier = Modifier.fillMaxSize()) { + 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 = androidx.compose.ui.graphics.ColorFilter.tint( + iconColor + ) ) - ) + } } } } @@ -268,6 +275,7 @@ class PersistentEmojiState: PersistentActionState { val EmojiAction = Action( icon = R.drawable.smile, name = R.string.emoji_action_title, + canShowKeyboard = true, simplePressImpl = null, persistentState = { manager -> val state = PersistentEmojiState() @@ -287,7 +295,7 @@ val EmojiAction = Action( } @Composable - override fun WindowContents() { + override fun WindowContents(keyboardShown: Boolean) { state.emojis.value?.let { emojis -> EmojiGrid(onClick = { manager.typeText(it.emoji) @@ -297,7 +305,7 @@ val EmojiAction = Action( manager.sendCodePointEvent(Constants.CODE_SPACE) }, onBackspace = { manager.sendCodePointEvent(Constants.CODE_DELETE) - }, bitmaps = state.bitmaps, emojis = emojis) + }, bitmaps = state.bitmaps, emojis = emojis, keyboardShown = keyboardShown) } } diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/TextEditAction.kt b/java/src/org/futo/inputmethod/latin/uix/actions/TextEditAction.kt index a56637e51..2990bf2e6 100644 --- a/java/src/org/futo/inputmethod/latin/uix/actions/TextEditAction.kt +++ b/java/src/org/futo/inputmethod/latin/uix/actions/TextEditAction.kt @@ -218,7 +218,7 @@ fun CtrlShiftMetaKeys(modifier: Modifier, ctrlState: MutableState, shif } @Composable -fun SideKeys(modifier: Modifier, onEvent: (Int, Int) -> Unit, onCodePoint: (Int) -> Unit) { +fun SideKeys(modifier: Modifier, onEvent: (Int, Int) -> Unit, onCodePoint: (Int) -> Unit, keyboardShown: Boolean) { Column(modifier = modifier) { ActionKey( modifier = Modifier @@ -248,18 +248,20 @@ fun SideKeys(modifier: Modifier, onEvent: (Int, Int) -> Unit, onCodePoint: (Int) ) } - ActionKey( - modifier = Modifier - .weight(1.0f) - .fillMaxWidth(), - repeatable = true, - color = MaterialTheme.colorScheme.primaryContainer, - onTrigger = { onCodePoint(Constants.CODE_DELETE) } - ) { - IconWithColor( - iconId = R.drawable.delete, - iconColor = MaterialTheme.colorScheme.onPrimaryContainer - ) + if(!keyboardShown) { + ActionKey( + modifier = Modifier + .weight(1.0f) + .fillMaxWidth(), + repeatable = true, + color = MaterialTheme.colorScheme.primaryContainer, + onTrigger = { onCodePoint(Constants.CODE_DELETE) } + ) { + IconWithColor( + iconId = R.drawable.delete, + iconColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + } } @@ -298,7 +300,7 @@ fun SideKeys(modifier: Modifier, onEvent: (Int, Int) -> Unit, onCodePoint: (Int) } @Composable -fun TextEditScreen(onCodePoint: (Int) -> Unit, onEvent: (Int, Int) -> Unit) { +fun TextEditScreen(onCodePoint: (Int) -> Unit, onEvent: (Int, Int) -> Unit, keyboardShown: Boolean) { val shiftState = remember { mutableStateOf(false) } val ctrlState = remember { mutableStateOf(false) } @@ -331,7 +333,8 @@ fun TextEditScreen(onCodePoint: (Int) -> Unit, onEvent: (Int, Int) -> Unit) { .fillMaxHeight() .weight(1.0f), onEvent = onEvent, - onCodePoint = onCodePoint + onCodePoint = onCodePoint, + keyboardShown = keyboardShown ) } } @@ -341,6 +344,7 @@ val TextEditAction = Action( name = R.string.text_edit_action_title, simplePressImpl = null, persistentState = null, + canShowKeyboard = true, windowImpl = { manager, persistentState -> object : ActionWindow { @Composable @@ -349,8 +353,8 @@ val TextEditAction = Action( } @Composable - override fun WindowContents() { - TextEditScreen(onCodePoint = { a -> manager.sendCodePointEvent(a)}, onEvent = { a, b -> manager.sendKeyEvent(a, b) }) + override fun WindowContents(keyboardShown: Boolean) { + TextEditScreen(onCodePoint = { a -> manager.sendCodePointEvent(a)}, onEvent = { a, b -> manager.sendKeyEvent(a, b) }, keyboardShown = keyboardShown) } override fun close() { @@ -363,6 +367,13 @@ val TextEditAction = Action( @Preview(showBackground = true) fun TextEditScreenPreview() { Surface(modifier = Modifier.height(256.dp)) { - TextEditScreen(onCodePoint = { }, onEvent = { _, _ -> }) + TextEditScreen(onCodePoint = { }, onEvent = { _, _ -> }, keyboardShown = false) + } +} +@Composable +@Preview(showBackground = true) +fun TextEditScreenPreviewWithKb() { + Surface(modifier = Modifier.height(256.dp)) { + TextEditScreen(onCodePoint = { }, onEvent = { _, _ -> }, keyboardShown = true) } } \ No newline at end of file diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/ThemeAction.kt b/java/src/org/futo/inputmethod/latin/uix/actions/ThemeAction.kt index 63577e24a..b25bf7303 100644 --- a/java/src/org/futo/inputmethod/latin/uix/actions/ThemeAction.kt +++ b/java/src/org/futo/inputmethod/latin/uix/actions/ThemeAction.kt @@ -1,27 +1,17 @@ package org.futo.inputmethod.latin.uix.actions -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.Button -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import org.futo.inputmethod.latin.R import org.futo.inputmethod.latin.uix.Action import org.futo.inputmethod.latin.uix.ActionWindow -import org.futo.inputmethod.latin.uix.KeyboardManagerForAction -import org.futo.inputmethod.latin.uix.theme.ThemeOptionKeys -import org.futo.inputmethod.latin.uix.theme.ThemeOptions import org.futo.inputmethod.latin.uix.theme.selector.ThemePicker val ThemeAction = Action( icon = R.drawable.eye, name = R.string.theme_switcher_action_title, simplePressImpl = null, + canShowKeyboard = true, windowImpl = { manager, _ -> object : ActionWindow { @Composable @@ -30,33 +20,8 @@ val ThemeAction = Action( } @Composable - override fun WindowContents() { - val context = LocalContext.current - + override fun WindowContents(keyboardShown: Boolean) { ThemePicker { manager.updateTheme(it) } - /* - LazyColumn( - modifier = Modifier - .padding(8.dp, 0.dp) - .fillMaxWidth() - ) - { - items(ThemeOptionKeys.count()) { - val key = ThemeOptionKeys[it] - val themeOption = ThemeOptions[key] - if (themeOption != null && themeOption.available(context)) { - Button(onClick = { - manager.updateTheme( - themeOption - ) - }) { - Text(stringResource(themeOption.name)) - } - } - } - } - - */ } override fun close() { diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt b/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt index d4ee4504f..4cc1e574c 100644 --- a/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt +++ b/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt @@ -180,7 +180,7 @@ private class VoiceInputActionWindow( } @Composable - override fun WindowContents() { + override fun WindowContents(keyboardShown: Boolean) { Box(modifier = Modifier .fillMaxSize() .clickable(enabled = true, From 55d5959f54b8d380276236f42facd5f8530b21ac Mon Sep 17 00:00:00 2001 From: Aleksandras Kostarevas Date: Tue, 9 Jan 2024 18:25:14 +0200 Subject: [PATCH 10/10] Skip non-alphabetic characters during mixing --- ...g_futo_inputmethod_latin_xlm_LanguageModel.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/native/jni/org_futo_inputmethod_latin_xlm_LanguageModel.cpp b/native/jni/org_futo_inputmethod_latin_xlm_LanguageModel.cpp index f2a2f4e94..a844ae0ee 100644 --- a/native/jni/org_futo_inputmethod_latin_xlm_LanguageModel.cpp +++ b/native/jni/org_futo_inputmethod_latin_xlm_LanguageModel.cpp @@ -651,6 +651,9 @@ namespace latinime { std::vector mixes; for(int i=0; i= 'a' && wc <= 'z') && !(wc >= 'A' && wc <= 'Z')) continue; + std::vector proportions = pInfo->decomposeTapPosition(xCoordinates[i], yCoordinates[i]); for(float &f : proportions) { if(f < 0.05f) f = 0.0f; @@ -701,12 +704,12 @@ namespace latinime { results.x = ((float)xCoordinates[i]) / ((float)pInfo->getKeyboardWidth()); results.y = ((float)yCoordinates[i]) / ((float)pInfo->getKeyboardHeight()); - AKLOGI("%d | Char %c, pos %.6f %.6f, nearest is %c at %.2f, then %c at %.2f, finally %c at %.2f", i, partialWordString[i], - results.x, results.y, - (char)(pInfo->getKeyCodePoint(index_value[0].second)), (float)(index_value[0].first), - (char)(pInfo->getKeyCodePoint(index_value[1].second)), (float)(index_value[1].first), - (char)(pInfo->getKeyCodePoint(index_value[2].second)), (float)(index_value[2].first) - ); + //AKLOGI("%d | Char %c, pos %.6f %.6f, nearest is %c at %.2f, then %c at %.2f, finally %c at %.2f", i, partialWordString[i], + // results.x, results.y, + // (char)(pInfo->getKeyCodePoint(index_value[0].second)), (float)(index_value[0].first), + // (char)(pInfo->getKeyCodePoint(index_value[1].second)), (float)(index_value[1].first), + // (char)(pInfo->getKeyCodePoint(index_value[2].second)), (float)(index_value[2].first) + // ); for(int j=0; j