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"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="26dp"
|
android:width="24dp"
|
||||||
android:height="26dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="26"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="26">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:pathData="M20,13L6,13"
|
android:pathData="M19,12L5,12"
|
||||||
android:strokeLineJoin="round"
|
android:strokeLineJoin="round"
|
||||||
android:strokeWidth="2"
|
android:strokeWidth="2"
|
||||||
android:fillColor="#00000000"
|
android:fillColor="#00000000"
|
||||||
android:strokeColor="#FFFFFF"
|
android:strokeColor="#ffffff"
|
||||||
android:strokeLineCap="round"/>
|
android:strokeLineCap="round"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="M13,20L6,13L13,6"
|
android:pathData="M12,19l-7,-7l7,-7"
|
||||||
android:strokeLineJoin="round"
|
android:strokeLineJoin="round"
|
||||||
android:strokeWidth="2"
|
android:strokeWidth="2"
|
||||||
android:fillColor="#00000000"
|
android:fillColor="#00000000"
|
||||||
android:strokeColor="#FFFFFF"
|
android:strokeColor="#ffffff"
|
||||||
android:strokeLineCap="round"/>
|
android:strokeLineCap="round"/>
|
||||||
</vector>
|
</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" />
|
<attr name="keyTextShadowColor" format="color" />
|
||||||
<!-- Color to use for the label in a key when in inactivated state. -->
|
<!-- Color to use for the label in a key when in inactivated state. -->
|
||||||
<attr name="keyTextInactivatedColor" format="color" />
|
<attr name="keyTextInactivatedColor" format="color" />
|
||||||
|
<attr name="keyPressedTextColor" format="color" />
|
||||||
<!-- Color to use for the label in a key that has followFunctionalTextColor keyLabelFlags. -->
|
<!-- Color to use for the label in a key that has followFunctionalTextColor keyLabelFlags. -->
|
||||||
<attr name="functionalTextColor" format="color" />
|
<attr name="functionalTextColor" format="color" />
|
||||||
<!-- Key hint letter (= one character hint label) color -->
|
<!-- Key hint letter (= one character hint label) color -->
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="voice_input_action_title">Voice Input</string>
|
<string name="voice_input_action_title">Voice Input</string>
|
||||||
<string name="theme_switcher_action_title">Theme Switcher</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="amoled_dark_theme_name">AMOLED Dark Purple</string>
|
||||||
<string name="classic_material_dark_theme_name">AOSP Material Dark</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 copied from packages/apps/Settings/res/values/strings.xml -->
|
||||||
<!-- This resource is corresponding to msgid="5433275485499039199" -->
|
<!-- This resource is corresponding to msgid="5433275485499039199" -->
|
||||||
<string name="user_dict_fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
|
<string name="user_dict_fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
|
||||||
<string name="title_emojis">Emojis</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
<item name="spacebarBackground">@drawable/btn_keyboard_spacebar_lxx_dark</item>
|
<item name="spacebarBackground">@drawable/btn_keyboard_spacebar_lxx_dark</item>
|
||||||
<item name="keyTextColor">@color/key_text_color_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="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="functionalTextColor">@color/key_text_color_lxx_dark</item>
|
||||||
<item name="keyHintLetterColor">@color/key_hint_letter_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>
|
<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="spacebarBackground">@drawable/btn_keyboard_spacebar_lxx_light</item>
|
||||||
<item name="keyTextColor">@color/key_text_color_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="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="functionalTextColor">@color/key_functional_text_color_lxx_light</item>
|
||||||
<item name="keyHintLetterColor">@color/key_hint_letter_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>
|
<item name="keyHintLabelColor">@color/key_text_inactive_color_lxx_light</item>
|
||||||
|
@ -23,16 +23,26 @@
|
|||||||
>
|
>
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="a"
|
latin:keySpec="a"
|
||||||
|
latin:keyHintLabel="\@"
|
||||||
|
latin:additionalMoreKeys="\\@"
|
||||||
latin:moreKeys="!text/morekeys_a" />
|
latin:moreKeys="!text/morekeys_a" />
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="s"
|
latin:keySpec="s"
|
||||||
|
latin:keyHintLabel="#"
|
||||||
|
latin:additionalMoreKeys="#"
|
||||||
latin:moreKeys="!text/morekeys_s" />
|
latin:moreKeys="!text/morekeys_s" />
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="d"
|
latin:keySpec="d"
|
||||||
|
latin:keyHintLabel="$"
|
||||||
|
latin:additionalMoreKeys="$,€,£,¥,¢"
|
||||||
latin:moreKeys="!text/morekeys_d" />
|
latin:moreKeys="!text/morekeys_d" />
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="f" />
|
latin:keySpec="f"
|
||||||
|
latin:keyHintLabel="%"
|
||||||
|
latin:additionalMoreKeys="%" />
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="g"
|
latin:keySpec="g"
|
||||||
|
latin:keyHintLabel="&"
|
||||||
|
latin:additionalMoreKeys="&,|"
|
||||||
latin:moreKeys="!text/morekeys_g" />
|
latin:moreKeys="!text/morekeys_g" />
|
||||||
</merge>
|
</merge>
|
||||||
|
@ -23,14 +23,22 @@
|
|||||||
>
|
>
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="h"
|
latin:keySpec="h"
|
||||||
|
latin:keyHintLabel="-"
|
||||||
|
latin:additionalMoreKeys="-,–,—,_"
|
||||||
latin:moreKeys="!text/morekeys_h" />
|
latin:moreKeys="!text/morekeys_h" />
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="j"
|
latin:keySpec="j"
|
||||||
|
latin:keyHintLabel="+"
|
||||||
|
latin:additionalMoreKeys="+,="
|
||||||
latin:moreKeys="!text/morekeys_j" />
|
latin:moreKeys="!text/morekeys_j" />
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="k"
|
latin:keySpec="k"
|
||||||
|
latin:keyHintLabel="("
|
||||||
|
latin:additionalMoreKeys="(,[,{,<"
|
||||||
latin:moreKeys="!text/morekeys_k" />
|
latin:moreKeys="!text/morekeys_k" />
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="l"
|
latin:keySpec="l"
|
||||||
|
latin:keyHintLabel=")"
|
||||||
|
latin:additionalMoreKeys="),],},>"
|
||||||
latin:moreKeys="!text/morekeys_l" />
|
latin:moreKeys="!text/morekeys_l" />
|
||||||
</merge>
|
</merge>
|
||||||
|
@ -23,14 +23,22 @@
|
|||||||
>
|
>
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="z"
|
latin:keySpec="z"
|
||||||
|
latin:keyHintLabel="*"
|
||||||
|
latin:additionalMoreKeys="*"
|
||||||
latin:moreKeys="!text/morekeys_z" />
|
latin:moreKeys="!text/morekeys_z" />
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="!text/keyspec_x"
|
latin:keySpec="!text/keyspec_x"
|
||||||
|
latin:keyHintLabel="""
|
||||||
|
latin:additionalMoreKeys="""
|
||||||
latin:moreKeys="!text/morekeys_x" />
|
latin:moreKeys="!text/morekeys_x" />
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="c"
|
latin:keySpec="c"
|
||||||
|
latin:keyHintLabel="'"
|
||||||
|
latin:additionalMoreKeys="'"
|
||||||
latin:moreKeys="!text/morekeys_c" />
|
latin:moreKeys="!text/morekeys_c" />
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="v"
|
latin:keySpec="v"
|
||||||
|
latin:keyHintLabel=":"
|
||||||
|
latin:additionalMoreKeys=":"
|
||||||
latin:moreKeys="!text/morekeys_v" />
|
latin:moreKeys="!text/morekeys_v" />
|
||||||
</merge>
|
</merge>
|
||||||
|
@ -22,10 +22,16 @@
|
|||||||
xmlns:latin="http://schemas.android.com/apk/res/org.futo.inputmethod.latin"
|
xmlns:latin="http://schemas.android.com/apk/res/org.futo.inputmethod.latin"
|
||||||
>
|
>
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="b" />
|
latin:keySpec="b"
|
||||||
|
latin:keyHintLabel=";"
|
||||||
|
latin:additionalMoreKeys=";" />
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="n"
|
latin:keySpec="n"
|
||||||
|
latin:keyHintLabel="!"
|
||||||
|
latin:additionalMoreKeys="!"
|
||||||
latin:moreKeys="!text/morekeys_n" />
|
latin:moreKeys="!text/morekeys_n" />
|
||||||
<Key
|
<Key
|
||||||
latin:keySpec="m" />
|
latin:keySpec="m"
|
||||||
|
latin:keyHintLabel="?"
|
||||||
|
latin:additionalMoreKeys="?,/" />
|
||||||
</merge>
|
</merge>
|
||||||
|
@ -665,6 +665,10 @@ public class Key implements Comparable<Key> {
|
|||||||
if ((mLabelFlags & LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR) != 0) {
|
if ((mLabelFlags & LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR) != 0) {
|
||||||
return params.mFunctionalTextColor;
|
return params.mFunctionalTextColor;
|
||||||
}
|
}
|
||||||
|
if (mPressed) {
|
||||||
|
return params.mPressedTextColor;
|
||||||
|
}
|
||||||
|
|
||||||
return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor;
|
return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +101,11 @@ public interface KeyboardActionListener {
|
|||||||
*/
|
*/
|
||||||
public boolean onCustomRequest(int requestCode);
|
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 final KeyboardActionListener EMPTY_LISTENER = new Adapter();
|
||||||
|
|
||||||
public static class Adapter implements KeyboardActionListener {
|
public static class Adapter implements KeyboardActionListener {
|
||||||
@ -125,8 +130,14 @@ public interface KeyboardActionListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onFinishSlidingInput() {}
|
public void onFinishSlidingInput() {}
|
||||||
@Override
|
@Override
|
||||||
public boolean onCustomRequest(int requestCode) {
|
public boolean onCustomRequest(int requestCode) { return false; }
|
||||||
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);
|
R.styleable.Keyboard_Key, defStyle, R.style.KeyboardView);
|
||||||
mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0);
|
mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0);
|
||||||
mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr, mDrawableProvider);
|
mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr, mDrawableProvider);
|
||||||
|
|
||||||
|
if(isMoreKeys && mKeyVisualAttributes != null) {
|
||||||
|
mKeyVisualAttributes.mTextColor = mDrawableProvider.getMoreKeysTextColor();
|
||||||
|
}
|
||||||
|
|
||||||
keyAttr.recycle();
|
keyAttr.recycle();
|
||||||
|
|
||||||
mPaint.setAntiAlias(true);
|
mPaint.setAntiAlias(true);
|
||||||
|
@ -85,6 +85,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
|
|||||||
|
|
||||||
// Parameters for pointer handling.
|
// Parameters for pointer handling.
|
||||||
private static PointerTrackerParams sParams;
|
private static PointerTrackerParams sParams;
|
||||||
|
private static final int sPointerStep = (int)(16.0 * Resources.getSystem().getDisplayMetrics().density);
|
||||||
|
|
||||||
private static GestureStrokeRecognitionParams sGestureStrokeRecognitionParams;
|
private static GestureStrokeRecognitionParams sGestureStrokeRecognitionParams;
|
||||||
private static GestureStrokeDrawingParams sGestureStrokeDrawingParams;
|
private static GestureStrokeDrawingParams sGestureStrokeDrawingParams;
|
||||||
private static boolean sNeedsPhantomSuddenMoveEventHack;
|
private static boolean sNeedsPhantomSuddenMoveEventHack;
|
||||||
@ -128,6 +130,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
|
|||||||
private int mLastX;
|
private int mLastX;
|
||||||
private int mLastY;
|
private int mLastY;
|
||||||
|
|
||||||
|
private int mStartX;
|
||||||
|
private int mStartY;
|
||||||
|
private long mStartTime;
|
||||||
|
private boolean mCursorMoved = false;
|
||||||
|
|
||||||
// true if keyboard layout has been changed.
|
// true if keyboard layout has been changed.
|
||||||
private boolean mKeyboardLayoutHasBeenChanged;
|
private boolean mKeyboardLayoutHasBeenChanged;
|
||||||
|
|
||||||
@ -691,6 +698,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
|
|||||||
startRepeatKey(key);
|
startRepeatKey(key);
|
||||||
startLongPressTimer(key);
|
startLongPressTimer(key);
|
||||||
setPressedKeyGraphics(key, eventTime);
|
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 lastX = mLastX;
|
||||||
final int lastY = mLastY;
|
final int lastY = mLastY;
|
||||||
final Key oldKey = mCurrentKey;
|
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);
|
final Key newKey = onMoveKey(x, y);
|
||||||
|
|
||||||
if (sGestureEnabler.shouldHandleGesture()) {
|
if (sGestureEnabler.shouldHandleGesture()) {
|
||||||
@ -966,6 +1000,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
|
|||||||
// Release the last pressed key.
|
// Release the last pressed key.
|
||||||
setReleasedKeyGraphics(currentKey, true /* withAnimation */);
|
setReleasedKeyGraphics(currentKey, true /* withAnimation */);
|
||||||
|
|
||||||
|
if(mCursorMoved && currentKey.getCode() == Constants.CODE_DELETE) {
|
||||||
|
sListener.onUpWithDeletePointerActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mCursorMoved) {
|
||||||
|
sListener.onUpWithPointerActive();
|
||||||
|
}
|
||||||
|
|
||||||
if (isShowingMoreKeysPanel()) {
|
if (isShowingMoreKeysPanel()) {
|
||||||
if (!mIsTrackingForActionDisabled) {
|
if (!mIsTrackingForActionDisabled) {
|
||||||
final int translatedX = mMoreKeysPanel.translateX(x);
|
final int translatedX = mMoreKeysPanel.translateX(x);
|
||||||
@ -988,6 +1030,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mCursorMoved) {
|
||||||
|
mCursorMoved = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (mIsTrackingForActionDisabled) {
|
if (mIsTrackingForActionDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1018,6 +1064,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
|
|||||||
if (isShowingMoreKeysPanel()) {
|
if (isShowingMoreKeysPanel()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (mCursorMoved) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final Key key = getKey();
|
final Key key = getKey();
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -37,6 +37,7 @@ public final class KeyDrawParams {
|
|||||||
|
|
||||||
public int mTextColor;
|
public int mTextColor;
|
||||||
public int mTextInactivatedColor;
|
public int mTextInactivatedColor;
|
||||||
|
public int mPressedTextColor;
|
||||||
public int mTextShadowColor;
|
public int mTextShadowColor;
|
||||||
public int mFunctionalTextColor;
|
public int mFunctionalTextColor;
|
||||||
public int mHintLetterColor;
|
public int mHintLetterColor;
|
||||||
@ -66,6 +67,7 @@ public final class KeyDrawParams {
|
|||||||
|
|
||||||
mTextColor = copyFrom.mTextColor;
|
mTextColor = copyFrom.mTextColor;
|
||||||
mTextInactivatedColor = copyFrom.mTextInactivatedColor;
|
mTextInactivatedColor = copyFrom.mTextInactivatedColor;
|
||||||
|
mPressedTextColor = copyFrom.mPressedTextColor;
|
||||||
mTextShadowColor = copyFrom.mTextShadowColor;
|
mTextShadowColor = copyFrom.mTextShadowColor;
|
||||||
mFunctionalTextColor = copyFrom.mFunctionalTextColor;
|
mFunctionalTextColor = copyFrom.mFunctionalTextColor;
|
||||||
mHintLetterColor = copyFrom.mHintLetterColor;
|
mHintLetterColor = copyFrom.mHintLetterColor;
|
||||||
@ -103,6 +105,7 @@ public final class KeyDrawParams {
|
|||||||
|
|
||||||
mTextColor = selectColor(attr.mTextColor, mTextColor);
|
mTextColor = selectColor(attr.mTextColor, mTextColor);
|
||||||
mTextInactivatedColor = selectColor(attr.mTextInactivatedColor, mTextInactivatedColor);
|
mTextInactivatedColor = selectColor(attr.mTextInactivatedColor, mTextInactivatedColor);
|
||||||
|
mPressedTextColor = selectColor(attr.mPressedTextColor, mPressedTextColor);
|
||||||
mTextShadowColor = selectColor(attr.mTextShadowColor, mTextShadowColor);
|
mTextShadowColor = selectColor(attr.mTextShadowColor, mTextShadowColor);
|
||||||
mFunctionalTextColor = selectColor(attr.mFunctionalTextColor, mFunctionalTextColor);
|
mFunctionalTextColor = selectColor(attr.mFunctionalTextColor, mFunctionalTextColor);
|
||||||
mHintLetterColor = selectColor(attr.mHintLetterColor, mHintLetterColor);
|
mHintLetterColor = selectColor(attr.mHintLetterColor, mHintLetterColor);
|
||||||
|
@ -41,8 +41,9 @@ public final class KeyVisualAttributes {
|
|||||||
public final float mHintLabelRatio;
|
public final float mHintLabelRatio;
|
||||||
public final float mPreviewTextRatio;
|
public final float mPreviewTextRatio;
|
||||||
|
|
||||||
public final int mTextColor;
|
public int mTextColor;
|
||||||
public final int mTextInactivatedColor;
|
public final int mTextInactivatedColor;
|
||||||
|
public final int mPressedTextColor;
|
||||||
public final int mTextShadowColor;
|
public final int mTextShadowColor;
|
||||||
public final int mFunctionalTextColor;
|
public final int mFunctionalTextColor;
|
||||||
public final int mHintLetterColor;
|
public final int mHintLetterColor;
|
||||||
@ -130,6 +131,8 @@ public final class KeyVisualAttributes {
|
|||||||
R.styleable.Keyboard_Key_keyTextColor, 0, keyAttr, provider);
|
R.styleable.Keyboard_Key_keyTextColor, 0, keyAttr, provider);
|
||||||
mTextInactivatedColor = DynamicThemeProvider.Companion.getColorOrDefault(
|
mTextInactivatedColor = DynamicThemeProvider.Companion.getColorOrDefault(
|
||||||
R.styleable.Keyboard_Key_keyTextInactivatedColor, 0, keyAttr, provider);
|
R.styleable.Keyboard_Key_keyTextInactivatedColor, 0, keyAttr, provider);
|
||||||
|
mPressedTextColor = DynamicThemeProvider.Companion.getColorOrDefault(
|
||||||
|
R.styleable.Keyboard_Key_keyPressedTextColor, 0, keyAttr, provider);
|
||||||
mTextShadowColor = DynamicThemeProvider.Companion.getColorOrDefault(
|
mTextShadowColor = DynamicThemeProvider.Companion.getColorOrDefault(
|
||||||
R.styleable.Keyboard_Key_keyTextShadowColor, 0, keyAttr, provider);
|
R.styleable.Keyboard_Key_keyTextShadowColor, 0, keyAttr, provider);
|
||||||
mFunctionalTextColor = DynamicThemeProvider.Companion.getColorOrDefault(
|
mFunctionalTextColor = DynamicThemeProvider.Companion.getColorOrDefault(
|
||||||
|
@ -302,7 +302,7 @@ public final class KeyboardTextsTable {
|
|||||||
/* ~ additional_morekeys_symbols_0 */
|
/* ~ additional_morekeys_symbols_0 */
|
||||||
/* morekeys_tablet_period */ "!text/morekeys_tablet_punctuation",
|
/* morekeys_tablet_period */ "!text/morekeys_tablet_punctuation",
|
||||||
/* morekeys_nordic_row2_11 */ EMPTY,
|
/* morekeys_nordic_row2_11 */ EMPTY,
|
||||||
/* morekeys_punctuation */ "!autoColumnOrder!8,\\,,?,!,#,!text/keyspec_right_parenthesis,!text/keyspec_left_parenthesis,/,;,',@,:,-,\",+,\\%,&",
|
/* morekeys_punctuation */ "_,\\\\,|,=",
|
||||||
/* keyspec_tablet_comma */ ",",
|
/* keyspec_tablet_comma */ ",",
|
||||||
// Period key
|
// Period key
|
||||||
/* keyspec_period */ ".",
|
/* keyspec_period */ ".",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.futo.inputmethod.latin
|
package org.futo.inputmethod.latin
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.inputmethodservice.InputMethodService
|
import android.inputmethodservice.InputMethodService
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@ -13,34 +12,17 @@ import android.view.inputmethod.InlineSuggestionsRequest
|
|||||||
import android.view.inputmethod.InlineSuggestionsResponse
|
import android.view.inputmethod.InlineSuggestionsResponse
|
||||||
import android.view.inputmethod.InputMethodSubtype
|
import android.view.inputmethod.InputMethodSubtype
|
||||||
import androidx.annotation.RequiresApi
|
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.foundation.layout.size
|
||||||
import androidx.compose.material3.ColorScheme
|
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.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.key
|
import androidx.compose.runtime.key
|
||||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
import androidx.compose.ui.draw.clipToBounds
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
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.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.LifecycleRegistry
|
import androidx.lifecycle.LifecycleRegistry
|
||||||
import androidx.lifecycle.ViewModelStore
|
import androidx.lifecycle.ViewModelStore
|
||||||
@ -56,34 +38,24 @@ import androidx.savedstate.SavedStateRegistryOwner
|
|||||||
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
|
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
|
||||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
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.BasicThemeProvider
|
||||||
import org.futo.inputmethod.latin.uix.DynamicThemeProvider
|
import org.futo.inputmethod.latin.uix.DynamicThemeProvider
|
||||||
import org.futo.inputmethod.latin.uix.DynamicThemeProviderOwner
|
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.THEME_KEY
|
||||||
|
import org.futo.inputmethod.latin.uix.UixManager
|
||||||
import org.futo.inputmethod.latin.uix.createInlineSuggestionsRequest
|
import org.futo.inputmethod.latin.uix.createInlineSuggestionsRequest
|
||||||
import org.futo.inputmethod.latin.uix.deferGetSetting
|
import org.futo.inputmethod.latin.uix.deferGetSetting
|
||||||
import org.futo.inputmethod.latin.uix.deferSetSetting
|
import org.futo.inputmethod.latin.uix.deferSetSetting
|
||||||
import org.futo.inputmethod.latin.uix.differsFrom
|
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.DarkColorScheme
|
||||||
import org.futo.inputmethod.latin.uix.theme.ThemeOption
|
import org.futo.inputmethod.latin.uix.theme.ThemeOption
|
||||||
import org.futo.inputmethod.latin.uix.theme.ThemeOptions
|
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.uix.theme.presets.VoiceInputTheme
|
||||||
import org.futo.inputmethod.latin.xlm.LanguageModelFacilitator
|
import org.futo.inputmethod.latin.xlm.LanguageModelFacilitator
|
||||||
|
|
||||||
class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner,
|
class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner,
|
||||||
LatinIMELegacy.SuggestionStripController, DynamicThemeProviderOwner, KeyboardManagerForAction {
|
LatinIMELegacy.SuggestionStripController, DynamicThemeProviderOwner {
|
||||||
|
|
||||||
private val mSavedStateRegistryController = SavedStateRegistryController.create(this)
|
private val mSavedStateRegistryController = SavedStateRegistryController.create(this)
|
||||||
|
|
||||||
@ -101,7 +73,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
override val viewModelStore
|
override val viewModelStore
|
||||||
get() = store
|
get() = store
|
||||||
|
|
||||||
private fun setOwners() {
|
fun setOwners() {
|
||||||
val decorView = window.window?.decorView
|
val decorView = window.window?.decorView
|
||||||
if (decorView?.findViewTreeLifecycleOwner() == null) {
|
if (decorView?.findViewTreeLifecycleOwner() == null) {
|
||||||
decorView?.setViewTreeLifecycleOwner(this)
|
decorView?.setViewTreeLifecycleOwner(this)
|
||||||
@ -114,14 +86,14 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var composeView: ComposeView? = null
|
val latinIMELegacy = LatinIMELegacy(
|
||||||
|
|
||||||
private val latinIMELegacy = LatinIMELegacy(
|
|
||||||
this as InputMethodService,
|
this as InputMethodService,
|
||||||
this as LatinIMELegacy.SuggestionStripController
|
this as LatinIMELegacy.SuggestionStripController
|
||||||
)
|
)
|
||||||
|
|
||||||
public val languageModelFacilitator = LanguageModelFacilitator(
|
val inputLogic get() = latinIMELegacy.mInputLogic
|
||||||
|
|
||||||
|
val languageModelFacilitator = LanguageModelFacilitator(
|
||||||
this,
|
this,
|
||||||
latinIMELegacy.mInputLogic,
|
latinIMELegacy.mInputLogic,
|
||||||
latinIMELegacy.mDictionaryFacilitator,
|
latinIMELegacy.mDictionaryFacilitator,
|
||||||
@ -130,21 +102,18 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
lifecycleScope
|
lifecycleScope
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val uixManager = UixManager(this)
|
||||||
|
|
||||||
private var activeThemeOption: ThemeOption? = null
|
private var activeThemeOption: ThemeOption? = null
|
||||||
private var activeColorScheme = DarkColorScheme
|
private var activeColorScheme = DarkColorScheme
|
||||||
private var colorSchemeLoaderJob: Job? = null
|
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 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
|
private var lastEditorInfo: EditorInfo? = null
|
||||||
|
|
||||||
// TODO: Calling this repeatedly as the theme changes tends to slow everything to a crawl
|
// 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)
|
drawableProvider = BasicThemeProvider(this, overrideColorScheme = colorScheme)
|
||||||
|
|
||||||
window.window?.navigationBarColor = drawableProvider!!.primaryKeyboardColor
|
window.window?.navigationBarColor = drawableProvider!!.primaryKeyboardColor
|
||||||
setContent()
|
uixManager.onColorSchemeChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDrawableProvider(): DynamicThemeProvider {
|
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() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
@ -241,41 +236,38 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
private var touchableHeight: Int = 0
|
private var touchableHeight: Int = 0
|
||||||
override fun onCreateInputView(): View {
|
override fun onCreateInputView(): View {
|
||||||
legacyInputView = latinIMELegacy.onCreateInputView()
|
legacyInputView = latinIMELegacy.onCreateInputView()
|
||||||
composeView = ComposeView(this).apply {
|
|
||||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
|
||||||
setParentCompositionContext(null)
|
|
||||||
|
|
||||||
this@LatinIME.setOwners()
|
|
||||||
}
|
|
||||||
|
|
||||||
setContent()
|
|
||||||
|
|
||||||
|
val composeView = uixManager.createComposeView()
|
||||||
latinIMELegacy.setComposeInputView(composeView)
|
latinIMELegacy.setComposeInputView(composeView)
|
||||||
|
|
||||||
return 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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var inputViewHeight: Int = -1
|
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
|
@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) {
|
val modifier = if(hidden) {
|
||||||
Modifier.clipToBounds().size(0.dp)
|
Modifier
|
||||||
|
.clipToBounds()
|
||||||
|
.size(0.dp)
|
||||||
} else {
|
} else {
|
||||||
Modifier.onSizeChanged {
|
Modifier.onSizeChanged {
|
||||||
inputViewHeight = it.height
|
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
|
// necessary for when KeyboardSwitcher updates the theme
|
||||||
fun updateLegacyView(newView: View) {
|
fun updateLegacyView(newView: View) {
|
||||||
legacyInputView = newView
|
legacyInputView = newView
|
||||||
setContent()
|
|
||||||
|
|
||||||
if (composeView != null) {
|
uixManager.setContent()
|
||||||
latinIMELegacy.setComposeInputView(composeView)
|
uixManager.getComposeView()?.let {
|
||||||
|
latinIMELegacy.setComposeInputView(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
latinIMELegacy.setInputView(legacyInputView)
|
latinIMELegacy.setInputView(legacyInputView)
|
||||||
@ -417,8 +295,8 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
override fun setInputView(view: View?) {
|
override fun setInputView(view: View?) {
|
||||||
super.setInputView(view)
|
super.setInputView(view)
|
||||||
|
|
||||||
if (composeView != null) {
|
uixManager.getComposeView()?.let {
|
||||||
latinIMELegacy.setComposeInputView(composeView)
|
latinIMELegacy.setComposeInputView(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
latinIMELegacy.setInputView(legacyInputView)
|
latinIMELegacy.setInputView(legacyInputView)
|
||||||
@ -443,15 +321,14 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
override fun onFinishInputView(finishingInput: Boolean) {
|
override fun onFinishInputView(finishingInput: Boolean) {
|
||||||
super.onFinishInputView(finishingInput)
|
super.onFinishInputView(finishingInput)
|
||||||
latinIMELegacy.onFinishInputView(finishingInput)
|
latinIMELegacy.onFinishInputView(finishingInput)
|
||||||
|
uixManager.onInputFinishing()
|
||||||
closeActionWindow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFinishInput() {
|
override fun onFinishInput() {
|
||||||
super.onFinishInput()
|
super.onFinishInput()
|
||||||
latinIMELegacy.onFinishInput()
|
latinIMELegacy.onFinishInput()
|
||||||
|
|
||||||
closeActionWindow()
|
uixManager.onInputFinishing()
|
||||||
languageModelFacilitator.saveHistoryLog()
|
languageModelFacilitator.saveHistoryLog()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,7 +348,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
super.onWindowHidden()
|
super.onWindowHidden()
|
||||||
latinIMELegacy.onWindowHidden()
|
latinIMELegacy.onWindowHidden()
|
||||||
|
|
||||||
closeActionWindow()
|
uixManager.onInputFinishing()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUpdateSelection(
|
override fun onUpdateSelection(
|
||||||
@ -521,12 +398,14 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onComputeInsets(outInsets: Insets?) {
|
override fun onComputeInsets(outInsets: Insets?) {
|
||||||
|
val composeView = uixManager.getComposeView()
|
||||||
|
|
||||||
// This method may be called before {@link #setInputView(View)}.
|
// This method may be called before {@link #setInputView(View)}.
|
||||||
if (legacyInputView == null || composeView == null) {
|
if (legacyInputView == null || composeView == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val inputHeight: Int = composeView!!.height
|
val inputHeight: Int = composeView.height
|
||||||
if (latinIMELegacy.isImeSuppressedByHardwareKeyboard && !legacyInputView!!.isShown) {
|
if (latinIMELegacy.isImeSuppressedByHardwareKeyboard && !legacyInputView!!.isShown) {
|
||||||
// If there is a hardware keyboard and a visible software keyboard view has been hidden,
|
// If there is a hardware keyboard and a visible software keyboard view has been hidden,
|
||||||
// no visual element will be shown on the screen.
|
// no visual element will be shown on the screen.
|
||||||
@ -541,7 +420,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
|
|
||||||
val touchLeft = 0
|
val touchLeft = 0
|
||||||
val touchTop = visibleTopY
|
val touchTop = visibleTopY
|
||||||
val touchRight = composeView!!.width
|
val touchRight = composeView.width
|
||||||
val touchBottom = inputHeight
|
val touchBottom = inputHeight
|
||||||
|
|
||||||
latinIMELegacy.setInsets(outInsets!!.apply {
|
latinIMELegacy.setInsets(outInsets!!.apply {
|
||||||
@ -581,124 +460,25 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateVisibility(shouldShowSuggestionsStrip: Boolean, fullscreenMode: Boolean) {
|
override fun updateVisibility(shouldShowSuggestionsStrip: Boolean, fullscreenMode: Boolean) {
|
||||||
this.shouldShowSuggestionStrip = shouldShowSuggestionsStrip
|
uixManager.updateVisibility(shouldShowSuggestionsStrip, fullscreenMode)
|
||||||
setContent()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setSuggestions(suggestedWords: SuggestedWords?, rtlSubtype: Boolean) {
|
override fun setSuggestions(suggestedWords: SuggestedWords?, rtlSubtype: Boolean) {
|
||||||
this.suggestedWords = suggestedWords
|
uixManager.setSuggestions(suggestedWords, rtlSubtype)
|
||||||
setContent()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun maybeShowImportantNoticeTitle(): Boolean {
|
override fun maybeShowImportantNoticeTitle(): Boolean {
|
||||||
return false
|
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() {
|
override fun onLowMemory() {
|
||||||
super.onLowMemory()
|
super.onLowMemory()
|
||||||
cleanUpPersistentStates()
|
uixManager.cleanUpPersistentStates()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTrimMemory(level: Int) {
|
override fun onTrimMemory(level: Int) {
|
||||||
super.onTrimMemory(level)
|
super.onTrimMemory(level)
|
||||||
cleanUpPersistentStates()
|
uixManager.cleanUpPersistentStates()
|
||||||
}
|
|
||||||
|
|
||||||
override fun getContext(): Context {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLifecycleScope(): LifecycleCoroutineScope {
|
|
||||||
return lifecycleScope
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun triggerContentUpdate() {
|
|
||||||
setContent()
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LatinIMEActionInputTransaction(
|
|
||||||
private val latinIME: LatinIME,
|
|
||||||
shouldApplySpace: Boolean
|
|
||||||
): ActionInputTransaction {
|
|
||||||
private val isSpaceNecessary: Boolean
|
|
||||||
init {
|
|
||||||
val priorText = latinIME.latinIMELegacy.mInputLogic.mConnection.getTextBeforeCursor(1, 0)
|
|
||||||
isSpaceNecessary = shouldApplySpace && !priorText.isNullOrEmpty() && !priorText.last().isWhitespace()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun transformText(text: String): String {
|
|
||||||
return if(isSpaceNecessary) { " $text" } else { text }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updatePartial(text: String) {
|
|
||||||
latinIME.latinIMELegacy.mInputLogic.mConnection.setComposingText(
|
|
||||||
transformText(text),
|
|
||||||
1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun commit(text: String) {
|
|
||||||
latinIME.latinIMELegacy.mInputLogic.mConnection.commitText(
|
|
||||||
transformText(text),
|
|
||||||
1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancel() {
|
|
||||||
// TODO: Do we want to leave the composing text as-is, or delete it?
|
|
||||||
latinIME.latinIMELegacy.mInputLogic.mConnection.finishComposingText()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createInputTransaction(applySpaceIfNeeded: Boolean): ActionInputTransaction {
|
|
||||||
return LatinIMEActionInputTransaction(this, applySpaceIfNeeded)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun typeText(v: String) {
|
|
||||||
latinIMELegacy.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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
@ -708,12 +488,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
override fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean {
|
override fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean {
|
||||||
inlineSuggestions = response.inlineSuggestions.map {
|
return uixManager.onInlineSuggestionsResponse(response)
|
||||||
inflateInlineSuggestion(it)
|
|
||||||
}
|
|
||||||
setContent()
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun postUpdateSuggestionStrip(inputStyle: Int) {
|
fun postUpdateSuggestionStrip(inputStyle: Int) {
|
||||||
|
@ -41,6 +41,7 @@ import android.os.IBinder;
|
|||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.PrintWriterPrinter;
|
import android.util.PrintWriterPrinter;
|
||||||
import android.util.Printer;
|
import android.util.Printer;
|
||||||
@ -158,6 +159,7 @@ public class LatinIMELegacy implements KeyboardActionListener,
|
|||||||
private static final String SCHEME_PACKAGE = "package";
|
private static final String SCHEME_PACKAGE = "package";
|
||||||
|
|
||||||
final Settings mSettings;
|
final Settings mSettings;
|
||||||
|
private Locale mLocale;
|
||||||
final DictionaryFacilitator mDictionaryFacilitator =
|
final DictionaryFacilitator mDictionaryFacilitator =
|
||||||
DictionaryFacilitatorProvider.getDictionaryFacilitator(
|
DictionaryFacilitatorProvider.getDictionaryFacilitator(
|
||||||
false /* isNeededForSpellChecking */);
|
false /* isNeededForSpellChecking */);
|
||||||
@ -671,18 +673,18 @@ public class LatinIMELegacy implements KeyboardActionListener,
|
|||||||
// Has to be package-visible for unit tests
|
// Has to be package-visible for unit tests
|
||||||
@UsedForTesting
|
@UsedForTesting
|
||||||
void loadSettings() {
|
void loadSettings() {
|
||||||
final Locale locale = mRichImm.getCurrentSubtypeLocale();
|
mLocale = mRichImm.getCurrentSubtypeLocale();
|
||||||
final EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo();
|
final EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo();
|
||||||
final InputAttributes inputAttributes = new InputAttributes(
|
final InputAttributes inputAttributes = new InputAttributes(
|
||||||
editorInfo, mInputMethodService.isFullscreenMode(), mInputMethodService.getPackageName());
|
editorInfo, mInputMethodService.isFullscreenMode(), mInputMethodService.getPackageName());
|
||||||
mSettings.loadSettings(mInputMethodService, locale, inputAttributes);
|
mSettings.loadSettings(mInputMethodService, mLocale, inputAttributes);
|
||||||
final SettingsValues currentSettingsValues = mSettings.getCurrent();
|
final SettingsValues currentSettingsValues = mSettings.getCurrent();
|
||||||
AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues);
|
AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues);
|
||||||
// This method is called on startup and language switch, before the new layout has
|
// This method is called on startup and language switch, before the new layout has
|
||||||
// been displayed. Opening dictionaries never affects responsivity as dictionaries are
|
// been displayed. Opening dictionaries never affects responsivity as dictionaries are
|
||||||
// asynchronously loaded.
|
// asynchronously loaded.
|
||||||
if (!mHandler.hasPendingReopenDictionaries()) {
|
if (!mHandler.hasPendingReopenDictionaries()) {
|
||||||
resetDictionaryFacilitator(locale);
|
resetDictionaryFacilitator(mLocale);
|
||||||
}
|
}
|
||||||
refreshPersonalizationDictionarySession(currentSettingsValues);
|
refreshPersonalizationDictionarySession(currentSettingsValues);
|
||||||
resetDictionaryFacilitatorIfNecessary();
|
resetDictionaryFacilitatorIfNecessary();
|
||||||
@ -1365,6 +1367,54 @@ public class LatinIMELegacy implements KeyboardActionListener,
|
|||||||
return false;
|
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() {
|
private boolean isShowingOptionDialog() {
|
||||||
return mOptionsDialog != null && mOptionsDialog.isShowing();
|
return mOptionsDialog != null && mOptionsDialog.isShowing();
|
||||||
}
|
}
|
||||||
|
@ -1044,4 +1044,41 @@ public final class RichInputConnection implements PrivateCommandPerformer {
|
|||||||
return InputConnectionCompatUtils.requestCursorUpdates(
|
return InputConnectionCompatUtils.requestCursorUpdates(
|
||||||
mIC, enableMonitor, requestImmediateCallback);
|
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
|
// 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
|
// 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.
|
// best thing is to leave it to whatever it thinks is best.
|
||||||
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
|
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL, 0);
|
||||||
int totalDeletedLength = 1;
|
int totalDeletedLength = 1;
|
||||||
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
|
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
|
||||||
// If this is an accelerated (i.e., double) deletion, then we need to
|
// 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.
|
// the previous word, and will lose it after next deletion.
|
||||||
hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
|
hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
|
||||||
inputTransaction.mSettingsValues, currentKeyboardScriptId);
|
inputTransaction.mSettingsValues, currentKeyboardScriptId);
|
||||||
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
|
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL, 0);
|
||||||
totalDeletedLength++;
|
totalDeletedLength++;
|
||||||
}
|
}
|
||||||
StatsUtils.onBackspacePressed(totalDeletedLength);
|
StatsUtils.onBackspacePressed(totalDeletedLength);
|
||||||
@ -2021,13 +2021,13 @@ public final class InputLogic {
|
|||||||
*
|
*
|
||||||
* @param keyCode the key code to send inside the key event.
|
* @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();
|
final long eventTime = SystemClock.uptimeMillis();
|
||||||
mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime,
|
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));
|
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
|
||||||
mConnection.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
|
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));
|
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.
|
// TODO: Remove this special handling of digit letters.
|
||||||
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
|
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
|
||||||
if (codePoint >= '0' && codePoint <= '9') {
|
if (codePoint >= '0' && codePoint <= '9') {
|
||||||
sendDownUpKeyEvent(codePoint - '0' + KeyEvent.KEYCODE_0);
|
sendDownUpKeyEvent(codePoint - '0' + KeyEvent.KEYCODE_0, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2055,7 +2055,7 @@ public final class InputLogic {
|
|||||||
// a hardware keyboard event on pressing enter or delete. This is bad for many
|
// 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
|
// reasons (there are race conditions with commits) but some applications are
|
||||||
// relying on this behavior so we continue to support it for older apps.
|
// relying on this behavior so we continue to support it for older apps.
|
||||||
sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER);
|
sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER, 0);
|
||||||
} else {
|
} else {
|
||||||
mConnection.commitText(StringUtils.newSingleCodePointString(codePoint), 1);
|
mConnection.commitText(StringUtils.newSingleCodePointString(codePoint), 1);
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,9 @@ interface KeyboardManagerForAction {
|
|||||||
fun triggerSystemVoiceInput()
|
fun triggerSystemVoiceInput()
|
||||||
|
|
||||||
fun updateTheme(newTheme: ThemeOption)
|
fun updateTheme(newTheme: ThemeOption)
|
||||||
|
|
||||||
|
fun sendCodePointEvent(codePoint: Int)
|
||||||
|
fun sendKeyEvent(keyCode: Int, metaState: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActionWindow {
|
interface ActionWindow {
|
||||||
@ -36,7 +39,7 @@ interface ActionWindow {
|
|||||||
fun windowName(): String
|
fun windowName(): String
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WindowContents()
|
fun WindowContents(keyboardShown: Boolean)
|
||||||
|
|
||||||
fun close()
|
fun close()
|
||||||
}
|
}
|
||||||
@ -54,6 +57,8 @@ interface PersistentActionState {
|
|||||||
data class Action(
|
data class Action(
|
||||||
@DrawableRes val icon: Int,
|
@DrawableRes val icon: Int,
|
||||||
@StringRes val name: Int,
|
@StringRes val name: Int,
|
||||||
|
val canShowKeyboard: Boolean = false,
|
||||||
|
|
||||||
val windowImpl: ((KeyboardManagerForAction, PersistentActionState?) -> ActionWindow)?,
|
val windowImpl: ((KeyboardManagerForAction, PersistentActionState?) -> ActionWindow)?,
|
||||||
val simplePressImpl: ((KeyboardManagerForAction, PersistentActionState?) -> Unit)?,
|
val simplePressImpl: ((KeyboardManagerForAction, PersistentActionState?) -> Unit)?,
|
||||||
val persistentState: ((KeyboardManagerForAction) -> PersistentActionState)? = null,
|
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.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.ColorScheme
|
import androidx.compose.material3.ColorScheme
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@ -20,6 +21,7 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.IconButtonDefaults
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.dynamicDarkColorScheme
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
import androidx.compose.material3.dynamicLightColorScheme
|
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
|
||||||
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo.KIND_TYPED
|
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo.KIND_TYPED
|
||||||
import org.futo.inputmethod.latin.suggestions.SuggestionStripView
|
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.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.ThemeAction
|
||||||
|
import org.futo.inputmethod.latin.uix.actions.UndoAction
|
||||||
import org.futo.inputmethod.latin.uix.actions.VoiceInputAction
|
import org.futo.inputmethod.latin.uix.actions.VoiceInputAction
|
||||||
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
|
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 org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
|
||||||
import java.lang.Integer.min
|
import java.lang.Integer.min
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
@ -287,7 +294,7 @@ fun ActionItem(action: Action, onSelect: (Action) -> Unit) {
|
|||||||
cornerRadius = CornerRadius(radius, radius)
|
cornerRadius = CornerRadius(radius, radius)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.width(64.dp)
|
.width(50.dp)
|
||||||
.fillMaxHeight(),
|
.fillMaxHeight(),
|
||||||
colors = IconButtonDefaults.iconButtonColors(contentColor = contentCol)
|
colors = IconButtonDefaults.iconButtonColors(contentColor = contentCol)
|
||||||
) {
|
) {
|
||||||
@ -317,12 +324,10 @@ fun RowScope.ActionItems(onSelect: (Action) -> Unit) {
|
|||||||
ActionItem(EmojiAction, onSelect)
|
ActionItem(EmojiAction, onSelect)
|
||||||
ActionItem(VoiceInputAction, onSelect)
|
ActionItem(VoiceInputAction, onSelect)
|
||||||
ActionItem(ThemeAction, onSelect)
|
ActionItem(ThemeAction, onSelect)
|
||||||
|
ActionItem(UndoAction, onSelect)
|
||||||
Box(modifier = Modifier
|
ActionItem(RedoAction, onSelect)
|
||||||
.fillMaxHeight()
|
ActionItem(ClipboardAction, onSelect)
|
||||||
.weight(1.0f)) {
|
ActionItem(TextEditAction, onSelect)
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -386,7 +391,11 @@ fun ActionBar(
|
|||||||
ExpandActionsButton(isActionsOpen.value) { isActionsOpen.value = !isActionsOpen.value }
|
ExpandActionsButton(isActionsOpen.value) { isActionsOpen.value = !isActionsOpen.value }
|
||||||
|
|
||||||
if(isActionsOpen.value) {
|
if(isActionsOpen.value) {
|
||||||
ActionItems(onActionActivated)
|
LazyRow {
|
||||||
|
item {
|
||||||
|
ActionItems(onActionActivated)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if(inlineSuggestions.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
} else if(inlineSuggestions.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
InlineSuggestions(inlineSuggestions)
|
InlineSuggestions(inlineSuggestions)
|
||||||
} else if(words != null) {
|
} else if(words != null) {
|
||||||
@ -399,7 +408,110 @@ fun ActionBar(
|
|||||||
Spacer(modifier = Modifier.weight(1.0f))
|
Spacer(modifier = Modifier.weight(1.0f))
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionItemSmall(VoiceInputAction, onActionActivated)
|
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
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
|
@ -31,6 +31,7 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch
|
|||||||
|
|
||||||
override val keyFeedback: Drawable
|
override val keyFeedback: Drawable
|
||||||
|
|
||||||
|
override val moreKeysTextColor: Int
|
||||||
override val moreKeysKeyboardBackground: Drawable
|
override val moreKeysKeyboardBackground: Drawable
|
||||||
override val popupKey: Drawable
|
override val popupKey: Drawable
|
||||||
|
|
||||||
@ -107,6 +108,10 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch
|
|||||||
val surface = colorScheme.background.toArgb()
|
val surface = colorScheme.background.toArgb()
|
||||||
val outline = colorScheme.outline.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 onSecondary = colorScheme.onSecondary.toArgb()
|
||||||
val onBackground = colorScheme.onBackground.toArgb()
|
val onBackground = colorScheme.onBackground.toArgb()
|
||||||
val onBackgroundHalf = colorScheme.onBackground.copy(alpha = 0.5f).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_keyTextColor] = onBackground
|
||||||
colors[R.styleable.Keyboard_Key_keyTextInactivatedColor] = onBackgroundHalf
|
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_keyTextShadowColor] = 0
|
||||||
colors[R.styleable.Keyboard_Key_functionalTextColor] = onBackground
|
colors[R.styleable.Keyboard_Key_functionalTextColor] = onBackground
|
||||||
colors[R.styleable.Keyboard_Key_keyHintLetterColor] = onBackgroundHalf
|
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())
|
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 {
|
popupKey = StateListDrawable().apply {
|
||||||
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(),
|
addStateWithHighlightLayerOnPressed(primary, intArrayOf(),
|
||||||
coloredRoundedRectangle(surface, dp(8.dp))
|
coloredRoundedRectangle(primaryContainer, dp(8.dp))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ interface DynamicThemeProvider {
|
|||||||
|
|
||||||
val keyFeedback: Drawable
|
val keyFeedback: Drawable
|
||||||
|
|
||||||
|
val moreKeysTextColor: Int
|
||||||
val moreKeysKeyboardBackground: Drawable
|
val moreKeysKeyboardBackground: Drawable
|
||||||
val popupKey: 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.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import org.futo.inputmethod.latin.R
|
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.Action
|
||||||
import org.futo.inputmethod.latin.uix.ActionWindow
|
import org.futo.inputmethod.latin.uix.ActionWindow
|
||||||
import org.futo.inputmethod.latin.uix.PersistentActionState
|
import org.futo.inputmethod.latin.uix.PersistentActionState
|
||||||
@ -161,7 +162,7 @@ data class BitmapRecycler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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 context = LocalContext.current
|
||||||
val spToDp = context.resources.displayMetrics.scaledDensity / context.resources.displayMetrics.density
|
val spToDp = context.resources.displayMetrics.scaledDensity / context.resources.displayMetrics.density
|
||||||
|
|
||||||
@ -181,41 +182,48 @@ fun EmojiGrid(onClick: (EmojiItem) -> Unit, onExit: () -> Unit, onBackspace: ()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Surface(color = MaterialTheme.colorScheme.background, modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(48.dp)) {
|
|
||||||
Row(modifier = Modifier.padding(2.dp, 8.dp, 2.dp, 0.dp)) {
|
|
||||||
IconButton(onClick = { onExit() }) {
|
|
||||||
Text("ABC", fontSize = 14.sp)
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(onClick = { onSpace() }, modifier = Modifier
|
if(!keyboardShown) {
|
||||||
.weight(1.0f)
|
Surface(
|
||||||
.padding(8.dp, 2.dp), colors = ButtonDefaults.buttonColors(
|
color = MaterialTheme.colorScheme.background, modifier = Modifier
|
||||||
containerColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.33f),
|
.fillMaxWidth()
|
||||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
.height(48.dp)
|
||||||
disabledContainerColor = MaterialTheme.colorScheme.outline,
|
) {
|
||||||
disabledContentColor = MaterialTheme.colorScheme.onBackground,
|
Row(modifier = Modifier.padding(2.dp, 8.dp, 2.dp, 0.dp)) {
|
||||||
), shape = RoundedCornerShape(32.dp)) {
|
IconButton(onClick = { onExit() }) {
|
||||||
Text("")
|
Text("ABC", fontSize = 14.sp)
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton(onClick = { onBackspace() }) {
|
Button(
|
||||||
val icon = painterResource(id = R.drawable.delete)
|
onClick = { onSpace() }, modifier = Modifier
|
||||||
val iconColor = MaterialTheme.colorScheme.onBackground
|
.weight(1.0f)
|
||||||
|
.padding(8.dp, 2.dp), colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.33f),
|
||||||
|
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||||
|
disabledContainerColor = MaterialTheme.colorScheme.outline,
|
||||||
|
disabledContentColor = MaterialTheme.colorScheme.onBackground,
|
||||||
|
), shape = RoundedCornerShape(32.dp)
|
||||||
|
) {
|
||||||
|
Text("")
|
||||||
|
}
|
||||||
|
|
||||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
IconButton(onClick = { onBackspace() }) {
|
||||||
translate(
|
val icon = painterResource(id = R.drawable.delete)
|
||||||
left = this.size.width / 2.0f - icon.intrinsicSize.width / 2.0f,
|
val iconColor = MaterialTheme.colorScheme.onBackground
|
||||||
top = this.size.height / 2.0f - icon.intrinsicSize.height / 2.0f
|
|
||||||
) {
|
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||||
with(icon) {
|
translate(
|
||||||
draw(
|
left = this.size.width / 2.0f - icon.intrinsicSize.width / 2.0f,
|
||||||
icon.intrinsicSize,
|
top = this.size.height / 2.0f - icon.intrinsicSize.height / 2.0f
|
||||||
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(
|
) {
|
||||||
iconColor
|
with(icon) {
|
||||||
|
draw(
|
||||||
|
icon.intrinsicSize,
|
||||||
|
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(
|
||||||
|
iconColor
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -266,7 +274,8 @@ class PersistentEmojiState: PersistentActionState {
|
|||||||
|
|
||||||
val EmojiAction = Action(
|
val EmojiAction = Action(
|
||||||
icon = R.drawable.smile,
|
icon = R.drawable.smile,
|
||||||
name = R.string.title_emojis,
|
name = R.string.emoji_action_title,
|
||||||
|
canShowKeyboard = true,
|
||||||
simplePressImpl = null,
|
simplePressImpl = null,
|
||||||
persistentState = { manager ->
|
persistentState = { manager ->
|
||||||
val state = PersistentEmojiState()
|
val state = PersistentEmojiState()
|
||||||
@ -282,21 +291,21 @@ val EmojiAction = Action(
|
|||||||
object : ActionWindow {
|
object : ActionWindow {
|
||||||
@Composable
|
@Composable
|
||||||
override fun windowName(): String {
|
override fun windowName(): String {
|
||||||
return stringResource(R.string.title_emojis)
|
return stringResource(R.string.emoji_action_title)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun WindowContents() {
|
override fun WindowContents(keyboardShown: Boolean) {
|
||||||
state.emojis.value?.let { emojis ->
|
state.emojis.value?.let { emojis ->
|
||||||
EmojiGrid(onClick = {
|
EmojiGrid(onClick = {
|
||||||
manager.typeText(it.emoji)
|
manager.typeText(it.emoji)
|
||||||
}, onExit = {
|
}, onExit = {
|
||||||
manager.closeActionWindow()
|
manager.closeActionWindow()
|
||||||
}, onSpace = {
|
}, onSpace = {
|
||||||
manager.typeText(" ")
|
manager.sendCodePointEvent(Constants.CODE_SPACE)
|
||||||
}, onBackspace = {
|
}, onBackspace = {
|
||||||
manager.backspace(1)
|
manager.sendCodePointEvent(Constants.CODE_DELETE)
|
||||||
}, bitmaps = state.bitmaps, emojis = emojis)
|
}, 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
|
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.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import org.futo.inputmethod.latin.R
|
import org.futo.inputmethod.latin.R
|
||||||
import org.futo.inputmethod.latin.uix.Action
|
import org.futo.inputmethod.latin.uix.Action
|
||||||
import org.futo.inputmethod.latin.uix.ActionWindow
|
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
|
import org.futo.inputmethod.latin.uix.theme.selector.ThemePicker
|
||||||
|
|
||||||
val ThemeAction = Action(
|
val ThemeAction = Action(
|
||||||
icon = R.drawable.eye,
|
icon = R.drawable.eye,
|
||||||
name = R.string.theme_switcher_action_title,
|
name = R.string.theme_switcher_action_title,
|
||||||
simplePressImpl = null,
|
simplePressImpl = null,
|
||||||
|
canShowKeyboard = true,
|
||||||
windowImpl = { manager, _ ->
|
windowImpl = { manager, _ ->
|
||||||
object : ActionWindow {
|
object : ActionWindow {
|
||||||
@Composable
|
@Composable
|
||||||
@ -30,33 +20,8 @@ val ThemeAction = Action(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun WindowContents() {
|
override fun WindowContents(keyboardShown: Boolean) {
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
ThemePicker { manager.updateTheme(it) }
|
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() {
|
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
|
@Composable
|
||||||
override fun WindowContents() {
|
override fun WindowContents(keyboardShown: Boolean) {
|
||||||
Box(modifier = Modifier
|
Box(modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clickable(enabled = true,
|
.clickable(enabled = true,
|
||||||
|
@ -651,6 +651,9 @@ namespace latinime {
|
|||||||
|
|
||||||
std::vector<TokenMix> mixes;
|
std::vector<TokenMix> mixes;
|
||||||
for(int i=0; i<inputSize; i++) {
|
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]);
|
std::vector<float> proportions = pInfo->decomposeTapPosition(xCoordinates[i], yCoordinates[i]);
|
||||||
for(float &f : proportions) {
|
for(float &f : proportions) {
|
||||||
if(f < 0.05f) f = 0.0f;
|
if(f < 0.05f) f = 0.0f;
|
||||||
@ -701,12 +704,12 @@ namespace latinime {
|
|||||||
results.x = ((float)xCoordinates[i]) / ((float)pInfo->getKeyboardWidth());
|
results.x = ((float)xCoordinates[i]) / ((float)pInfo->getKeyboardWidth());
|
||||||
results.y = ((float)yCoordinates[i]) / ((float)pInfo->getKeyboardHeight());
|
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],
|
//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,
|
// results.x, results.y,
|
||||||
(char)(pInfo->getKeyCodePoint(index_value[0].second)), (float)(index_value[0].first),
|
// (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[1].second)), (float)(index_value[1].first),
|
||||||
(char)(pInfo->getKeyCodePoint(index_value[2].second)), (float)(index_value[2].first)
|
// (char)(pInfo->getKeyCodePoint(index_value[2].second)), (float)(index_value[2].first)
|
||||||
);
|
// );
|
||||||
|
|
||||||
|
|
||||||
for(int j=0; j<NUM_TOKEN_MIX; j++) {
|
for(int j=0; j<NUM_TOKEN_MIX; j++) {
|
||||||
|
Loading…
Reference in New Issue
Block a user