mirror of
https://gitlab.futo.org/keyboard/latinime.git
synced 2024-09-28 14:54:30 +01:00
Merge branch 'lm-2-finetuning-whisperggml' into 'lm-2-finetuning'
Add additional actions and cursor sliding See merge request alex/latinime!5
This commit is contained in:
commit
05f5f9fe8f
20
java/res/drawable/arrow_down.xml
Normal file
20
java/res/drawable/arrow_down.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,5L12,19"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M19,12l-7,7l-7,-7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
@ -1,20 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="26dp"
|
||||
android:height="26dp"
|
||||
android:viewportWidth="26"
|
||||
android:viewportHeight="26">
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M20,13L6,13"
|
||||
android:pathData="M19,12L5,12"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M13,20L6,13L13,6"
|
||||
android:pathData="M12,19l-7,-7l7,-7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
|
20
java/res/drawable/arrow_left_26.xml
Normal file
20
java/res/drawable/arrow_left_26.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="26dp"
|
||||
android:height="26dp"
|
||||
android:viewportWidth="26"
|
||||
android:viewportHeight="26">
|
||||
<path
|
||||
android:pathData="M20,13L6,13"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M13,20L6,13L13,6"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
20
java/res/drawable/arrow_right.xml
Normal file
20
java/res/drawable/arrow_right.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M5,12L19,12"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M12,5l7,7l-7,7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
20
java/res/drawable/arrow_up.xml
Normal file
20
java/res/drawable/arrow_up.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,19L12,5"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M5,12l7,-7l7,7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
13
java/res/drawable/chevron_down.xml
Normal file
13
java/res/drawable/chevron_down.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M6,9l6,6l6,-6"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
20
java/res/drawable/clipboard.xml
Normal file
20
java/res/drawable/clipboard.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M16,4h2a2,2 0,0 1,2 2v14a2,2 0,0 1,-2 2H6a2,2 0,0 1,-2 -2V6a2,2 0,0 1,2 -2h2"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M9,2L15,2A1,1 0,0 1,16 3L16,5A1,1 0,0 1,15 6L9,6A1,1 0,0 1,8 5L8,3A1,1 0,0 1,9 2z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
20
java/res/drawable/close.xml
Normal file
20
java/res/drawable/close.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M18,6L6,18"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M6,6L18,18"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
20
java/res/drawable/copy.xml
Normal file
20
java/res/drawable/copy.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M11,9L20,9A2,2 0,0 1,22 11L22,20A2,2 0,0 1,20 22L11,22A2,2 0,0 1,9 20L9,11A2,2 0,0 1,11 9z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M5,15H4a2,2 0,0 1,-2 -2V4a2,2 0,0 1,2 -2h9a2,2 0,0 1,2 2v1"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
27
java/res/drawable/ctrl.xml
Normal file
27
java/res/drawable/ctrl.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<vector android:height="24dp" android:viewportHeight="26"
|
||||
android:viewportWidth="26" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M1.995,15.995L23.995,15.995"
|
||||
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M24.005,11.995L24.005,15.995"
|
||||
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M5.995,10.995a1,1 0,1 0,2 0a1,1 0,1 0,-2 0z"
|
||||
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M11.995,10.995a1,1 0,1 0,2 0a1,1 0,1 0,-2 0z"
|
||||
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M2.005,11.995L2.005,15.995"
|
||||
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M17.995,10.995a1,1 0,1 0,2 0a1,1 0,1 0,-2 0z"
|
||||
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
</vector>
|
41
java/res/drawable/cut.xml
Normal file
41
java/res/drawable/cut.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M6,6m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M6,18m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M20,4L8.12,15.88"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M14.47,14.48L20,20"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M8.12,8.12L12,12"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
20
java/res/drawable/edit_text.xml
Normal file
20
java/res/drawable/edit_text.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M11,4H4a2,2 0,0 0,-2 2v14a2,2 0,0 0,2 2h14a2,2 0,0 0,2 -2v-7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M18.5,2.5a2.121,2.121 0,0 1,3 3L12,15l-4,1 1,-4 9.5,-9.5z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
20
java/res/drawable/redo.xml
Normal file
20
java/res/drawable/redo.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M15,14l5,-5l-5,-5"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M4,20v-7a4,4 0,0 1,4 -4h12"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
20
java/res/drawable/undo.xml
Normal file
20
java/res/drawable/undo.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M9,14l-5,-5l5,-5"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M20,20v-7a4,4 0,0 0,-4 -4H4"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
@ -421,6 +421,7 @@
|
||||
<attr name="keyTextShadowColor" format="color" />
|
||||
<!-- Color to use for the label in a key when in inactivated state. -->
|
||||
<attr name="keyTextInactivatedColor" format="color" />
|
||||
<attr name="keyPressedTextColor" format="color" />
|
||||
<!-- Color to use for the label in a key that has followFunctionalTextColor keyLabelFlags. -->
|
||||
<attr name="functionalTextColor" format="color" />
|
||||
<!-- Key hint letter (= one character hint label) color -->
|
||||
|
@ -2,6 +2,11 @@
|
||||
<resources>
|
||||
<string name="voice_input_action_title">Voice Input</string>
|
||||
<string name="theme_switcher_action_title">Theme Switcher</string>
|
||||
<string name="emoji_action_title">Emojis</string>
|
||||
<string name="clipboard_action_title">Paste from Clipboard</string>
|
||||
<string name="undo_action_title">Undo</string>
|
||||
<string name="redo_action_title">Redo</string>
|
||||
<string name="text_edit_action_title">Text Editor</string>
|
||||
|
||||
<string name="amoled_dark_theme_name">AMOLED Dark Purple</string>
|
||||
<string name="classic_material_dark_theme_name">AOSP Material Dark</string>
|
||||
|
@ -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 -->
|
||||
<!-- This resource is corresponding to msgid="5433275485499039199" -->
|
||||
<string name="user_dict_fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
|
||||
<string name="title_emojis">Emojis</string>
|
||||
</resources>
|
||||
|
@ -48,6 +48,7 @@
|
||||
<item name="spacebarBackground">@drawable/btn_keyboard_spacebar_lxx_dark</item>
|
||||
<item name="keyTextColor">@color/key_text_color_lxx_dark</item>
|
||||
<item name="keyTextInactivatedColor">@color/key_functional_text_color_lxx_dark</item>
|
||||
<item name="keyPressedTextColor">@color/key_text_color_lxx_dark</item>
|
||||
<item name="functionalTextColor">@color/key_text_color_lxx_dark</item>
|
||||
<item name="keyHintLetterColor">@color/key_hint_letter_color_lxx_dark</item>
|
||||
<item name="keyHintLabelColor">@color/key_text_inactive_color_lxx_dark</item>
|
||||
|
@ -48,6 +48,7 @@
|
||||
<item name="spacebarBackground">@drawable/btn_keyboard_spacebar_lxx_light</item>
|
||||
<item name="keyTextColor">@color/key_text_color_lxx_light</item>
|
||||
<item name="keyTextInactivatedColor">@color/key_text_inactive_color_lxx_light</item>
|
||||
<item name="keyPressedTextColor">@color/key_text_color_lxx_light</item>
|
||||
<item name="functionalTextColor">@color/key_functional_text_color_lxx_light</item>
|
||||
<item name="keyHintLetterColor">@color/key_hint_letter_color_lxx_light</item>
|
||||
<item name="keyHintLabelColor">@color/key_text_inactive_color_lxx_light</item>
|
||||
|
@ -23,16 +23,26 @@
|
||||
>
|
||||
<Key
|
||||
latin:keySpec="a"
|
||||
latin:keyHintLabel="\@"
|
||||
latin:additionalMoreKeys="\\@"
|
||||
latin:moreKeys="!text/morekeys_a" />
|
||||
<Key
|
||||
latin:keySpec="s"
|
||||
latin:keyHintLabel="#"
|
||||
latin:additionalMoreKeys="#"
|
||||
latin:moreKeys="!text/morekeys_s" />
|
||||
<Key
|
||||
latin:keySpec="d"
|
||||
latin:keyHintLabel="$"
|
||||
latin:additionalMoreKeys="$,€,£,¥,¢"
|
||||
latin:moreKeys="!text/morekeys_d" />
|
||||
<Key
|
||||
latin:keySpec="f" />
|
||||
latin:keySpec="f"
|
||||
latin:keyHintLabel="%"
|
||||
latin:additionalMoreKeys="%" />
|
||||
<Key
|
||||
latin:keySpec="g"
|
||||
latin:keyHintLabel="&"
|
||||
latin:additionalMoreKeys="&,|"
|
||||
latin:moreKeys="!text/morekeys_g" />
|
||||
</merge>
|
||||
|
@ -23,14 +23,22 @@
|
||||
>
|
||||
<Key
|
||||
latin:keySpec="h"
|
||||
latin:keyHintLabel="-"
|
||||
latin:additionalMoreKeys="-,–,—,_"
|
||||
latin:moreKeys="!text/morekeys_h" />
|
||||
<Key
|
||||
latin:keySpec="j"
|
||||
latin:keyHintLabel="+"
|
||||
latin:additionalMoreKeys="+,="
|
||||
latin:moreKeys="!text/morekeys_j" />
|
||||
<Key
|
||||
latin:keySpec="k"
|
||||
latin:keyHintLabel="("
|
||||
latin:additionalMoreKeys="(,[,{,<"
|
||||
latin:moreKeys="!text/morekeys_k" />
|
||||
<Key
|
||||
latin:keySpec="l"
|
||||
latin:keyHintLabel=")"
|
||||
latin:additionalMoreKeys="),],},>"
|
||||
latin:moreKeys="!text/morekeys_l" />
|
||||
</merge>
|
||||
|
@ -23,14 +23,22 @@
|
||||
>
|
||||
<Key
|
||||
latin:keySpec="z"
|
||||
latin:keyHintLabel="*"
|
||||
latin:additionalMoreKeys="*"
|
||||
latin:moreKeys="!text/morekeys_z" />
|
||||
<Key
|
||||
latin:keySpec="!text/keyspec_x"
|
||||
latin:keyHintLabel="""
|
||||
latin:additionalMoreKeys="""
|
||||
latin:moreKeys="!text/morekeys_x" />
|
||||
<Key
|
||||
latin:keySpec="c"
|
||||
latin:keyHintLabel="'"
|
||||
latin:additionalMoreKeys="'"
|
||||
latin:moreKeys="!text/morekeys_c" />
|
||||
<Key
|
||||
latin:keySpec="v"
|
||||
latin:keyHintLabel=":"
|
||||
latin:additionalMoreKeys=":"
|
||||
latin:moreKeys="!text/morekeys_v" />
|
||||
</merge>
|
||||
|
@ -22,10 +22,16 @@
|
||||
xmlns:latin="http://schemas.android.com/apk/res/org.futo.inputmethod.latin"
|
||||
>
|
||||
<Key
|
||||
latin:keySpec="b" />
|
||||
latin:keySpec="b"
|
||||
latin:keyHintLabel=";"
|
||||
latin:additionalMoreKeys=";" />
|
||||
<Key
|
||||
latin:keySpec="n"
|
||||
latin:keyHintLabel="!"
|
||||
latin:additionalMoreKeys="!"
|
||||
latin:moreKeys="!text/morekeys_n" />
|
||||
<Key
|
||||
latin:keySpec="m" />
|
||||
latin:keySpec="m"
|
||||
latin:keyHintLabel="?"
|
||||
latin:additionalMoreKeys="?,/" />
|
||||
</merge>
|
||||
|
@ -665,6 +665,10 @@ public class Key implements Comparable<Key> {
|
||||
if ((mLabelFlags & LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR) != 0) {
|
||||
return params.mFunctionalTextColor;
|
||||
}
|
||||
if (mPressed) {
|
||||
return params.mPressedTextColor;
|
||||
}
|
||||
|
||||
return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor;
|
||||
}
|
||||
|
||||
|
@ -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() {}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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(
|
||||
|
@ -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 */ ".",
|
||||
|
@ -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,17 @@ 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.LaunchedEffect
|
||||
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,34 +38,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.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)
|
||||
|
||||
@ -101,7 +73,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)
|
||||
@ -114,14 +86,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,
|
||||
@ -130,21 +102,18 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
lifecycleScope
|
||||
)
|
||||
|
||||
val uixManager = UixManager(this)
|
||||
|
||||
private var activeThemeOption: ThemeOption? = null
|
||||
private var activeColorScheme = DarkColorScheme
|
||||
private var colorSchemeLoaderJob: Job? = null
|
||||
private var pendingRecreateKeyboard: Boolean = false
|
||||
|
||||
val themeOption get() = activeThemeOption
|
||||
val colorScheme get() = activeColorScheme
|
||||
|
||||
private var drawableProvider: DynamicThemeProvider? = null
|
||||
|
||||
private var currWindowAction: Action? = null
|
||||
private var currWindowActionWindow: ActionWindow? = null
|
||||
private var persistentStates: HashMap<Action, PersistentActionState?> = hashMapOf()
|
||||
private fun isActionWindowOpen(): Boolean {
|
||||
return currWindowActionWindow != null
|
||||
}
|
||||
|
||||
private var inlineSuggestions: List<MutableState<View?>> = listOf()
|
||||
|
||||
private var lastEditorInfo: EditorInfo? = null
|
||||
|
||||
// TODO: Calling this repeatedly as the theme changes tends to slow everything to a crawl
|
||||
@ -158,7 +127,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
drawableProvider = BasicThemeProvider(this, overrideColorScheme = colorScheme)
|
||||
|
||||
window.window?.navigationBarColor = drawableProvider!!.primaryKeyboardColor
|
||||
setContent()
|
||||
uixManager.onColorSchemeChanged()
|
||||
}
|
||||
|
||||
override fun getDrawableProvider(): DynamicThemeProvider {
|
||||
@ -193,6 +162,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.isMainKeyboardHidden) {
|
||||
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()
|
||||
|
||||
@ -241,41 +236,38 @@ 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) {
|
||||
LaunchedEffect(hidden) {
|
||||
if(hidden) {
|
||||
latinIMELegacy.mKeyboardSwitcher.saveKeyboardState()
|
||||
} else {
|
||||
if(pendingRecreateKeyboard) {
|
||||
pendingRecreateKeyboard = false
|
||||
recreateKeyboard()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val modifier = if(hidden) {
|
||||
Modifier.clipToBounds().size(0.dp)
|
||||
Modifier
|
||||
.clipToBounds()
|
||||
.size(0.dp)
|
||||
} else {
|
||||
Modifier.onSizeChanged {
|
||||
inputViewHeight = it.height
|
||||
@ -288,127 +280,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),
|
||||
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)
|
||||
@ -417,8 +295,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)
|
||||
@ -443,15 +321,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()
|
||||
}
|
||||
|
||||
@ -471,7 +348,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
super.onWindowHidden()
|
||||
latinIMELegacy.onWindowHidden()
|
||||
|
||||
closeActionWindow()
|
||||
uixManager.onInputFinishing()
|
||||
}
|
||||
|
||||
override fun onUpdateSelection(
|
||||
@ -521,12 +398,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.
|
||||
@ -541,7 +420,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 {
|
||||
@ -581,124 +460,25 @@ 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()
|
||||
}
|
||||
|
||||
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.mInputLogic.mConnection.commitText(v, 1)
|
||||
}
|
||||
|
||||
override fun backspace(amount: Int) {
|
||||
latinIMELegacy.mInputLogic.mConnection.deleteTextBeforeCursor(amount)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
uixManager.cleanUpPersistentStates()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@ -708,12 +488,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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -29,6 +29,9 @@ interface KeyboardManagerForAction {
|
||||
fun triggerSystemVoiceInput()
|
||||
|
||||
fun updateTheme(newTheme: ThemeOption)
|
||||
|
||||
fun sendCodePointEvent(codePoint: Int)
|
||||
fun sendKeyEvent(keyCode: Int, metaState: Int)
|
||||
}
|
||||
|
||||
interface ActionWindow {
|
||||
@ -36,7 +39,7 @@ interface ActionWindow {
|
||||
fun windowName(): String
|
||||
|
||||
@Composable
|
||||
fun WindowContents()
|
||||
fun WindowContents(keyboardShown: Boolean)
|
||||
|
||||
fun close()
|
||||
}
|
||||
@ -54,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,
|
||||
|
@ -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
|
||||
@ -20,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
|
||||
@ -60,10 +62,15 @@ 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.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
|
||||
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
|
||||
@ -287,7 +294,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 +324,10 @@ 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)
|
||||
ActionItem(TextEditAction, onSelect)
|
||||
}
|
||||
|
||||
|
||||
@ -386,7 +391,11 @@ fun ActionBar(
|
||||
ExpandActionsButton(isActionsOpen.value) { isActionsOpen.value = !isActionsOpen.value }
|
||||
|
||||
if(isActionsOpen.value) {
|
||||
LazyRow {
|
||||
item {
|
||||
ActionItems(onActionActivated)
|
||||
}
|
||||
}
|
||||
} else if(inlineSuggestions.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
InlineSuggestions(inlineSuggestions)
|
||||
} else if(words != null) {
|
||||
@ -399,10 +408,113 @@ fun ActionBar(
|
||||
Spacer(modifier = Modifier.weight(1.0f))
|
||||
}
|
||||
|
||||
if(!isActionsOpen.value) {
|
||||
ActionItemSmall(VoiceInputAction, onActionActivated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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<MutableState<View?>>,
|
||||
) {
|
||||
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -491,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
|
||||
|
@ -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))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ interface DynamicThemeProvider {
|
||||
|
||||
val keyFeedback: Drawable
|
||||
|
||||
val moreKeysTextColor: Int
|
||||
val moreKeysKeyboardBackground: Drawable
|
||||
val popupKey: Drawable
|
||||
|
||||
|
344
java/src/org/futo/inputmethod/latin/uix/UixManager.kt
Normal file
344
java/src/org/futo/inputmethod/latin/uix/UixManager.kt
Normal file
@ -0,0 +1,344 @@
|
||||
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.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
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.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.futo.inputmethod.latin.LatinIME
|
||||
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.UixThemeWrapper
|
||||
|
||||
private class LatinIMEActionInputTransaction(
|
||||
private val inputLogic: InputLogic,
|
||||
shouldApplySpace: Boolean
|
||||
): ActionInputTransaction {
|
||||
private val isSpaceNecessary: Boolean
|
||||
init {
|
||||
val priorText = inputLogic.mConnection.getTextBeforeCursor(1, 0)
|
||||
isSpaceNecessary = shouldApplySpace && !priorText.isNullOrEmpty() && !priorText.last().isWhitespace()
|
||||
}
|
||||
|
||||
private fun transformText(text: String): String {
|
||||
return if(isSpaceNecessary) { " $text" } else { text }
|
||||
}
|
||||
|
||||
override fun updatePartial(text: String) {
|
||||
inputLogic.mConnection.setComposingText(
|
||||
transformText(text),
|
||||
1
|
||||
)
|
||||
}
|
||||
|
||||
override fun commit(text: String) {
|
||||
inputLogic.mConnection.commitText(
|
||||
transformText(text),
|
||||
1
|
||||
)
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
inputLogic.mConnection.finishComposingText()
|
||||
}
|
||||
}
|
||||
|
||||
class UixActionKeyboardManager(val uixManager: UixManager, val latinIME: LatinIME) : KeyboardManagerForAction {
|
||||
override fun getContext(): Context {
|
||||
return latinIME
|
||||
}
|
||||
|
||||
override fun getLifecycleScope(): LifecycleCoroutineScope {
|
||||
return latinIME.lifecycleScope
|
||||
}
|
||||
|
||||
override fun triggerContentUpdate() {
|
||||
uixManager.setContent()
|
||||
}
|
||||
|
||||
override fun createInputTransaction(applySpaceIfNeeded: Boolean): ActionInputTransaction {
|
||||
return LatinIMEActionInputTransaction(latinIME.inputLogic, applySpaceIfNeeded)
|
||||
}
|
||||
|
||||
override fun typeText(v: String) {
|
||||
latinIME.latinIMELegacy.onTextInput(v)
|
||||
}
|
||||
|
||||
override fun backspace(amount: Int) {
|
||||
latinIME.latinIMELegacy.onCodeInput(
|
||||
Constants.CODE_DELETE,
|
||||
Constants.NOT_A_COORDINATE,
|
||||
Constants.NOT_A_COORDINATE, false)
|
||||
}
|
||||
|
||||
override fun closeActionWindow() {
|
||||
if(uixManager.currWindowActionWindow == null) return
|
||||
uixManager.returnBackToMainKeyboardViewFromAction()
|
||||
}
|
||||
|
||||
override fun triggerSystemVoiceInput() {
|
||||
latinIME.latinIMELegacy.onCodeInput(
|
||||
Constants.CODE_SHORTCUT,
|
||||
Constants.SUGGESTION_STRIP_COORDINATE,
|
||||
Constants.SUGGESTION_STRIP_COORDINATE,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
override fun updateTheme(newTheme: ThemeOption) {
|
||||
latinIME.updateTheme(newTheme)
|
||||
}
|
||||
|
||||
override fun sendCodePointEvent(codePoint: Int) {
|
||||
latinIME.latinIMELegacy.onCodeInput(codePoint,
|
||||
Constants.NOT_A_COORDINATE,
|
||||
Constants.NOT_A_COORDINATE, false)
|
||||
}
|
||||
|
||||
override fun sendKeyEvent(keyCode: Int, metaState: Int) {
|
||||
latinIME.inputLogic.sendDownUpKeyEvent(keyCode, metaState)
|
||||
}
|
||||
}
|
||||
|
||||
class UixManager(private val latinIME: LatinIME) {
|
||||
private var shouldShowSuggestionStrip: Boolean = true
|
||||
private var suggestedWords: SuggestedWords? = null
|
||||
|
||||
private var composeView: ComposeView? = null
|
||||
|
||||
private var currWindowAction: Action? = null
|
||||
private var persistentStates: HashMap<Action, PersistentActionState?> = hashMapOf()
|
||||
|
||||
private var inlineSuggestions: List<MutableState<View?>> = listOf()
|
||||
private val keyboardManagerForAction = UixActionKeyboardManager(this, latinIME)
|
||||
|
||||
private var mainKeyboardHidden = false
|
||||
|
||||
var currWindowActionWindow: ActionWindow? = null
|
||||
|
||||
val isMainKeyboardHidden get() = mainKeyboardHidden
|
||||
|
||||
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)
|
||||
|
||||
mainKeyboardHidden = true
|
||||
|
||||
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
|
||||
|
||||
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 {
|
||||
if(mainKeyboardHidden) {
|
||||
ActionWindowBar(
|
||||
onBack = { returnBackToMainKeyboardViewFromAction() },
|
||||
canExpand = currWindowAction!!.canShowKeyboard,
|
||||
onExpand = { toggleExpandAction() },
|
||||
windowName = windowImpl.windowName()
|
||||
)
|
||||
}
|
||||
|
||||
Box(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(with(LocalDensity.current) {
|
||||
(latinIME.getInputViewHeight().toFloat() / heightDiv.toFloat()).toDp()
|
||||
})
|
||||
) {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setContent() {
|
||||
composeView?.setContent {
|
||||
UixThemeWrapper(latinIME.colorScheme) {
|
||||
Column {
|
||||
Spacer(modifier = Modifier.weight(1.0f))
|
||||
Surface(modifier = Modifier.onSizeChanged {
|
||||
latinIME.updateTouchableHeight(it.height)
|
||||
}) {
|
||||
Column {
|
||||
when {
|
||||
currWindowActionWindow != null -> ActionViewWithHeader(
|
||||
currWindowActionWindow!!
|
||||
)
|
||||
|
||||
else -> MainKeyboardViewWithActionBar()
|
||||
}
|
||||
|
||||
latinIME.LegacyKeyboardView(hidden = isMainKeyboardHidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
@ -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,
|
||||
)
|
@ -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
|
||||
@ -161,7 +162,7 @@ data class BitmapRecycler(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EmojiGrid(onClick: (EmojiItem) -> Unit, onExit: () -> Unit, onBackspace: () -> Unit, onSpace: () -> Unit, bitmaps: BitmapRecycler, emojis: List<EmojiItem>) {
|
||||
fun EmojiGrid(onClick: (EmojiItem) -> Unit, onExit: () -> Unit, onBackspace: () -> Unit, onSpace: () -> Unit, bitmaps: BitmapRecycler, emojis: List<EmojiItem>, keyboardShown: Boolean) {
|
||||
val context = LocalContext.current
|
||||
val spToDp = context.resources.displayMetrics.scaledDensity / context.resources.displayMetrics.density
|
||||
|
||||
@ -181,22 +182,28 @@ fun EmojiGrid(onClick: (EmojiItem) -> Unit, onExit: () -> Unit, onBackspace: ()
|
||||
}
|
||||
}
|
||||
}
|
||||
Surface(color = MaterialTheme.colorScheme.background, modifier = Modifier
|
||||
|
||||
if(!keyboardShown) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.background, modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(48.dp)) {
|
||||
.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
|
||||
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)) {
|
||||
), shape = RoundedCornerShape(32.dp)
|
||||
) {
|
||||
Text("")
|
||||
}
|
||||
|
||||
@ -224,6 +231,7 @@ fun EmojiGrid(onClick: (EmojiItem) -> Unit, onExit: () -> Unit, onBackspace: ()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@Preview(showBackground = true)
|
||||
@ -266,7 +274,8 @@ class PersistentEmojiState: PersistentActionState {
|
||||
|
||||
val EmojiAction = Action(
|
||||
icon = R.drawable.smile,
|
||||
name = R.string.title_emojis,
|
||||
name = R.string.emoji_action_title,
|
||||
canShowKeyboard = true,
|
||||
simplePressImpl = null,
|
||||
persistentState = { manager ->
|
||||
val state = PersistentEmojiState()
|
||||
@ -282,21 +291,21 @@ val EmojiAction = Action(
|
||||
object : ActionWindow {
|
||||
@Composable
|
||||
override fun windowName(): String {
|
||||
return stringResource(R.string.title_emojis)
|
||||
return stringResource(R.string.emoji_action_title)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun WindowContents() {
|
||||
override fun WindowContents(keyboardShown: Boolean) {
|
||||
state.emojis.value?.let { emojis ->
|
||||
EmojiGrid(onClick = {
|
||||
manager.typeText(it.emoji)
|
||||
}, onExit = {
|
||||
manager.closeActionWindow()
|
||||
}, onSpace = {
|
||||
manager.typeText(" ")
|
||||
manager.sendCodePointEvent(Constants.CODE_SPACE)
|
||||
}, onBackspace = {
|
||||
manager.backspace(1)
|
||||
}, bitmaps = state.bitmaps, emojis = emojis)
|
||||
manager.sendCodePointEvent(Constants.CODE_DELETE)
|
||||
}, bitmaps = state.bitmaps, emojis = emojis, keyboardShown = keyboardShown)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,379 @@
|
||||
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.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
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.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.common.Constants
|
||||
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 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() }
|
||||
val isPressed by interactionSource.collectIsPressedAsState()
|
||||
|
||||
LaunchedEffect(isPressed) {
|
||||
if(isPressed) {
|
||||
onTrigger()
|
||||
if(repeatable) {
|
||||
delay(670L)
|
||||
while (isPressed) {
|
||||
onTrigger()
|
||||
delay(50L)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.padding(4.dp)
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = LocalIndication.current,
|
||||
onClick = { }
|
||||
),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = color
|
||||
) {
|
||||
contents()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
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<Boolean>, shiftState: MutableState<Boolean>) {
|
||||
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, keyboardShown: Boolean) {
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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, keyboardShown: Boolean) {
|
||||
val shiftState = remember { mutableStateOf(false) }
|
||||
val ctrlState = remember { mutableStateOf(false) }
|
||||
|
||||
val metaState = 0 or
|
||||
(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) }
|
||||
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
Column(modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(3.0f)) {
|
||||
ArrowKeys(
|
||||
modifier = Modifier
|
||||
.weight(3.0f)
|
||||
.fillMaxWidth(),
|
||||
sendEvent = sendEvent
|
||||
)
|
||||
CtrlShiftMetaKeys(
|
||||
modifier = Modifier
|
||||
.weight(1.0f)
|
||||
.fillMaxWidth(),
|
||||
ctrlState = ctrlState,
|
||||
shiftState = shiftState
|
||||
)
|
||||
}
|
||||
SideKeys(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1.0f),
|
||||
onEvent = onEvent,
|
||||
onCodePoint = onCodePoint,
|
||||
keyboardShown = keyboardShown
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val TextEditAction = Action(
|
||||
icon = R.drawable.edit_text,
|
||||
name = R.string.text_edit_action_title,
|
||||
simplePressImpl = null,
|
||||
persistentState = null,
|
||||
canShowKeyboard = true,
|
||||
windowImpl = { manager, persistentState ->
|
||||
object : ActionWindow {
|
||||
@Composable
|
||||
override fun windowName(): String {
|
||||
return stringResource(R.string.text_edit_action_title)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun WindowContents(keyboardShown: Boolean) {
|
||||
TextEditScreen(onCodePoint = { a -> manager.sendCodePointEvent(a)}, onEvent = { a, b -> manager.sendKeyEvent(a, b) }, keyboardShown = keyboardShown)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
fun TextEditScreenPreview() {
|
||||
Surface(modifier = Modifier.height(256.dp)) {
|
||||
TextEditScreen(onCodePoint = { }, onEvent = { _, _ -> }, keyboardShown = false)
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
fun TextEditScreenPreviewWithKb() {
|
||||
Surface(modifier = Modifier.height(256.dp)) {
|
||||
TextEditScreen(onCodePoint = { }, onEvent = { _, _ -> }, keyboardShown = true)
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -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,
|
||||
)
|
@ -180,7 +180,7 @@ private class VoiceInputActionWindow(
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun WindowContents() {
|
||||
override fun WindowContents(keyboardShown: Boolean) {
|
||||
Box(modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clickable(enabled = true,
|
||||
|
@ -651,6 +651,9 @@ namespace latinime {
|
||||
|
||||
std::vector<TokenMix> mixes;
|
||||
for(int i=0; i<inputSize; i++) {
|
||||
char wc = partialWordString[i];
|
||||
if (!(wc >= 'a' && wc <= 'z') && !(wc >= 'A' && wc <= 'Z')) continue;
|
||||
|
||||
std::vector<float> 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<NUM_TOKEN_MIX; j++) {
|
||||
|
Loading…
Reference in New Issue
Block a user