From 47c846ca39c0382e60f82d45af23360a68095afd Mon Sep 17 00:00:00 2001 From: Dimo Karaivanov Date: Fri, 19 Jul 2024 18:03:20 +0300 Subject: [PATCH] Copy pasta (#566) --- app/src/main/AndroidManifest.xml | 4 +- .../io/github/sspanak/tt9/hacks/AppHacks.java | 49 ++-- .../sspanak/tt9/ime/AbstractHandler.java | 3 +- .../sspanak/tt9/ime/CommandHandler.java | 30 ++- .../github/sspanak/tt9/ime/HotkeyHandler.java | 32 ++- .../github/sspanak/tt9/ime/KeyPadHandler.java | 11 +- .../sspanak/tt9/ime/MainViewHandler.java | 8 + .../sspanak/tt9/ime/TextEditingHandler.java | 111 ++++++++ .../github/sspanak/tt9/ime/TypingHandler.java | 8 +- .../github/sspanak/tt9/ime/VoiceHandler.java | 5 +- .../github/sspanak/tt9/ime/helpers/Key.java | 17 +- .../tt9/ime/helpers/OrientationListener.java | 27 +- .../sspanak/tt9/ime/helpers/TextField.java | 22 +- .../tt9/ime/helpers/TextSelection.java | 160 ++++++++++++ .../tt9/preferences/items/ItemText.java | 18 +- .../preferences/settings/SettingsStore.java | 3 +- .../sspanak/tt9/ui/main/BaseMainLayout.java | 12 +- .../sspanak/tt9/ui/main/MainLayoutNumpad.java | 90 ++++++- .../sspanak/tt9/ui/main/MainLayoutSmall.java | 18 +- .../tt9/ui/main/MainLayoutStealth.java | 14 +- .../sspanak/tt9/ui/main/MainLayoutTray.java | 59 ++++- .../github/sspanak/tt9/ui/main/MainView.java | 18 ++ .../tt9/ui/main/ResizableMainView.java | 71 +++-- .../tt9/ui/main/keys/SoftCommandKey.java | 81 ++++-- .../sspanak/tt9/ui/main/keys/SoftKey.java | 7 - .../sspanak/tt9/ui/main/keys/SoftKeyF3.java | 29 +++ .../sspanak/tt9/ui/main/keys/SoftKeyF5.java | 32 +++ .../sspanak/tt9/ui/main/keys/SoftKeyRF3.java | 88 +++++++ .../tt9/ui/main/keys/SoftKeyTextEdit.java | 59 +++++ .../tt9/ui/main/keys/SoftVoiceInputKey.java | 24 -- .../io/github/sspanak/tt9/util/Clipboard.java | 75 ++++++ .../io/github/sspanak/tt9/util/Ternary.java | 7 + .../res/drawable-anydpi-v24/ic_dpad_left.xml | 16 ++ .../res/drawable-anydpi-v24/ic_dpad_right.xml | 16 ++ .../res/drawable-anydpi-v24/ic_txt_copy.xml | 15 ++ .../res/drawable-anydpi-v24/ic_txt_cut.xml | 15 ++ .../res/drawable-anydpi-v24/ic_txt_paste.xml | 15 ++ .../drawable-anydpi-v24/ic_txt_select_all.xml | 15 ++ .../ic_txt_select_none.xml | 15 ++ .../drawable-anydpi-v24/ic_txt_word_back.xml | 15 ++ .../ic_txt_word_forward.xml | 15 ++ .../main/res/drawable-hdpi/ic_dpad_left.png | Bin 0 -> 282 bytes .../main/res/drawable-hdpi/ic_dpad_right.png | Bin 0 -> 270 bytes .../main/res/drawable-hdpi/ic_txt_copy.png | Bin 0 -> 324 bytes app/src/main/res/drawable-hdpi/ic_txt_cut.png | Bin 0 -> 613 bytes .../main/res/drawable-hdpi/ic_txt_paste.png | Bin 0 -> 351 bytes .../res/drawable-hdpi/ic_txt_select_all.png | Bin 0 -> 456 bytes .../res/drawable-hdpi/ic_txt_select_none.png | Bin 0 -> 568 bytes .../res/drawable-hdpi/ic_txt_word_back.png | Bin 0 -> 407 bytes .../res/drawable-hdpi/ic_txt_word_forward.png | Bin 0 -> 425 bytes .../main/res/drawable-mdpi/ic_dpad_left.png | Bin 0 -> 211 bytes .../main/res/drawable-mdpi/ic_dpad_right.png | Bin 0 -> 218 bytes .../main/res/drawable-mdpi/ic_txt_copy.png | Bin 0 -> 224 bytes app/src/main/res/drawable-mdpi/ic_txt_cut.png | Bin 0 -> 401 bytes .../main/res/drawable-mdpi/ic_txt_paste.png | Bin 0 -> 221 bytes .../res/drawable-mdpi/ic_txt_select_all.png | Bin 0 -> 339 bytes .../res/drawable-mdpi/ic_txt_select_none.png | Bin 0 -> 493 bytes .../res/drawable-mdpi/ic_txt_word_back.png | Bin 0 -> 313 bytes .../res/drawable-mdpi/ic_txt_word_forward.png | Bin 0 -> 288 bytes .../main/res/drawable-xhdpi/ic_dpad_left.png | Bin 0 -> 333 bytes .../main/res/drawable-xhdpi/ic_dpad_right.png | Bin 0 -> 320 bytes .../main/res/drawable-xhdpi/ic_txt_copy.png | Bin 0 -> 401 bytes .../main/res/drawable-xhdpi/ic_txt_cut.png | Bin 0 -> 809 bytes .../main/res/drawable-xhdpi/ic_txt_paste.png | Bin 0 -> 440 bytes .../res/drawable-xhdpi/ic_txt_select_all.png | Bin 0 -> 549 bytes .../res/drawable-xhdpi/ic_txt_select_none.png | Bin 0 -> 735 bytes .../res/drawable-xhdpi/ic_txt_word_back.png | Bin 0 -> 532 bytes .../drawable-xhdpi/ic_txt_word_forward.png | Bin 0 -> 475 bytes .../main/res/drawable-xxhdpi/ic_dpad_left.png | Bin 0 -> 444 bytes .../res/drawable-xxhdpi/ic_dpad_right.png | Bin 0 -> 443 bytes .../main/res/drawable-xxhdpi/ic_txt_copy.png | Bin 0 -> 654 bytes .../main/res/drawable-xxhdpi/ic_txt_cut.png | Bin 0 -> 1307 bytes .../main/res/drawable-xxhdpi/ic_txt_paste.png | Bin 0 -> 679 bytes .../res/drawable-xxhdpi/ic_txt_select_all.png | Bin 0 -> 853 bytes .../drawable-xxhdpi/ic_txt_select_none.png | Bin 0 -> 1043 bytes .../res/drawable-xxhdpi/ic_txt_word_back.png | Bin 0 -> 785 bytes .../drawable-xxhdpi/ic_txt_word_forward.png | Bin 0 -> 812 bytes app/src/main/res/layout/main_numpad.xml | 244 +----------------- app/src/main/res/layout/main_small.xml | 112 +------- .../main/res/layout/panel_command_palette.xml | 65 +++++ app/src/main/res/layout/panel_numpad.xml | 175 +++++++++++++ .../main/res/layout/panel_numpad_row_1.xml | 26 ++ .../main/res/layout/panel_numpad_row_2.xml | 26 ++ .../main/res/layout/panel_numpad_row_3.xml | 26 ++ .../main/res/layout/panel_numpad_row_4.xml | 26 ++ .../panel_numpad_text_editing_row_1.xml | 35 +++ .../panel_numpad_text_editing_row_2.xml | 35 +++ .../panel_numpad_text_editing_row_3.xml | 36 +++ .../res/layout/panel_small_function_keys.xml | 50 ++++ .../res/layout/panel_small_text_editing.xml | 138 ++++++++++ app/src/main/res/values/dimens.xml | 2 + app/src/main/res/values/strings.xml | 1 + 92 files changed, 1778 insertions(+), 548 deletions(-) create mode 100644 app/src/main/java/io/github/sspanak/tt9/ime/TextEditingHandler.java create mode 100644 app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextSelection.java create mode 100644 app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyF3.java create mode 100644 app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyF5.java create mode 100644 app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyRF3.java create mode 100644 app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyTextEdit.java delete mode 100644 app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftVoiceInputKey.java create mode 100644 app/src/main/java/io/github/sspanak/tt9/util/Clipboard.java create mode 100644 app/src/main/java/io/github/sspanak/tt9/util/Ternary.java create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_dpad_left.xml create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_dpad_right.xml create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_txt_copy.xml create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_txt_cut.xml create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_txt_paste.xml create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_txt_select_all.xml create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_txt_select_none.xml create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_txt_word_back.xml create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_txt_word_forward.xml create mode 100644 app/src/main/res/drawable-hdpi/ic_dpad_left.png create mode 100644 app/src/main/res/drawable-hdpi/ic_dpad_right.png create mode 100644 app/src/main/res/drawable-hdpi/ic_txt_copy.png create mode 100644 app/src/main/res/drawable-hdpi/ic_txt_cut.png create mode 100644 app/src/main/res/drawable-hdpi/ic_txt_paste.png create mode 100644 app/src/main/res/drawable-hdpi/ic_txt_select_all.png create mode 100644 app/src/main/res/drawable-hdpi/ic_txt_select_none.png create mode 100644 app/src/main/res/drawable-hdpi/ic_txt_word_back.png create mode 100644 app/src/main/res/drawable-hdpi/ic_txt_word_forward.png create mode 100644 app/src/main/res/drawable-mdpi/ic_dpad_left.png create mode 100644 app/src/main/res/drawable-mdpi/ic_dpad_right.png create mode 100644 app/src/main/res/drawable-mdpi/ic_txt_copy.png create mode 100644 app/src/main/res/drawable-mdpi/ic_txt_cut.png create mode 100644 app/src/main/res/drawable-mdpi/ic_txt_paste.png create mode 100644 app/src/main/res/drawable-mdpi/ic_txt_select_all.png create mode 100644 app/src/main/res/drawable-mdpi/ic_txt_select_none.png create mode 100644 app/src/main/res/drawable-mdpi/ic_txt_word_back.png create mode 100644 app/src/main/res/drawable-mdpi/ic_txt_word_forward.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_dpad_left.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_dpad_right.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_txt_copy.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_txt_cut.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_txt_paste.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_txt_select_all.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_txt_select_none.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_txt_word_back.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_txt_word_forward.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_dpad_left.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_dpad_right.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_txt_copy.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_txt_cut.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_txt_paste.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_txt_select_all.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_txt_select_none.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_txt_word_back.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_txt_word_forward.png create mode 100644 app/src/main/res/layout/panel_command_palette.xml create mode 100644 app/src/main/res/layout/panel_numpad.xml create mode 100644 app/src/main/res/layout/panel_numpad_row_1.xml create mode 100644 app/src/main/res/layout/panel_numpad_row_2.xml create mode 100644 app/src/main/res/layout/panel_numpad_row_3.xml create mode 100644 app/src/main/res/layout/panel_numpad_row_4.xml create mode 100644 app/src/main/res/layout/panel_numpad_text_editing_row_1.xml create mode 100644 app/src/main/res/layout/panel_numpad_text_editing_row_2.xml create mode 100644 app/src/main/res/layout/panel_numpad_text_editing_row_3.xml create mode 100644 app/src/main/res/layout/panel_small_function_keys.xml create mode 100644 app/src/main/res/layout/panel_small_text_editing.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1f2f9b54..cbff4896 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ diff --git a/app/src/main/java/io/github/sspanak/tt9/hacks/AppHacks.java b/app/src/main/java/io/github/sspanak/tt9/hacks/AppHacks.java index 4ca005fa..5d331d41 100644 --- a/app/src/main/java/io/github/sspanak/tt9/hacks/AppHacks.java +++ b/app/src/main/java/io/github/sspanak/tt9/hacks/AppHacks.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; import io.github.sspanak.tt9.ime.helpers.CursorOps; import io.github.sspanak.tt9.ime.helpers.SuggestionOps; import io.github.sspanak.tt9.ime.helpers.TextField; +import io.github.sspanak.tt9.ime.helpers.TextSelection; import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.preferences.settings.SettingsStore; @@ -16,13 +17,15 @@ public class AppHacks { private final InputType inputType; private final SettingsStore settings; private final TextField textField; + private final TextSelection textSelection; - public AppHacks(SettingsStore settings, InputConnection inputConnection, InputType inputType, TextField textField) { + public AppHacks(SettingsStore settings, InputConnection inputConnection, InputType inputType, TextField textField, TextSelection textSelection) { this.inputConnection = inputConnection; this.inputType = inputType; this.settings = settings; this.textField = textField; + this.textSelection = textSelection; } @@ -51,8 +54,12 @@ public class AppHacks { return false; } - // When there is no text, allow double function keys to function normally (e.g. "Back" navigates back) - return inputMode.getSuggestions().isEmpty() && textField.getStringBeforeCursor(1).isEmpty(); + // When no text is selected and the cursor is at the beginning, + // allow double function keys to function normally (e.g. "Back" navigates back) + return + inputMode.getSuggestions().isEmpty() + && textSelection.isEmpty() + && textField.getStringBeforeCursor(1).isEmpty(); } @@ -63,7 +70,7 @@ public class AppHacks { */ public boolean onAction(int action) { if (inputType.isSonimSearchField(action)) { - return sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER); + return textField.sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER); } return false; @@ -76,7 +83,7 @@ public class AppHacks { */ public boolean onMoveCursor(boolean backward) { if (inputType.isRustDesk() || inputType.isTermux()) { - return sendDownUpKeyEvents(backward ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT); + return textField.sendDownUpKeyEvents(backward ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT); } return false; @@ -119,7 +126,7 @@ public class AppHacks { // Termux supports only ENTER, so we convert DPAD_CENTER for it. // Any extra installed apps are likely not designed for hardware keypads, so again, // we don't want to send DPAD_CENTER to them. - return sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER); + return textField.sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER); } // The rest of the cases are probably system apps or numeric fields, which should @@ -138,30 +145,14 @@ public class AppHacks { return false; } - sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); - sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); - sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER); - sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true); - sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true); - sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true); - sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true); + textField.sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); + textField.sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); + textField.sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER); + textField.sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true, false); + textField.sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true, false); + textField.sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true, false); + textField.sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true, false); return true; } - - - private boolean sendDownUpKeyEvents(int keyCode) { - return sendDownUpKeyEvents(keyCode, false); - } - - - private boolean sendDownUpKeyEvents(int keyCode, boolean shift) { - if (inputConnection != null) { - KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0, shift ? KeyEvent.META_SHIFT_ON : 0); - KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, shift ? KeyEvent.META_SHIFT_ON : 0); - return inputConnection.sendKeyEvent(downEvent) && inputConnection.sendKeyEvent(upEvent); - } - - return false; - } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/AbstractHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/AbstractHandler.java index 729b5a6b..f14e68b6 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/AbstractHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/AbstractHandler.java @@ -6,10 +6,11 @@ import android.view.inputmethod.InputConnection; import io.github.sspanak.tt9.ime.helpers.SuggestionOps; import io.github.sspanak.tt9.ime.modes.InputMode; +import io.github.sspanak.tt9.util.Ternary; abstract public class AbstractHandler extends InputMethodService { // hardware key handlers - abstract protected boolean onBack(); + abstract protected Ternary onBack(); abstract public boolean onBackspace(); abstract public boolean onHotkey(int keyCode, boolean repeat, boolean validateOnly); abstract protected boolean onNumber(int key, boolean hold, int repeat); diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java index 46dca251..431ba2ee 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java @@ -7,17 +7,17 @@ import io.github.sspanak.tt9.ime.modes.ModeABC; import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.ui.UI; import io.github.sspanak.tt9.ui.dialogs.AddWordDialog; +import io.github.sspanak.tt9.util.Clipboard; +import io.github.sspanak.tt9.util.Ternary; -abstract public class CommandHandler extends VoiceHandler { +abstract public class CommandHandler extends TextEditingHandler { @Override - protected boolean onBack() { - return super.onBack() || hideCommandPalette(); - } - - - @Override - public boolean onBackspace() { - return hideCommandPalette() || super.onBackspace(); + protected Ternary onBack() { + if (hideCommandPalette()) { + return Ternary.TRUE; + } else { + return super.onBack(); + } } @@ -44,9 +44,6 @@ abstract public class CommandHandler extends VoiceHandler { private void onCommand(int key) { switch (key) { - case 0: - changeKeyboard(); - break; case 1: showSettings(); break; @@ -56,6 +53,12 @@ abstract public class CommandHandler extends VoiceHandler { case 3: toggleVoiceInput(); break; + case 5: + showTextEditingPalette(); + break; + case 8: + changeKeyboard(); + break; } } @@ -63,6 +66,9 @@ abstract public class CommandHandler extends VoiceHandler { protected void resetStatus() { if (mainView.isCommandPaletteShown()) { statusBar.setText(R.string.commands_select_command); + } if (mainView.isTextEditingPaletteShown()) { + String preview = Clipboard.getPreview(this); + statusBar.setText(preview.isEmpty() ? getString(R.string.commands_select_command) : "[ \"" + preview + "\" ]"); } else { statusBar.setText(mInputMode); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java index c88ccbf7..17a2d490 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java @@ -1,21 +1,15 @@ package io.github.sspanak.tt9.ime; import android.view.KeyEvent; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; import io.github.sspanak.tt9.db.DictionaryLoader; import io.github.sspanak.tt9.ime.helpers.TextField; import io.github.sspanak.tt9.ime.modes.ModePredictive; -import io.github.sspanak.tt9.languages.LanguageCollection; -import io.github.sspanak.tt9.languages.LanguageKind; import io.github.sspanak.tt9.preferences.helpers.Hotkeys; import io.github.sspanak.tt9.ui.UI; +import io.github.sspanak.tt9.util.Ternary; public abstract class HotkeyHandler extends CommandHandler { - private boolean isSystemRTL; - - @Override protected void onInit() { super.onInit(); @@ -26,15 +20,14 @@ public abstract class HotkeyHandler extends CommandHandler { @Override - protected boolean onStart(InputConnection connection, EditorInfo field) { - isSystemRTL = LanguageKind.isRTL(LanguageCollection.getDefault(this)); - return super.onStart(connection, field); - } - - - @Override - public boolean onBack() { - return super.onBack() || settings.isMainLayoutNumpad(); + public Ternary onBack() { + if (super.onBack() == Ternary.TRUE) { + return Ternary.TRUE; + } else if (settings.isMainLayoutNumpad()) { + return Ternary.ALTERNATIVE; + } else { + return Ternary.FALSE; + } } @@ -102,7 +95,12 @@ public abstract class HotkeyHandler extends CommandHandler { public boolean onKeyMoveCursor(boolean backward) { - return appHacks.onMoveCursor(backward) || textField.moveCursor(backward); + if (textSelection.isEmpty()) { + return appHacks.onMoveCursor(backward) || textField.moveCursor(backward); + } else { + textSelection.clear(backward); + return true; + } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/KeyPadHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/KeyPadHandler.java index 0e2011f2..21b41f83 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/KeyPadHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/KeyPadHandler.java @@ -76,8 +76,13 @@ abstract class KeyPadHandler extends UiHandler { event.startTracking(); } + // on many devices there is a default back handler, so we must fall back to it when we don't + // perform any operation if (Key.isBack(keyCode)) { - return onBack() && super.onKeyDown(keyCode, event); + Key.setHandled(keyCode, onBack()); + return Key.isHandledInSuper(keyCode) ? super.onKeyDown(keyCode, event) : Key.isHandled(keyCode); + } else { + Key.setHandled(KeyEvent.KEYCODE_BACK, false); } return @@ -169,11 +174,11 @@ abstract class KeyPadHandler extends UiHandler { } if (Key.isBack(keyCode)) { - return onBack() && super.onKeyUp(keyCode, event); + return Key.isHandledInSuper(keyCode) ? super.onKeyUp(keyCode, event) : Key.isHandled(keyCode); } return - (Key.isOK(KeyEvent.KEYCODE_ENTER) && Key.isHandled(keyCode)) + (Key.isOK(keyCode) && Key.isHandled(KeyEvent.KEYCODE_ENTER)) || handleHotkey(keyCode, false, keyRepeatCounter > 0, false) || Key.isPoundOrStar(keyCode) && onText(String.valueOf((char) event.getUnicodeChar()), false) || super.onKeyUp(keyCode, event); // let the system handle the keys we don't care about (usually, the touch "buttons") diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/MainViewHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/MainViewHandler.java index 9ac44535..7a7fa448 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/MainViewHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/MainViewHandler.java @@ -29,6 +29,10 @@ abstract public class MainViewHandler extends HotkeyHandler { return mInputMode.getTextCase(); } + public boolean isInputLimited() { + return inputType.isLimited(); + } + public boolean isInputModeABC() { return mInputMode.getClass().equals(ModeABC.class); } @@ -49,6 +53,10 @@ abstract public class MainViewHandler extends HotkeyHandler { return mInputMode.is123() && inputType.isPhoneNumber(); } + public boolean isTextEditingActive() { + return mainView != null && mainView.isTextEditingPaletteShown(); + } + public boolean isVoiceInputActive() { return voiceInputOps != null && voiceInputOps.isListening(); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/TextEditingHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/TextEditingHandler.java new file mode 100644 index 00000000..99bedb1e --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ime/TextEditingHandler.java @@ -0,0 +1,111 @@ +package io.github.sspanak.tt9.ime; + +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +import io.github.sspanak.tt9.languages.LanguageCollection; +import io.github.sspanak.tt9.languages.LanguageKind; +import io.github.sspanak.tt9.util.Clipboard; +import io.github.sspanak.tt9.util.Ternary; + +abstract public class TextEditingHandler extends VoiceHandler { + protected boolean isSystemRTL; + + + @Override + protected boolean onStart(InputConnection connection, EditorInfo field) { + isSystemRTL = LanguageKind.isRTL(LanguageCollection.getDefault(this)); + return super.onStart(connection, field); + } + + + protected boolean onNumber(int key, boolean hold, int repeat) { + if (!shouldBeOff() && mainView.isTextEditingPaletteShown()) { + onCommand(key); + return true; + } + + return super.onNumber(key, hold, repeat); + } + + + @Override + protected Ternary onBack() { + if (hideTextEditingPalette()) { + return Ternary.TRUE; + } else { + return super.onBack(); + } + } + + + private void onCommand(int key) { + switch (key) { + case 0: + if (!mInputMode.isNumeric()) { + textField.setText(" "); + } + break; + case 1: + textSelection.selectNextChar(!isSystemRTL); + break; + case 2: + textSelection.clear(); + break; + case 3: + textSelection.selectNextChar(isSystemRTL); + break; + case 4: + textSelection.selectNextWord(!isSystemRTL); + break; + case 5: + textSelection.selectAll(); + break; + case 6: + textSelection.selectNextWord(isSystemRTL); + break; + case 7: + textSelection.cut(textField); + break; + case 8: + textSelection.copy(); + break; + case 9: + textSelection.paste(textField); + break; + } + } + + + public void showTextEditingPalette() { + if (inputType.isLimited() || mainView.isTextEditingPaletteShown()) { + return; + } + + suggestionOps.cancelDelayedAccept(); + suggestionOps.acceptIncomplete(); + mInputMode.reset(); + stopVoiceInput(); + + mainView.showTextEditingPalette(); + Clipboard.setOnChangeListener(this, this::resetStatus); + resetStatus(); + } + + + public boolean hideTextEditingPalette() { + if (!mainView.isTextEditingPaletteShown()) { + return false; + } + + if (settings.isMainLayoutNumpad() || settings.isMainLayoutStealth()) { + mainView.hideTextEditingPalette(); + } else { + mainView.showCommandPalette(); + } + + Clipboard.setOnChangeListener(this, null); + resetStatus(); + return true; + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java index e62eb4ba..c6ccc968 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java @@ -16,6 +16,7 @@ import io.github.sspanak.tt9.ime.helpers.CursorOps; import io.github.sspanak.tt9.ime.helpers.InputModeValidator; import io.github.sspanak.tt9.ime.helpers.SuggestionOps; import io.github.sspanak.tt9.ime.helpers.TextField; +import io.github.sspanak.tt9.ime.helpers.TextSelection; import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.ime.modes.ModePredictive; import io.github.sspanak.tt9.languages.Language; @@ -26,10 +27,11 @@ import io.github.sspanak.tt9.util.Text; public abstract class TypingHandler extends KeyPadHandler { // internal settings/data - @NonNull protected AppHacks appHacks = new AppHacks(null,null, null, null); + @NonNull protected AppHacks appHacks = new AppHacks(null,null, null, null, null); protected InputConnection currentInputConnection = null; @NonNull protected InputType inputType = new InputType(null, null); @NonNull protected TextField textField = new TextField(null, null); + @NonNull protected TextSelection textSelection = new TextSelection(this,null); protected SuggestionOps suggestionOps; boolean isEnabled = false; @@ -94,9 +96,10 @@ public abstract class TypingHandler extends KeyPadHandler { currentInputConnection = connection; inputType = new InputType(currentInputConnection, field); textField = new TextField(currentInputConnection, field); + textSelection = new TextSelection(this, currentInputConnection); // changing the TextField and notifying all interested classes is an atomic operation - appHacks = new AppHacks(settings, connection, inputType, textField); + appHacks = new AppHacks(settings, connection, inputType, textField, textSelection); suggestionOps.setTextField(textField); } @@ -288,6 +291,7 @@ public abstract class TypingHandler extends KeyPadHandler { // Logger.d("onUpdateSelection", "oldSelStart: " + oldSelStart + " oldSelEnd: " + oldSelEnd + " newSelStart: " + newSelStart + " oldSelEnd: " + oldSelEnd + " candidatesStart: " + candidatesStart + " candidatesEnd: " + candidatesEnd); super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd); + textSelection.onSelectionUpdate(newSelStart, newSelEnd); // in case the app has modified the InputField and moved the cursor without notifiying us... if (appHacks.onUpdateSelection(mInputMode, suggestionOps, oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd)) { diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/VoiceHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/VoiceHandler.java index dfa6bfa9..4ac34334 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/VoiceHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/VoiceHandler.java @@ -5,6 +5,7 @@ import io.github.sspanak.tt9.ime.voice.VoiceInputError; import io.github.sspanak.tt9.ime.voice.VoiceInputOps; import io.github.sspanak.tt9.ui.dialogs.RequestPermissionDialog; import io.github.sspanak.tt9.util.Logger; +import io.github.sspanak.tt9.util.Ternary; abstract class VoiceHandler extends TypingHandler { private final static String LOG_TAG = VoiceHandler.class.getSimpleName(); @@ -24,9 +25,9 @@ abstract class VoiceHandler extends TypingHandler { } @Override - protected boolean onBack() { + protected Ternary onBack() { stopVoiceInput(); - return false; // we don't want to abort other operations, we just silently stop voice input + return Ternary.FALSE; // we don't want to abort other operations, we just silently stop voice input } @Override diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/Key.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/Key.java index ab360df8..e4f88de5 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/Key.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/Key.java @@ -5,19 +5,30 @@ import android.view.KeyEvent; import java.util.HashMap; import io.github.sspanak.tt9.preferences.settings.SettingsStore; +import io.github.sspanak.tt9.util.Ternary; public class Key { - private static final HashMap handledKeys = new HashMap<>(); + private static final HashMap handledKeys = new HashMap<>(); + + + public static void setHandled(int keyCode, Ternary handled) { + handledKeys.put(keyCode, handled); + } public static boolean setHandled(int keyCode, boolean handled) { - handledKeys.put(keyCode, handled); + handledKeys.put(keyCode, handled ? Ternary.TRUE : Ternary.FALSE); return handled; } public static boolean isHandled(int keyCode) { - return Boolean.TRUE.equals(handledKeys.get(keyCode)); + return handledKeys.containsKey(keyCode) && handledKeys.get(keyCode) == Ternary.TRUE; + } + + + public static boolean isHandledInSuper(int keyCode) { + return handledKeys.containsKey(keyCode) && handledKeys.get(keyCode) == Ternary.ALTERNATIVE; } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/OrientationListener.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/OrientationListener.java index fa015968..a6d744fe 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/OrientationListener.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/OrientationListener.java @@ -1,36 +1,35 @@ package io.github.sspanak.tt9.ime.helpers; import android.content.Context; +import android.content.res.Configuration; import androidx.annotation.NonNull; public class OrientationListener extends android.view.OrientationEventListener { - private static final short ORIENTATION_LANDSCAPE = 1; - private static final short ORIENTATION_UNKNOWN = 0; - private static final short ORIENTATION_PORTRAIT = -1; - - private short previousOrientation = ORIENTATION_UNKNOWN; + private final Configuration configuration; private final Runnable onChange; + private int previousOrientation = Configuration.ORIENTATION_UNDEFINED; + public OrientationListener(@NonNull Context context, @NonNull Runnable onChange) { super(context); + configuration = context.getResources().getConfiguration(); this.onChange = onChange; } @Override public void onOrientationChanged(int orientation) { - short currentOrientation; - - if (orientation > 345 || orientation < 15 || (orientation > 165 && orientation < 195)) { - currentOrientation = ORIENTATION_PORTRAIT; - } else if ((orientation > 75 && orientation < 105) || (orientation > 255 && orientation < 285)) { - currentOrientation = ORIENTATION_LANDSCAPE; - } else { + if ( + (orientation > 15 && orientation < 75) + || (orientation > 105 && orientation < 165) + || (orientation > 195 && orientation < 255) + || (orientation > 285 && orientation < 345) + ) { return; } - if (currentOrientation != previousOrientation) { - previousOrientation = currentOrientation; + if (previousOrientation != configuration.orientation) { + previousOrientation = configuration.orientation; onChange.run(); } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java index eb7c2b65..637b0c5f 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java @@ -245,10 +245,26 @@ public class TextField extends InputField { return false; } - int keyCode = backward ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT; - connection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); - connection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); + sendDownUpKeyEvents(backward ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT); return true; } + + + public boolean sendDownUpKeyEvents(int keyCode) { + return sendDownUpKeyEvents(keyCode, false, false); + } + + + public boolean sendDownUpKeyEvents(int keyCode, boolean shift, boolean ctrl) { + if (connection != null) { + int metaState = shift ? KeyEvent.META_SHIFT_ON : 0; + metaState |= ctrl ? KeyEvent.META_CTRL_ON : 0; + KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0, metaState); + KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, metaState); + return connection.sendKeyEvent(downEvent) && connection.sendKeyEvent(upEvent); + } + + return false; + } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextSelection.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextSelection.java new file mode 100644 index 00000000..448a3666 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextSelection.java @@ -0,0 +1,160 @@ +package io.github.sspanak.tt9.ime.helpers; + +import android.content.Context; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.github.sspanak.tt9.util.Clipboard; + +public class TextSelection { + @Nullable private final InputConnection connection; + private final Context context; + private int currentStart = 0; + private int currentEnd = 0; + + + public TextSelection(Context context, @Nullable InputConnection connection) { + this.context = context; + this.connection = connection; + detectCursorPosition(); + } + + + public void onSelectionUpdate(int start, int end) { + currentStart = start; + currentEnd = end; + } + + + public boolean isEmpty() { + return currentStart == currentEnd; + } + + + public void clear() { + if (connection != null) { + connection.setSelection(currentEnd, currentEnd); + } + } + + + public void clear(boolean backward) { + if (connection != null) { + connection.setSelection( + backward ? Math.min(currentStart, currentEnd) : Math.max(currentStart, currentEnd), + backward ? Math.min(currentStart, currentEnd) : Math.max(currentStart, currentEnd) + ); + } + } + + + public void selectAll() { + if (connection != null) { + connection.performContextMenuAction(android.R.id.selectAll); + } + } + + + public void selectNextChar(boolean backward) { + if (connection != null) { + connection.setSelection(currentStart, currentEnd + (backward ? -1 : 1)); + } + } + + + public void selectNextWord(boolean backward) { + if (connection == null) { + return; + } + + connection.setSelection(currentStart, getNextWordPosition(backward)); + } + + + public boolean copy() { + CharSequence selectedText = getSelectedText(); + if (selectedText.length() == 0) { + return false; + } + + Clipboard.copy(context, selectedText); + return true; + } + + + public void cut(@NonNull TextField textField) { + if (copy()) { + textField.setText(""); + } + } + + + public void paste(@NonNull TextField textField) { + String clipboardText = Clipboard.paste(context); + if (!clipboardText.isEmpty()) { + textField.setText(clipboardText); + } + } + + + private int getNextWordPosition(boolean backward) { + if (connection == null) { + return currentEnd + (backward ? -1 : 1); + } + + ExtractedText extractedText = connection.getExtractedText(new ExtractedTextRequest(), 0); + if (extractedText == null) { + return currentEnd + (backward ? -1 : 1); + } + + int increment = backward ? -1 : 1; + int textLength = extractedText.text.length(); + for (int ch = currentEnd + increment; ch >= 0 && ch < textLength; ch += increment) { + if (!Character.isWhitespace(extractedText.text.charAt(ch))) { + continue; + } + + if (ch >= currentStart) { + return ch; + } else if (ch + 1 != currentEnd) { + return ch + 1; + } + } + + return backward ? 0 : textLength; + } + + + private void detectCursorPosition() { + if (connection == null) { + return; + } + + ExtractedText extractedText = connection.getExtractedText(new ExtractedTextRequest(), 0); + if (extractedText != null) { + currentStart = extractedText.startOffset + extractedText.selectionStart; + currentEnd = extractedText.startOffset + extractedText.selectionEnd; + } + } + + + private CharSequence getSelectedText() { + if (connection == null) { + return ""; + } + + ExtractedText extractedText = connection.getExtractedText(new ExtractedTextRequest(), 0); + if (extractedText == null) { + return ""; + } + + + int start = Math.min(extractedText.selectionStart, extractedText.selectionEnd); + int end = Math.max(extractedText.selectionStart, extractedText.selectionEnd); + return extractedText.text.subSequence(start, end); + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/items/ItemText.java b/app/src/main/java/io/github/sspanak/tt9/preferences/items/ItemText.java index 2c6fb640..3b43fd84 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/items/ItemText.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/items/ItemText.java @@ -1,8 +1,5 @@ package io.github.sspanak.tt9.preferences.items; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; import android.os.Build; import androidx.preference.Preference; @@ -10,6 +7,7 @@ import androidx.preference.Preference; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.preferences.PreferencesActivity; import io.github.sspanak.tt9.ui.UI; +import io.github.sspanak.tt9.util.Clipboard; public class ItemText extends ItemClickable { private final PreferencesActivity activity; @@ -21,16 +19,18 @@ public class ItemText extends ItemClickable { @Override protected boolean onClick(Preference p) { - if (activity == null) { + if (activity == null || p.getSummary() == null) { return false; } - ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); - String label = activity.getString(R.string.app_name_short) + " / " + item.getTitle(); - clipboard.setPrimaryClip(ClipData.newPlainText(label , p.getSummary())); + Clipboard.copy( + activity, + activity.getString(R.string.app_name_short) + " / " + item.getTitle(), + p.getSummary() + ); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { - UI.toast(activity, "Text copied."); + UI.toast(activity, "\"" + Clipboard.getPreview(activity) + "\" copied."); } return true; @@ -43,6 +43,4 @@ public class ItemText extends ItemClickable { return this; } - - } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java index 2a79d483..e637df6e 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java @@ -6,7 +6,8 @@ import android.content.Context; public class SettingsStore extends SettingsUI { public SettingsStore(Context context) { super(context); } - /************* internal settings *************/ + /************* internal settings *************/ + public final static int CLIPBOARD_PREVIEW_LENGTH = 20; public final static int DELETE_WORDS_SEARCH_DELAY = 500; // ms public final static int DICTIONARY_AUTO_LOAD_COOLDOWN_TIME = 1200000; // 20 minutes in ms public final static int DICTIONARY_DOWNLOAD_CONNECTION_TIMEOUT = 10000; // ms diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/BaseMainLayout.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/BaseMainLayout.java index 6249cc4d..c6989365 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/BaseMainLayout.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/BaseMainLayout.java @@ -77,15 +77,23 @@ abstract class BaseMainLayout { } - int getHeight() { + int getHeight(boolean forceRecalculate) { return 0; } - void resetHeight() {} + + int getHeight() { + return getHeight(false); + } + abstract void showCommandPalette(); abstract void hideCommandPalette(); abstract boolean isCommandPaletteShown(); + abstract void showTextEditingPalette(); + abstract void hideTextEditingPalette(); + abstract boolean isTextEditingPaletteShown(); + /** * render diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java index 6533c872..2c6745da 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java @@ -15,10 +15,14 @@ import java.util.Arrays; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.hacks.DeviceInfo; import io.github.sspanak.tt9.ime.TraditionalT9; +import io.github.sspanak.tt9.ui.main.keys.SoftCommandKey; import io.github.sspanak.tt9.ui.main.keys.SoftKey; import io.github.sspanak.tt9.ui.main.keys.SoftKeySettings; +import io.github.sspanak.tt9.ui.main.keys.SoftNumberKey; +import io.github.sspanak.tt9.ui.main.keys.SoftPunctuationKey; class MainLayoutNumpad extends BaseMainLayout { + private boolean isTextEditingShown = false; private int height; @@ -82,6 +86,80 @@ class MainLayoutNumpad extends BaseMainLayout { @Override boolean isCommandPaletteShown() { return false; } + @Override + void showTextEditingPalette() { + isTextEditingShown = true; + + for (SoftKey key : getKeys()) { + int keyId = key.getId(); + + if (keyId == R.id.soft_key_0) { + key.setEnabled(tt9 != null && !tt9.isInputModeNumeric()); + } else if (key.getClass().equals(SoftNumberKey.class)) { + key.setVisibility(View.GONE); + } + + if (key.getClass().equals(SoftPunctuationKey.class)) { + key.setVisibility(View.INVISIBLE); + } + + if (key.getClass().equals(SoftCommandKey.class)) { + key.setVisibility(View.VISIBLE); + } + + if (keyId == R.id.soft_key_rf3) { + key.render(); + } + + if ( + keyId == R.id.soft_key_add_word + || keyId == R.id.soft_key_input_mode + || keyId == R.id.soft_key_language + || keyId == R.id.soft_key_filter_suggestions + ) { + key.setEnabled(false); + } + } + } + + @Override + void hideTextEditingPalette() { + isTextEditingShown = false; + + for (SoftKey key : getKeys()) { + if (key.getClass().equals(SoftNumberKey.class) || key.getClass().equals(SoftPunctuationKey.class)) { + key.setVisibility(View.VISIBLE); + key.setEnabled(true); + } + + if (key.getClass().equals(SoftCommandKey.class)) { + key.setVisibility(View.GONE); + } + + + int keyId = key.getId(); + + if (keyId == R.id.soft_key_rf3) { + key.render(); + } + + if ( + keyId == R.id.soft_key_add_word + || keyId == R.id.soft_key_input_mode + || keyId == R.id.soft_key_language + || keyId == R.id.soft_key_filter_suggestions + ) { + key.setEnabled(true); + } + } + } + + @Override + boolean isTextEditingPaletteShown() { + return isTextEditingShown; + } + + /** * Uses the key height from the settings, but if it takes up too much of the screen, it will * be adjusted so that the entire Main View would take up around 50% of the screen in landscape mode @@ -120,8 +198,8 @@ class MainLayoutNumpad extends BaseMainLayout { } - int getHeight() { - if (height <= 0) { + int getHeight(boolean forceRecalculate) { + if (height <= 0 || forceRecalculate) { Resources resources = tt9.getResources(); height = getKeyHeightCompat() * 4 + resources.getDimensionPixelSize(R.dimen.numpad_candidate_height) @@ -132,11 +210,6 @@ class MainLayoutNumpad extends BaseMainLayout { } - void resetHeight() { - height = 0; - } - - @Override void render() { getView(); @@ -178,8 +251,7 @@ class MainLayoutNumpad extends BaseMainLayout { } } - ViewGroup statusBarContainer = view.findViewById(R.id.status_bar_container); - keys.addAll(getKeysFromContainer(statusBarContainer)); + keys.addAll(getKeysFromContainer(view.findViewById(R.id.status_bar_container))); return keys; } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutSmall.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutSmall.java index 3105bbbf..3a51c905 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutSmall.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutSmall.java @@ -1,6 +1,5 @@ package io.github.sspanak.tt9.ui.main; -import android.content.res.Resources; import android.view.View; import android.widget.LinearLayout; @@ -14,19 +13,20 @@ import io.github.sspanak.tt9.ui.main.keys.SoftKey; import io.github.sspanak.tt9.ui.main.keys.SoftKeyCommandPalette; class MainLayoutSmall extends MainLayoutTray { - private int height; - MainLayoutSmall(TraditionalT9 tt9) { super(tt9); } - int getHeight() { - if (height <= 0) { - Resources resources = tt9.getResources(); - height = - resources.getDimensionPixelSize(R.dimen.soft_key_height) + - resources.getDimensionPixelSize(R.dimen.candidate_height); + @Override + int getHeight(boolean forceRecalculate) { + if (height <= 0 || forceRecalculate) { + height = super.getHeight(forceRecalculate); + + if (!isCommandPaletteShown() && !isTextEditingPaletteShown()) { + height += tt9.getResources().getDimensionPixelSize(R.dimen.soft_key_height); + } } + return height; } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutStealth.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutStealth.java index 1584ece4..91f5c8c7 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutStealth.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutStealth.java @@ -5,11 +5,23 @@ import io.github.sspanak.tt9.ime.TraditionalT9; class MainLayoutStealth extends BaseMainLayout { private boolean isCommandPaletteShown = false; + private boolean isTextEditingPaletteShown = false; MainLayoutStealth(TraditionalT9 tt9) { super(tt9, R.layout.main_stealth); } - @Override void showCommandPalette() { isCommandPaletteShown = true; } + @Override void showCommandPalette() { + isCommandPaletteShown = true; + isTextEditingPaletteShown = false; + } @Override void hideCommandPalette() { isCommandPaletteShown = false; } @Override boolean isCommandPaletteShown() { return isCommandPaletteShown; } + + @Override void showTextEditingPalette() { + isTextEditingPaletteShown = true; + isCommandPaletteShown = false; + } + @Override void hideTextEditingPalette() { isTextEditingPaletteShown = false; } + @Override boolean isTextEditingPaletteShown() { return isTextEditingPaletteShown; } + @Override void render() {} } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutTray.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutTray.java index 111dc024..f6e023da 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutTray.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutTray.java @@ -16,17 +16,22 @@ import io.github.sspanak.tt9.ime.TraditionalT9; import io.github.sspanak.tt9.ui.main.keys.SoftKey; class MainLayoutTray extends BaseMainLayout { - private int height; + protected int height; MainLayoutTray(TraditionalT9 tt9) { super(tt9, R.layout.main_small); } - int getHeight() { - if (height <= 0) { + int getHeight(boolean forceRecalculate) { + if (height <= 0 || forceRecalculate) { Resources resources = tt9.getResources(); height = resources.getDimensionPixelSize(R.dimen.candidate_height); + + if (isCommandPaletteShown() || isTextEditingPaletteShown()) { + height += resources.getDimensionPixelSize(R.dimen.numpad_key_height); + } } + return height; } @@ -37,16 +42,46 @@ class MainLayoutTray extends BaseMainLayout { } void showCommandPalette() { - view.findViewById(R.id.main_command_keys).setVisibility(LinearLayout.VISIBLE); + view.findViewById(R.id.text_editing_container).setVisibility(LinearLayout.GONE); view.findViewById(R.id.main_soft_keys).setVisibility(LinearLayout.GONE); + view.findViewById(R.id.main_command_keys).setVisibility(LinearLayout.VISIBLE); + + height = 0; + getHeight(); } void hideCommandPalette() { view.findViewById(R.id.main_command_keys).setVisibility(LinearLayout.GONE); + + height = 0; + getHeight(); } boolean isCommandPaletteShown() { - return view.findViewById(R.id.main_command_keys).getVisibility() == LinearLayout.VISIBLE; + return view != null && view.findViewById(R.id.main_command_keys).getVisibility() == LinearLayout.VISIBLE; + } + + @Override + void showTextEditingPalette() { + view.findViewById(R.id.main_command_keys).setVisibility(LinearLayout.GONE); + view.findViewById(R.id.main_soft_keys).setVisibility(LinearLayout.GONE); + view.findViewById(R.id.text_editing_container).setVisibility(LinearLayout.VISIBLE); + + height = 0; + getHeight(); + } + + @Override + void hideTextEditingPalette() { + view.findViewById(R.id.text_editing_container).setVisibility(LinearLayout.GONE); + + height = 0; + getHeight(); + } + + @Override + boolean isTextEditingPaletteShown() { + return view != null && view.findViewById(R.id.text_editing_container).getVisibility() == LinearLayout.VISIBLE; } protected Drawable getBackgroundColor(@NonNull View contextView, boolean dark) { @@ -71,6 +106,7 @@ class MainLayoutTray extends BaseMainLayout { // background view.findViewById(R.id.main_command_keys).setBackground(getBackgroundColor(view, dark)); + view.findViewById(R.id.text_editing_container).setBackground(getBackgroundColor(view, dark)); // text for (SoftKey key : getKeys()) { @@ -100,6 +136,7 @@ class MainLayoutTray extends BaseMainLayout { protected ArrayList getKeys() { if (view != null && keys.isEmpty()) { keys.addAll(getKeysFromContainer(view.findViewById(R.id.main_command_keys))); + keys.addAll(getKeysFromContainer(view.findViewById(R.id.text_editing_keys_small))); } return keys; } @@ -108,9 +145,19 @@ class MainLayoutTray extends BaseMainLayout { return new ArrayList<>(Arrays.asList( view.findViewById(R.id.separator_top), view.findViewById(R.id.separator_candidates_bottom), + view.findViewById(R.id.separator_1_1), + view.findViewById(R.id.separator_1_2), view.findViewById(R.id.separator_2_1), view.findViewById(R.id.separator_2_2), - view.findViewById(R.id.separator_3_1) + view.findViewById(R.id.separator_10_1), + view.findViewById(R.id.separator_10_2), + view.findViewById(R.id.separator_10_2), + view.findViewById(R.id.separator_10_3), + view.findViewById(R.id.separator_10_4), + view.findViewById(R.id.separator_10_5), + view.findViewById(R.id.separator_10_6), + view.findViewById(R.id.separator_10_7), + view.findViewById(R.id.separator_10_8) )); } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainView.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainView.java index dc8b9d95..1e0a6cc9 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainView.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainView.java @@ -50,6 +50,8 @@ public class MainView { } public void render() { + main.hideCommandPalette(); + main.hideTextEditingPalette(); main.render(); } @@ -72,4 +74,20 @@ public class MainView { public boolean isCommandPaletteShown() { return main != null && main.isCommandPaletteShown(); } + + public void showTextEditingPalette() { + if (main != null) { + main.showTextEditingPalette(); + } + } + + public void hideTextEditingPalette() { + if (main != null) { + main.hideTextEditingPalette(); + } + } + + public boolean isTextEditingPaletteShown() { + return main != null && main.isTextEditingPaletteShown(); + } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/ResizableMainView.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/ResizableMainView.java index 38cc921c..3777bc34 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/ResizableMainView.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/ResizableMainView.java @@ -24,7 +24,7 @@ public class ResizableMainView extends MainView implements View.OnAttachStateCha public ResizableMainView(TraditionalT9 tt9) { super(tt9); - resetHeight(); + calculateSnapHeights(); } @@ -35,27 +35,9 @@ public class ResizableMainView extends MainView implements View.OnAttachStateCha } - private void calculateInitialHeight() { - if (main == null) { - return; - } - - if (tt9.getSettings().isMainLayoutNumpad()) { - height = heightNumpad; - } else if (tt9.getSettings().isMainLayoutSmall()) { - height = heightSmall; - } else { - height = heightTray; - } - } - - @Override public boolean createInputView() { - if (!super.createInputView()) { - // recalculate the total height in case the user has changed the key height in the settings - resetHeight(); return false; } @@ -79,8 +61,6 @@ public class ResizableMainView extends MainView implements View.OnAttachStateCha public void onOrientationChanged() { - calculateSnapHeights(); - calculateInitialHeight(); hideCommandPalette(); render(); } @@ -211,14 +191,51 @@ public class ResizableMainView extends MainView implements View.OnAttachStateCha return true; } + private void fitMain() { + calculateSnapHeights(); + int heightLow, heightHigh, heightMain = main.getHeight(true); - private void resetHeight() { - if (main != null) { - main.resetHeight(); + if (main instanceof MainLayoutNumpad) { + heightLow = heightSmall; + heightHigh = heightNumpad; + } else if (main instanceof MainLayoutSmall) { + heightLow = 0; + heightHigh = Math.max(heightSmall, heightMain); // make room for the command palette + } else { + heightLow = 0; + heightHigh = Math.max(heightTray, heightMain); // make room for the command palette } - calculateSnapHeights(); - calculateInitialHeight(); - setHeight(height, heightSmall, heightNumpad); + setHeight(heightMain, heightLow, heightHigh); + } + + @Override + public void showCommandPalette() { + super.showCommandPalette(); + fitMain(); + } + + @Override + public void hideCommandPalette() { + super.hideCommandPalette(); + fitMain(); + } + + @Override + public void showTextEditingPalette() { + super.showTextEditingPalette(); + fitMain(); + } + + @Override + public void hideTextEditingPalette() { + super.hideTextEditingPalette(); + fitMain(); + } + + @Override + public void render() { + super.render(); + fitMain(); } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftCommandKey.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftCommandKey.java index fded55e2..458f18a0 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftCommandKey.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftCommandKey.java @@ -1,8 +1,14 @@ package io.github.sspanak.tt9.ui.main.keys; import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.drawable.Drawable; +import android.os.Build; import android.util.AttributeSet; +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.core.widget.TextViewCompat; + import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.Characters; @@ -12,46 +18,73 @@ public class SoftCommandKey extends SoftNumberKey { public SoftCommandKey(Context context, AttributeSet attrs) { super(context, attrs);} public SoftCommandKey(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr);} + @Override protected void handleHold() {} + + @Override + public void setDarkTheme(boolean darkEnabled) { + super.setDarkTheme(darkEnabled); + + final int color = darkEnabled ? R.color.dark_button_text : R.color.button_text; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + TextViewCompat.setCompoundDrawableTintList(this, ColorStateList.valueOf(getContext().getColor(color))); + } else { + setDarkThemeLegacy(color); + } + } + + + private void setDarkThemeLegacy(int color) { + Drawable[] icons = getCompoundDrawables(); + + if (icons.length >= 4 && icons[3] != null) { + Drawable icon = DrawableCompat.wrap(icons[3]); + DrawableCompat.setTint(icon, getResources().getColor(color)); + setCompoundDrawables(null, null, null, icon); + } + } + + + protected String getTextSubTitle(int resId) { + setTextSize(SettingsStore.SOFT_KEY_TITLE_SIZE); + return getContext().getString(resId); + } + + @Override protected String getTitle() { return getNumber(getId()) + ""; } - private String getTextSubTitle(int resId) { - setTextSize(SettingsStore.SOFT_KEY_TITLE_SIZE); - return getContext().getString(resId); - } @Override protected String getSubTitle() { - int number = getNumber(getId()); - boolean noIconSupport = Characters.noEmojiSupported(); + int keyId = getId(); - switch (number) { - case 0: - return noIconSupport ? getTextSubTitle(R.string.virtual_key_change_keyboard) : "⌨"; - case 1: - return noIconSupport ? getTextSubTitle(R.string.virtual_key_settings) : "⚙"; - case 2: - return "+"; - case 3: - return "🎤"; -// case 5: -// return "✂"; - } + // command palette + if (keyId == R.id.soft_key_1) return noIconSupport ? getTextSubTitle(R.string.virtual_key_settings) : "⚙"; + if (keyId == R.id.soft_key_2) return "+"; + if (keyId == R.id.soft_key_8) return noIconSupport ? getTextSubTitle(R.string.virtual_key_change_keyboard) : "⌨"; return null; } + @Override - public void render() { - if (tt9 != null && tt9.isVoiceInputMissing() && getNumber(getId()) == 3) { - setVisibility(GONE); - } else { - super.render(); - } + protected int getNumber(int keyId) { + if (keyId == R.id.soft_key_101) return 1; + if (keyId == R.id.soft_key_102) return 2; + if (keyId == R.id.soft_key_103) return 3; + if (keyId == R.id.soft_key_104) return 4; + if (keyId == R.id.soft_key_105) return 5; + if (keyId == R.id.soft_key_106) return 6; + if (keyId == R.id.soft_key_107) return 7; + if (keyId == R.id.soft_key_108) return 8; + if (keyId == R.id.soft_key_109) return 9; + + return super.getNumber(keyId); } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKey.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKey.java index cc469cdb..13135cb7 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKey.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKey.java @@ -155,13 +155,6 @@ public class SoftKey extends androidx.appcompat.widget.AppCompatButton implement protected boolean handleRelease() { - if (!validateTT9Handler()) { - return false; - } - - int keyId = getId(); - if (keyId == R.id.soft_key_voice_input) { tt9.toggleVoiceInput(); return true; } - return false; } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyF3.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyF3.java new file mode 100644 index 00000000..aafd4ca8 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyF3.java @@ -0,0 +1,29 @@ +package io.github.sspanak.tt9.ui.main.keys; + +import android.content.Context; +import android.util.AttributeSet; + +public class SoftKeyF3 extends SoftCommandKey { + public SoftKeyF3(Context context) { + super(context); + } + + public SoftKeyF3(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SoftKeyF3(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected String getSubTitle() { + return "🎤"; + } + + @Override + public void render() { + setVisibility(tt9 != null && tt9.isVoiceInputMissing() ? GONE : VISIBLE); + super.render(); + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyF5.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyF5.java new file mode 100644 index 00000000..cb793f54 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyF5.java @@ -0,0 +1,32 @@ +package io.github.sspanak.tt9.ui.main.keys; + +import android.content.Context; +import android.util.AttributeSet; + +import io.github.sspanak.tt9.R; +import io.github.sspanak.tt9.util.Characters; + +public class SoftKeyF5 extends SoftCommandKey { + public SoftKeyF5(Context context) { + super(context); + } + + public SoftKeyF5(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SoftKeyF5(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected String getSubTitle() { + return Characters.noEmojiSupported() ? getTextSubTitle(R.string.virtual_key_text_editing) : "✂"; + } + + @Override + public void render() { + setVisibility(tt9 != null && tt9.isInputLimited() ? GONE : VISIBLE); + super.render(); + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyRF3.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyRF3.java new file mode 100644 index 00000000..3fd281ee --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyRF3.java @@ -0,0 +1,88 @@ +package io.github.sspanak.tt9.ui.main.keys; + +import android.content.Context; +import android.util.AttributeSet; + +import io.github.sspanak.tt9.R; +import io.github.sspanak.tt9.preferences.settings.SettingsStore; + +public class SoftKeyRF3 extends SoftKey { + public SoftKeyRF3(Context context) { super(context); setFontSize(); } + public SoftKeyRF3(Context context, AttributeSet attrs) { super(context, attrs); setFontSize(); } + public SoftKeyRF3(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setFontSize(); } + + private void setFontSize() { + complexLabelTitleSize = SettingsStore.SOFT_KEY_COMPLEX_LABEL_TITLE_RELATIVE_SIZE / 0.85f; + complexLabelSubTitleSize = SettingsStore.SOFT_KEY_COMPLEX_LABEL_SUB_TITLE_RELATIVE_SIZE / 0.85f; + } + + private boolean isVoiceInputMissing() { + return tt9 != null && tt9.isVoiceInputMissing(); + } + + private boolean isTextEditingMissing() { + return tt9 != null && tt9.isInputLimited(); + } + + private boolean isTextEdtingActive() { + return tt9 != null && tt9.isTextEditingActive(); + } + + @Override + protected void handleHold() { + if (!validateTT9Handler() || isTextEdtingActive()) { + return; + } + + preventRepeat(); + + if (tt9.isVoiceInputActive()) { + tt9.toggleVoiceInput(); + } else { + tt9.showTextEditingPalette(); + } + } + + @Override + protected boolean handleRelease() { + if (!validateTT9Handler()) { + return false; + } + + if (isTextEdtingActive()) { + tt9.hideTextEditingPalette(); + } else { + tt9.toggleVoiceInput(); + } + return true; + } + + @Override + protected String getTitle() { + if (isTextEdtingActive()) { + if (tt9 == null) { + return "ABC"; + } else if (tt9.isInputModeNumeric()) { + return "123"; + } else if (tt9.getLanguage() != null) { + return tt9.getLanguage().getAbcString().toUpperCase(tt9.getLanguage().getLocale()); + } + } + + return isTextEditingMissing() && !isVoiceInputMissing() ? "🎤" : getContext().getString(R.string.virtual_key_text_editing).toUpperCase(); + } + + @Override + protected String getSubTitle() { + return isTextEdtingActive() || (!isVoiceInputMissing() && isTextEditingMissing()) ? null : "🎤"; + } + + @Override + public void render() { + if (isVoiceInputMissing() && isTextEditingMissing()) { + setVisibility(INVISIBLE); + } else { + super.render(); + } + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyTextEdit.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyTextEdit.java new file mode 100644 index 00000000..216e4060 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyTextEdit.java @@ -0,0 +1,59 @@ +package io.github.sspanak.tt9.ui.main.keys; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; + +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.core.widget.TextViewCompat; + +import io.github.sspanak.tt9.R; + +public class SoftKeyTextEdit extends SoftNumberKey { + public SoftKeyTextEdit(Context context) { + super(context); + } + + public SoftKeyTextEdit(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SoftKeyTextEdit(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public void setDarkTheme(boolean darkEnabled) { + super.setDarkTheme(darkEnabled); + + final int color = darkEnabled ? R.color.dark_button_text : R.color.button_text; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + TextViewCompat.setCompoundDrawableTintList(this, ColorStateList.valueOf(getContext().getColor(color))); + } else { + setDarkThemeLegacy(color); + } + } + + + private void setDarkThemeLegacy(int color) { + Drawable[] icons = getCompoundDrawables(); + + if (icons.length >= 4 && icons[3] != null) { + Drawable icon = DrawableCompat.wrap(icons[3]); + DrawableCompat.setTint(icon, getResources().getColor(color)); + setCompoundDrawables(null, null, null, icon); + } + } + + private Drawable getIcon(int resId) { + return getResources().getDrawable(resId); + } + + @Override + protected String getSubTitle() { + return super.getSubTitle(); + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftVoiceInputKey.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftVoiceInputKey.java deleted file mode 100644 index e6a63bf0..00000000 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftVoiceInputKey.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.sspanak.tt9.ui.main.keys; - -import android.content.Context; -import android.util.AttributeSet; - -public class SoftVoiceInputKey extends SoftKey { - public SoftVoiceInputKey(Context context) { super(context); } - public SoftVoiceInputKey(Context context, AttributeSet attrs) { super(context, attrs); } - public SoftVoiceInputKey(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } - - @Override - protected String getTitle() { - return "🎤"; - } - - @Override - public void render() { - if (tt9 != null && tt9.isVoiceInputMissing()) { - setVisibility(INVISIBLE); - } else { - super.render(); - } - } -} diff --git a/app/src/main/java/io/github/sspanak/tt9/util/Clipboard.java b/app/src/main/java/io/github/sspanak/tt9/util/Clipboard.java new file mode 100644 index 00000000..ead5ee26 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/util/Clipboard.java @@ -0,0 +1,75 @@ +package io.github.sspanak.tt9.util; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; + +import androidx.annotation.NonNull; + +import io.github.sspanak.tt9.R; +import io.github.sspanak.tt9.preferences.settings.SettingsStore; + +public class Clipboard { + private static Runnable externalChangeListener; + private static boolean ignoreNextChange = false; + + @NonNull private static CharSequence lastText = ""; + + public static void copy(@NonNull Context context, @NonNull CharSequence label, @NonNull CharSequence text) { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText(label, text)); + + // Android clipboard works unreliably on all versions from 5 to 14, even when invoked from + // the context menu. So, just in case, we keep a backup of the text. + lastText = text; + + ignoreNextChange = true; + } + + public static void copy(@NonNull Context context, @NonNull CharSequence text) { + String label = context.getString(R.string.app_name_short) + " / text"; + copy(context, label, text); + } + + @NonNull public static String paste(@NonNull Context context) { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = clipboard.getPrimaryClip(); + + // Try using the shared clipboard, but if Android has failed preserving it, use our backup. + CharSequence text = clip != null && clip.getItemCount() > 0 ? clip.getItemAt(0).getText() : ""; + text = text == null || text.length() == 0 ? lastText : text; + + return text.toString(); + } + + + @NonNull public static String getPreview(@NonNull Context context) { + String text = paste(context); + + if (text.length() > SettingsStore.CLIPBOARD_PREVIEW_LENGTH) { + return text.substring(0, SettingsStore.CLIPBOARD_PREVIEW_LENGTH) + "..."; + } + + return text; + } + + public static void setOnChangeListener(Context context, Runnable newListener) { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + + if (newListener != null) { + clipboard.addPrimaryClipChangedListener(Clipboard::changeListener); + } else if (externalChangeListener != null) { + clipboard.removePrimaryClipChangedListener(Clipboard::changeListener); + } + + externalChangeListener = newListener; + } + + private static void changeListener() { + if (ignoreNextChange) { + ignoreNextChange = false; + } else if (externalChangeListener != null) { + externalChangeListener.run(); + } + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/util/Ternary.java b/app/src/main/java/io/github/sspanak/tt9/util/Ternary.java new file mode 100644 index 00000000..3a10c835 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/util/Ternary.java @@ -0,0 +1,7 @@ +package io.github.sspanak.tt9.util; + +public enum Ternary { + FALSE, + TRUE, + ALTERNATIVE +} diff --git a/app/src/main/res/drawable-anydpi-v24/ic_dpad_left.xml b/app/src/main/res/drawable-anydpi-v24/ic_dpad_left.xml new file mode 100644 index 00000000..f2fc31ef --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_dpad_left.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable-anydpi-v24/ic_dpad_right.xml b/app/src/main/res/drawable-anydpi-v24/ic_dpad_right.xml new file mode 100644 index 00000000..d1061c4c --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_dpad_right.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable-anydpi-v24/ic_txt_copy.xml b/app/src/main/res/drawable-anydpi-v24/ic_txt_copy.xml new file mode 100644 index 00000000..7b16d5ad --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_txt_copy.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable-anydpi-v24/ic_txt_cut.xml b/app/src/main/res/drawable-anydpi-v24/ic_txt_cut.xml new file mode 100644 index 00000000..953e2ab8 --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_txt_cut.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable-anydpi-v24/ic_txt_paste.xml b/app/src/main/res/drawable-anydpi-v24/ic_txt_paste.xml new file mode 100644 index 00000000..c56b246d --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_txt_paste.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable-anydpi-v24/ic_txt_select_all.xml b/app/src/main/res/drawable-anydpi-v24/ic_txt_select_all.xml new file mode 100644 index 00000000..e8eb4876 --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_txt_select_all.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable-anydpi-v24/ic_txt_select_none.xml b/app/src/main/res/drawable-anydpi-v24/ic_txt_select_none.xml new file mode 100644 index 00000000..22c424ac --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_txt_select_none.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable-anydpi-v24/ic_txt_word_back.xml b/app/src/main/res/drawable-anydpi-v24/ic_txt_word_back.xml new file mode 100644 index 00000000..4234cb53 --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_txt_word_back.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable-anydpi-v24/ic_txt_word_forward.xml b/app/src/main/res/drawable-anydpi-v24/ic_txt_word_forward.xml new file mode 100644 index 00000000..6034ca6b --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_txt_word_forward.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable-hdpi/ic_dpad_left.png b/app/src/main/res/drawable-hdpi/ic_dpad_left.png new file mode 100644 index 0000000000000000000000000000000000000000..18fb2162663da5cb31a783a2a4b063c3963d5628 GIT binary patch literal 282 zcmV+#0pjf zY6o&3zqonDC+^e+#2539PfMsih~pSl7epCG)dNwEQ9le)l%wi`6lJLTAjNT18<1k8 zi!sDVzm5=jsP-U5I?4v5NJH6y6#FP!kYXEU4^ph7`hXP6sD2=|fa(iU9yP|YA;d`Q gn2<+}v9QYR1Dx>)f;{~Mr~m)}07*qoM6N<$f`G7i5C8xG literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_dpad_right.png b/app/src/main/res/drawable-hdpi/ic_dpad_right.png new file mode 100644 index 0000000000000000000000000000000000000000..102347ef36f7e335578344425f6ef18097874887 GIT binary patch literal 270 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBu6nvShE&Aey zY+B5{?z#G5-c2D@gpNU1_<_aeC^ zZvwV7P6@dFXV2k_d_nS6i!IoKY6JgBuF!Y8$oHy0a7*L8hy;^~DlZY#w1B*{MM57cRPvIQcM<4IR|Oym};1@QDJ-D;nj9xw5R!%NUe RG#KbX22WQ%mvv4FO#qqvW{dy; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_txt_copy.png b/app/src/main/res/drawable-hdpi/ic_txt_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..47e2df15e0e9d5bc24efbc093b945cd47aa218e1 GIT binary patch literal 324 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$UKzz{u_C;uum9_jZOY-ys8@R!a`& z_Ir!G+1OP0E-p6s79pd`o>*~2Xwmv@m*xw&X#bq7v+zdc&kV^Zzvapd52n3}TUj-C zf2WAS0zLlPV_BD;g)I+#puNz+QSjTR=^;OL4~MMyGyCeJ0;kNi&3jdsUVT}^oYrzr zajWDH9%dt_m!B=KPOjQI;Y^#zG6nr3uYsEK*LF;F;0j*i*=(u!`Sz`@lz_MkfB7#h zaTGW5n9LS?Lcv$6M6uZ<^Fk{Rir}5rfaZ^a3zvWW&+&AEugC)@wwo@DdMQBW2J3|! zzc1|Ds^r#WmoNC|ovBC0*UHrnfA?p${1iW`8gzga=+tYW&)-iHU(cVV{H;E{85lYY Mp00i_>zopr06RpAt^fc4 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_txt_cut.png b/app/src/main/res/drawable-hdpi/ic_txt_cut.png new file mode 100644 index 0000000000000000000000000000000000000000..8a4b6671d070895c76ee1caa78226502f0e84bff GIT binary patch literal 613 zcmV-r0-F7aP)QgIJ}Kvp0s5C{YU!2=)=6_rRH0Es{# zhWXjqGCLjGyS8_f@Fjn8xtZDdySMG_c46T!KtwC7Lslj_X06-2(&46ytTyYNhWTJ! zu$F9HDNrzTn|g40WG&nJyr7o#zq*67aAz<;4rsKQbk->>V9l-?E9Pvn-c(M@=KKT|F(U<;O6Vs+1;_4GUf0Gw zsHhn!%2ZO%#v_z2`mu&+5;K4UO_hlCQFn5%&R$jC_abzlNzDKb)+qfZVF7QbILzFu zIB4iGG&GHw2q#^dLM&jc89XLEGZA-S+>@AxTjPR6DJVkH0M~NQBfpsl3RoR8&^QK- zC_H-nhFOZ6(Qxq&tHtVyGFC$d_84VqIy0ldz|z3OGgY3M2pUKu4{NGiGZ8eB=8-ej zxm=e&D;6}A#*tG>cMH{-LL+H7j`B^Rfi%j?Y)tVW+Y}x_mvv3yYkwdOJaB(hY%)_Q zWL=p7$MMxrG&@r$kisG)jc_gZJc^kj|K*Ao>kU=?a;4$z2MTK{j_rp%k(9Unpl}e3 zPaQTUQIwJ^@r00000NkvXXu0mjfGsy`i literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_txt_paste.png b/app/src/main/res/drawable-hdpi/ic_txt_paste.png new file mode 100644 index 0000000000000000000000000000000000000000..ff916fb69e6fe955cf7dfec9736d2db41aa58155 GIT binary patch literal 351 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$UKzz^LZw;uum9_jdY5-ev;F4*iia*=eXH>@(oi4eved{zk{ujIaLY``GuT8($ zZ1i6<$Ru#vM*aBpp0q& literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_txt_select_all.png b/app/src/main/res/drawable-hdpi/ic_txt_select_all.png new file mode 100644 index 0000000000000000000000000000000000000000..dac7a1d2432a45e72747171df1bc48393a46143d GIT binary patch literal 456 zcmV;(0XP1MP)#DcIn3gnA*!y0ZD{19lw)`9>lG*Ixh z)`ISy_Z^G7=L+PDwOLbSThfn$NpBaGJs8J?zLFcX&z}VoF@3{=Jnr-jMZX2Wq_>ND zNr1(%VHnrJ%`ziI~1&L0;O{(KWD)m=-ig zfqYHaQ}YXwfA`PNTCNsW_!s!5<3c0Up*)? y8+^UMqjXsx8@dkVe2z)-I8#f1u*zqqG<^e9(sf+((KdGg0000}M{P)0UK!^nJ?`ZD#T^a>C`ucB5-`Hu1f<+87%sZXCpnC6}) zM^a|{k@{l@d2}h$*wP?DllmPK>n^W=EqG z#twZUMbgvgrpP_Lgw=nmt&llMG^#yn(~y8ph_VvRNEKNi9o(k?n3wU7FxsJ z9a4MBqn#j6%8y|o`R%KG@%U_@2)R60s^6kcfrOCjDv7>1AhjGT;RNCuL*3=}t@+yfJ(xC3RPI9H%dT!Z2Qlu|O5GFE1!(C4vRtCshi zz0cbA9;~vSdM0bV@7GtHb5^I5$z(G1FtaH<*Yi5iU&B2;uMI<1@d-`uVxTsG$M}UO zdLIqtaXfN^3N$keDvNmM`}Ws`C@$b_FsMLtWe^>~U3`}j^%io2cJNIG6=;4C#AfhP zyN>vU2v6gMP6*MCLSz$Pv=Mi84RGuFp*3JWM z3GaR1nk?k8cAl_3Yx;x@v34H$>$tDynM~$i@D0IE4k%E&u8RNw002ovPDHLkV1lSb BwqyVR literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_txt_word_forward.png b/app/src/main/res/drawable-hdpi/ic_txt_word_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..952fb2a0e32f8732fbe938d14ea515824ae2205b GIT binary patch literal 425 zcmV;a0apHrP)ROF08tR}1q6KutAz#k30ABG@d-p5eFgCe#6q-J3v00xEmW@O zvKg2hGs#StBCzwp*G%rY+>lH%tyUtD7)C^-2e)}W1mrK_46oM$jZWb&Jb@E@-3J-) z3OD&$FxfzcN4UZ@Rakkwv12&SHQq>mAms~o;Rx5dpuBPqm$}Xh`9F~12F`PR5gg3; zgdMJ}MBD=@?{Jqh42OtoILjF)Qa+I398Pi8z{Lf;!5z*OB7OrYFL0gn3Ky1e1}8b6 zL;MF)zTpuZr5+(X!8JRdL&63!+`&bwM<{w@eW4pO&FT!Ke8ByzM_q2=LemCn#~4T- z*^<>G6n$ie65)~k$l#?OqaGnVOQe@txBUj^6)r3x+P1%d)0{PMaRJdjjQZ=?;|#+g zLVq1|oN*%Vfg1C3ZPfAO0j_dQ6;@t%VUz<}qmHl#aEsSNK>kW%9r6>2#4zy(|2YT^ T>ao2+00000NkvXXu0mjfC5*GJ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_dpad_left.png b/app/src/main/res/drawable-mdpi/ic_dpad_left.png new file mode 100644 index 0000000000000000000000000000000000000000..357f6a9017120969e1cf00f47e62c602e1c1f498 GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjvpiiKLn`9VUUlSZHV|k{^jC`g zk`lM<_?qr}k1g%*o|rIW#>@u$l*K>n1P-QFR!s`Cp1ptGB;NgRYTnlT*;}x@@D%Hv z3cUb{^UPY0Mc6~+mrE>oT`0x==Hd3aclCE2WB7Z#zHaS>T8F=nzc@5p`Tj4HM`7!J z1u1rqTotwn>n2(_tWvgQSlRQ0F~p&WvB<;DK|O`fVb;&f?_V+m8oEv~pMQoO=t>4p LS3j3^P6<5G+1<|8&>W^D``;%{uQD#jt!v zwB7qW)*eTpfPNDO^;H758iYkTZZ`<;<@ntoY|C-HK{#D-AM+jUXY>Bs_t-NYIiM|+ zKHd5oJ;tr4?{Z0eUZvYGy}xq0Tq8;* SUlRa2mci52&t;ucLK6VKF;*1- literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_txt_copy.png b/app/src/main/res/drawable-mdpi/ic_txt_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..44df7a56a59bc23f022da5a271b44ee552d83d5b GIT binary patch literal 224 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj%ROBjLn`9-PIKgHb`W5(efGe< z_=BLG=5d?+O@{R3UUL*2eBFK_`}6CjD_`3) fe&#>7{|7^rnZ@E1N|;vKq$`q_Ih9{%dVnPv7j0bd__gx*3r$dwbW zehQ7Cnb*izvT9frz7#*%EzEY0Wh(PnfAacD>xvb^SyxjqlDVwkczvVwfFafv?nZ7X znMsY4U%Wn7zl^tsyP0oGz>VU{pQW}knaFGF8cqrmGCu+MeJG9X1*VBrtKC2gzgE73 zpaHbiTF&Z(hvhrdCcKm5!UGSBCfpb5_*G{L4ao1Cr0^yVYXb8aT3fZrd|i31Ffcc4 zZ$d-E#7t~%lXrg;JHsC>j*2^}F(jHS)Gy=Rnuy_Khuwz{UZwh<9oPE7xrT<&eYnA; vWY(}k6E&Rnlj(-|=*Q!tiF;7rwEx5xbNmq>krHL#00000NkvXXu0mjfy>GO- literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_txt_paste.png b/app/src/main/res/drawable-mdpi/ic_txt_paste.png new file mode 100644 index 0000000000000000000000000000000000000000..3e38eba1dc0ae4818855ebfe8c72a7e32be76f19 GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjOFUg1Ln`9VUOmWp*nr33qKr{M z`3nI_?c;nili3SoH#kk`jMotP<(P2r|GunQ{~Ci%#XJvvemAZD{LSC9e%*|!y7p$G zly2#^KbJe*XKQ~eJNaMEB=N}RvmLtss|DQdxqeH2f5PdF((jGsDT-wi|K70HVE87& zP|ej~%X(lQQ-VL^hUE-5f*Iauq6o*=6udLAv9Ow%RAW?ske!)tw{wv0|fO8wa-tpx0j@V&`+idw_*qox-QHN9|>rUAQO lnF9ovR<8}ftRYX|8E?C9vPn5xp2`3K002ovPDHLkV1jw^kzD`) literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_txt_select_none.png b/app/src/main/res/drawable-mdpi/ic_txt_select_none.png new file mode 100644 index 0000000000000000000000000000000000000000..70e0f0728b58e33a29beeb42e5b4227c07839c86 GIT binary patch literal 493 zcmV)m+Qb_Rn}!OYIuwxa>TN8kszYT2Cyl+u;-TDp{W(gnBy=Sdhp@gPT{ zPJ;2XY^8K1-Isva#27VTe9Z$Zlx`^v{U>!-01_XTvzj8@1 zo?GZij@}R7X|y#_6JTO%0V{p|IGIm(n4;YSYoaEAePN+-tZlYj7~jck@xCkjU7OVS1$m*Co(Q{ogLqJ2+x65u{WI$TQ2$Y6nVeY zUczyF zkKLUMC|u(6Pq{@q5k?od@?A?UU~q^tT+0oe1w;pUhhI`{=q?~U$0uz@q&vb#?Krj< z5ZvOYwmhyo#SfheVF4GXxX@{W0X}G$;ExtW2yU#zBl1{y$xCfUq?`AWBMfRQ^_hAs z??)TkZ+|oor4tKBMT^comU~oqEF5<4n)gBP3SYApG+4>wdQUMn1eN;Nk8KXA00000 LNkvXXu0mjfeTI2o literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_txt_word_forward.png b/app/src/main/res/drawable-mdpi/ic_txt_word_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..ce1710a79b57bb342ba9e3f183bdbf29e18a87dd GIT binary patch literal 288 zcmV+*0pI?KP)cZlQAyorcaS6z-sL0)<4P6p7CpOfvg+X0lT(iv1Xxvx6PVyV%37nbjM71&Rxt7Q_(|IHJ3F#4}l#|Q@i0000-M3j=($ zHxDWXV7vzr2e9SB?{-0Fu>fQ2dx!@Z;}CNI#u&srfYFbb3o!Z+^8v__AR82b?U0LFGiPJpoukr!aRM&t$<&k^|n#$!YcfN}qS%<2G)dqgdOHHdluyFt_h f*ovqN__tjENcmDbT^0?o00000NkvXXu0mjfZ`X~M literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_dpad_right.png b/app/src/main/res/drawable-xhdpi/ic_dpad_right.png new file mode 100644 index 0000000000000000000000000000000000000000..a3ba833e2d8c18c36a7b684900e73c7e34126de6 GIT binary patch literal 320 zcmV-G0l)r(nn~&faz~&-24zPI$`T*>!y?aRX1K4MK|LM^eU}F*V2iQ0S=KyRBf(x+NnDIt` zz_{^7U%=S$MnAxu*O)$lIpdAvfO+GMV}QBijs1Z6F91G2{(&jI=4jb%WM@y35Zo$=NHYK^xCK)vyH2h<#Ybwa8A>q&fO{I=~g z6>mJ6e0Db5fj4PeWd3zK?oFsOmiqFfKA}!{TmE^5$9tcD;PJh2$g^SIY&GsbrvH~U z{H@tixo|u4dNcQD|GTuf&T!}2y?A|Md6xHzbq6}s{mbP0l+XkKwn4H> literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_txt_cut.png b/app/src/main/res/drawable-xhdpi/ic_txt_cut.png new file mode 100644 index 0000000000000000000000000000000000000000..1cb1af037f5b63e6ded078fc3149f5ba969f687b GIT binary patch literal 809 zcmV+^1J?YBP)cT=+b`OTb}dp9S^cd{qD*<@|)Uje`ay*6to*zDBogV_%(xG{TU zwxe}sgNb`;_O&B=h__}NTE8i9qdtbuW)HQUsjyA6k8pQ9Wm{S&jQlFFqiS;mPCN%D z$F^2t&Zp9b*DlNs&0d;a_lxg1?KQX}C=<`}e(EBfI-J0pJxAwzVD?jdznQJ8?-)=n zK2BLUv0XUwk@$d>y%K-2r+%eiig<$lqmo>xPZulSKztX@)UO0o5YPNq4%JVAdm41P zQlDjDs`!A`FizA*K|JZt)h7pPi6@9h^ICnnSoyB_lVkN80yV`GxaEPeuYLqB|39fS zQZJVTs4bo#J))eXeA?VSbM5yI(&E3PphNngw)gL_+NUr8)^wot44jTY$@hA(-+i0Qf6 ziP^he>4LULOuq6n^(>_QAzuLLRLYUQr4wPB7<if`o{z+!vxJ^GK> nFL29_U9B@~7?583PX&Gf{G(R|K?-u$00000NkvXXu0mjf=3#^; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_txt_paste.png b/app/src/main/res/drawable-xhdpi/ic_txt_paste.png new file mode 100644 index 0000000000000000000000000000000000000000..413deeee9e097e35b8cbc3941cbb9c1b6686bbee GIT binary patch literal 440 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezr3(FxGpzIEGZjy`8zykJ(VbC3wk# z1q*!IcTW+?)cCPoq{hdtS&4Q2geE0-kSU>)43m+=YO{e{=aAg#&&F;y7vBoCf19`i(F1 z|IIdeHTBDeiH|e3yiWM>?D?m(s$CuDi&y^lSNMF)Xx_SzFaEDIoi>N1+r+MXG5<<% zOWN9V6}y5i+OJ&BaW-o1$C9Os>Vy0RXKl55{L;(y|B`u1CfTLOE>G?H=V|MjaV_iX z9jUYKIuClDDT@`jeHP@lSp2htb6%}~WmrD1pHX$G?5N9{7p$N4epVfnoL9O}%RuL&9$kA0s{Vs)K?J{(cY<3AuC$k;?oM#`MI3%?k*n`(w6vV4>lq5#ra%o)P)^kK z3}I>k)`)FAwd$}cz!mwueQo!$oTzIZyCtug`%yM|S0q)o7wRj!mnVY=P)s&CS3?bu z$uU<=VQK-!y@>nD zChv-*dU=3ivdOs`YJg0Rxni=(xf+HL0At@yls1~@H#nB z*E0kPUcgA6oyk!q@YMa2T~$|L^@K?1&MUf6NwT_IRHnP1Ek0iwoI8w9AFCyjzE!O4?sad znd0X?JQJRO&)7T8?Aj~&lU}j&&y43&JmXzwZ*OL%JOEyD+s+`1Kb86;b;X%Qa)&0% zNFU~i@&)A!%Fm=$Qdd&XY!(Io7avGUV0!|ApXid1@IdOf84!p{Nc(5%w`l<-x|Ro| z!@1Nw4N{^5rTwevvkifj&7&Y+;w|b6okTKix3y3CG3CdU>s`^9!{$klDKSYfON@*x z@)GqXyKAoffvAMEzeYW7NL|oexwx+%6k}}(j4a^j z;+C}}ut@=#?77sTsi%~?aMc9866g_-&--Yu;n3!tAaV*(?kJDZDS>{0Gs?40p%sm- zD0h_qLgvQC!~~YeJe^PpAD5LcMA1^4gsGJk(iEv?~^Oh z_({1@u!ewwOo?L11+Gz_ETKM9?!rnu`buV*C_Q3hbV^8m0-B5aBtiZuG?b(S9&-v| zdjd*S0`)7K2HYnJwkx2dBpN@8I8WjSI#Z1oSMvEi(XOnr*koXoRm!Mrl!4eREJ{3{b9egoy!YOp z_wL^M)$dc!a`%0n>#4in-TfL3kH_Qjcs$7f*wkZHz}yvTK#x}j!-uJJ3VN;#jO(Y4 zgIqGA=PQB1i_~j$k;)1%YL0r0b5V5x2KG`1)Q`DH=>m-CruL{Wn~N$7Fl?H-lMDQz zvqhkNlDg5%i1Gy((nal1pA!C$dh$Z+I`yG>hH?cMFh*T!s-nIuXc?l;G$(=f3(&kw zy=h_+>P>^=3)G8dw4itan);|CO-e&ElHgbmwNHJEJ&j@oXqcrQG$jkoQ8+S1-KsAK z`3fLh#&*%XjO9Y}Vh}C>TS!>|GRQ8#p$Y0*okh^h03IHtE_Aja=><5jM!l=6C<(bs z$hiPykX(Q#2B}k>ZWogqEC7d)H+2b`3-I7P^{kU+h;nDBdp%c@mIa=4vJ6pfX9@%$ zyoFA5x?N0e{kM<>u0F&T4vkY+>MVk02A~tmXbfC_ky+sCr-TJAf6Q6n>gTitF8|QP zAbb$Fk@6s_Coi;apw3@P3tauFx4`9X6$@P5*s{Rotwjs!+uU76otwKJkH_Qjbc$ax W`KZj^gG|Bz0000qKM_S>fX`pl_XzMTZX=l^=)50H3cS z>3f2V@VRI*1E^bMj@Oix>HCN*^7$yFaRBv!>>%B|wkYcRh3p}{ysi>y9w2prO!E37 zxMAW7ndY^nN&5lRE3(ctp}1MbJF8KS1gj8RptfxJe6V$T-&(B4Gol2V{Zk z^KgSUo{?p)4JP3Os83`U>ES-DaB&9*NI&=JNQw=Rxbz-T*fJ*Y5*lnEncx{t=tKjp`Gn**`L~j}oRI=1f0M zn|-1A%U8-R$+-apllB9ceW^FSt#T)ZH@3`fE$Zg(Dj$zQ{$q33Ip>_S>Kk;^sI8jt Rve^It002ovPDHLkV1l;6+{^#~ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_dpad_left.png b/app/src/main/res/drawable-xxhdpi/ic_dpad_left.png new file mode 100644 index 0000000000000000000000000000000000000000..e72d0fd77d1220aa97282f7d8ad1eb3566cd929e GIT binary patch literal 444 zcmV;t0Ym3`J3O;}TPbyG$E5en4ypf@Dp9;&1_O$lWP`)0Aadb}ZoWnxEx9n?5+duX!$S z;hW*81e&+NNuYTvI0^J_14ktg+c+vV5Z`fBTp;#wR7@a_;iz~(9LG_yfH;Sv;s9|T zN5uf*8jkW0#C06y8;E;2$}bT2agLIDF%?@ z8d4k}#d)MyK#Frn@qiS^kzxWVjv>VbQtTte22y-SN+88HQUbkW9Vvn4tso`PvISBC mtu;eRpsoIq^1)J;+361{dn12Fh%qey00006~vq-V?nSt1$BK$b|n2ePq|_zYy@BC!t0#zbO0kd24L zHXs`diS0l(4ifu-Yz!p!16lt_90RhxkvI-y{UT8Y$ofR09FX;gL|Gu~3yJbT)(;YO zfUFNB>H%4NB5z7whe|FA#K|-Oi)XY+@11eeiuPR-?Mtpx0qw~?~6<6u6CBMtm^y)=Q*GY#iyWX(x zg_-EDHPz)xdUb+dgSW2?$Y+#&sW$c1^Ysxie?rYY?H8@nUX^@zuW;c<&S|q>-uy2l z6CepkvzV=}Z1#QhhVRvn{nF(#Uerk+WMe!!b9$h7;P*?nx4#cuUvhtI!r6vL&+l(p z=(Ic#XppE0`;{O00e{}?m0Nd%{qBCzEj$|kYRq4S@rU>9w!8X1Lh=@S$pUuvg!ui* zsTuMYV)*$Exc}{&y0F%P+jhpiS2x!kz!69F2t_uwps(bIAo>sd<5=-Jj8!uXJRkJp2OX}N;>gUx7QMk6Nh)>{{Kk0Tlvu4 zinsYo=cs&kAa*MDZ`Zv8g}tR?TY&N6+}621B40k#5sII6j7EGOAm6cPI;J*gAr65% zI#RsS{wc)wi4Kl9*Zws@D{%w=cz5OG}T=!h5Ee+Y?rq?1IPX#aIUYjFtj0zKFMLrBb_P7-mV{l|hS#32%JO=pRC zqy2}Fm>+eLh(ql^223SRzz6riPMBuqa0AH{<2o3Dsl_31BjOaN+CPQ(-qpbo@3p@b z%pneu1SdLc1V_k&vvRmqfO8cav(|J2bBROXu0vexO2LjD>9_#gk&_dL9P7AJFsC>H zU7vw)W9%)$M;E||djE}WYR~!O2>7)QHF^NQ9A7i1uH~~p~2lftG zDH4f3-q%7qaRL~gmDeCE62J347TSvwkcRHTtpv6tDUy8t7;d6m%5>rc;MS*K!q!nM zHAI?ToET~R7Toe`?l#U&QcO-%Q={?}>xeY3I2yRYAXgtxym4aM=^+319k(z>q|Fk? z0z2*tuwW5sd&LQ`0AGlO5|OrFoB#{)1zE_6#B1C##0jt;BI&}kXE&ABow_=|wpE2U__TM)I$gvqBNhdyoTkg2%bgv7G<8sF_#)-o* zQfi!(V?L2E&4O8H;>mye&n!$Yk~H|0*gMsc+VsxK`I-OD!gM0BA>Uw2dc_IwJMUwm zy-3o)m%gEvWW@>4$NO4nClcI)!_=#IwI~uNKwlqYp^Zo=d-VJ#oF5mixE}oz${#(O z#0fCQ$6BZ>63Sj)c%tJ1aL>_lPd&&Z9ajqG771?rl7g?lJkT*3w153&K9P{$qhWc0 zibqn48ZS`IBNEb$GWmh_&w>0k$$SmcFbC58O)*|1)PISS7w!~?+J6j~QY38eYrxZp zguFnT{vvsMk+A(+5hf8SHG<|$-n0P)`PcYNxk^E6kx*|t)R7L3IM@C)K}(TPXG7i4 zD!>#Q>e`5e@-3M+dSW@rJ;mm1$!sRl4?3y`7J4T3o7k_|+)ce>MfybN>VW)|V=Na) z?Ivyr#)-7jc{-rY4-4GW?MwyiBCT|eOQ7ygQw**Gv<6NjUKZXKSlKWHb$;CBj?TL{ z;4a)lcej|`_<5WdxFG1ez$&LDFxgFm$o}c%AP$Y{s+i{TqIoW RPB;Jn002ovPDHLkV1n|!ay$S4 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_txt_paste.png b/app/src/main/res/drawable-xxhdpi/ic_txt_paste.png new file mode 100644 index 0000000000000000000000000000000000000000..49e754e04b568e88a998ce6c905533a0c158deb6 GIT binary patch literal 679 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@Zgyv2V9N7!aSW-5dppNATO?4Tl{rgU zDn!J0%LZXVme~kT%?56W3AuF?;m)ZXM?r@mR; zdULt?>nAn^3TN)wmfvenR6l#>P?OjZa7Tb{aa zEpp_6xl(@HlAcGUPpsn)9gUMhdAbn+lT`0SZCIeXMeD*GdePNA20zjc)I$ztaD0e0syBGDy;wj literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_txt_select_all.png b/app/src/main/res/drawable-xxhdpi/ic_txt_select_all.png new file mode 100644 index 0000000000000000000000000000000000000000..6c14303c6455a8065bb181698f223dd20cf584de GIT binary patch literal 853 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@Zgyv2V3zT8aSW-5dppO`PuWnwHF8rz zBa6_RW85k`j(*WiFyF`De&V6UA(;ioS|110-F@pKY#f&U%5&?%^#xzWJ)@TFUcXM} z`E9|8vqP@`sZ8AeFL~{Y=u5w3YlF+b=yLB{EB&i=OWEBh`_3=iazT5S|C(^dW%-0_ zkM2db>{Hc!|3bR#i|!Q1CGj5Fm+xInzMNdM;IfM8o@Kvc%WfH8)IL$hto35!zDvo zS?8#yy@3DMe2;%C@(edFSC}q3!N`8;MR~8hnAOqWL{#>dU$hsg^^U4vnE$GEU+Tf_ zTYtq(x|Szi6aBeC=6In@`fd+9{$E#DTs!{ka+Ce8c^2u8pE5aXqd(WTE|F);GzJ>! z?kB=7XbBYG>DPTgj(>&PhU3huINyA1$YQFtSuhJp$dcPqNZ@Z=fxN&=e&_awC1St! zKCn}`B;Uq&_@&O5>VvfoOYC`M+b&1G_}=up!J}Tnif8%e3-4LKGfw!#RhsQ)gu|r$o;OWj&9l8FvgGWMc}+R` z3p$NBusH_ozAY#1pGC`mey$+R{bK&&!v$8{RB+zr3HdgGGA>W%u>L$P?8^TFcc{3} z{-We(dts|pnV|aLi=}Ur{C;hFCSGXO_t@fG*~A*}XXo8ycdJhRw-A(=jBA6>gA>!W z%+7n0JnvuoGtKAY>?+;Q4I&?JADa5~_94-a?A#h>*|~o#)|7f5J748tZ)`O%y#N!$ zy%&0m7M=M&Nv#T$daBkeZw;RCr|5UQ+=eT4k+RNy)h^)?+P}n3hEF|H_3i4OQ%RtN jpwIpCV~5^x%T9Z}2m2r17G78l%q|R`u6{1-oD!M1jS6opezB9Q_Kx@^G%P|$%CIwGY=LqUNQmvkvWqD+~BB9E{|N($&Gx8yPM2s{AA z!6zOk=HQ*NC!QTI>rcAPo#Q*ZpOV@6+1=Y~G#ZV@_5$G8)&uP~_HN{y!SOXmlFh#E z*2P$8p4k3UgB>f)dNdedG)vgGb#3d))}e0C0(Wve$&ygFppYdJ-_u{%MHe#2lOze- zBZw?D*Na{7MJEV@lI->ux+q|wd4l-_k@SBz8-8o+w@wgJBnPv(5ku2-IW|Kf6N&huyl7>N( zGz^-gVbCPSv2XZ|t<$c)1v1t#D9LVrh`X7ktM5}xvp1#LEbM?4@=1{tJ5MTs(R~s2 zHR#c778oap%q7X_ehB*-^k_BTlJXf_M17j%w72X2&=WOP1Z zUxOaaW`Q0--S*lf8J$ho*PutUSztg=m#ImT(Yb_u4SF;?2`D5f6&)O4G+PbWpd=j{ zU^H75_62^>R~+Hql5}8z(QH+q|DJTny(HZ9JLWG-#5BL6bBLnxtVcwwv_Ei_M1_SZgrl$Qi|(>}&F3L<4C* z*gM?=>J?-#$AKG8Quvott)OoEERuMg#ap60a7RC3;I7?PYwD@y3EXi`v#d+adLmh0 zx4%-v9Fn#Tq)9r~bvp(6C7o%0CqZ0NlM^D6nw%J&qzmmg8jVJ6`~g6C&;^O@U$g)K N002ovPDHLkV1n|u+0y_3 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_txt_word_back.png b/app/src/main/res/drawable-xxhdpi/ic_txt_word_back.png new file mode 100644 index 0000000000000000000000000000000000000000..2e0fda0f34af5dde1fcbdf3961ea80668754d025 GIT binary patch literal 785 zcmV+s1Md8ZP)=aokE3&Y%vQbKjg_W|qDJ5m4ETk+H3zUVF zBubHp$e-75&bgZ6pnfYesTfDz|HfO%~ecqnB_nR~Gorwv@aU92S9LI4S$8knu zW|R6^7cj?^e%3h*wE^AN&)Wq5Rp=I4uh%+-fexe3$n;uUV7(W;%VtO>AO=~EX6J_L zp=+83%SGrC`kNc0`+yi?7kZr=s)w#?67063r#gdm84%|4(RuV+hU%f~n*f`6=p_0f z_5->J2;yalPFpj6WSj@AJuaR?FEF{YIIvC7=_ly zfVNA~HT4=pI{{&|54~6SacGaa&~gWQsXhfX8xRJI(G~lD>kLABRfSel=q&mr79lhj z5Y4xt=js@W_NxMIHlhdGy9DtAvi{b28vPWrQfS|@(BcUCqJ2veHy|3WL-*9B3femq zN>`v8+OHz<0;1U=^hq76qWwdlY&UwNy=qWDAet;g*R^w9)Mp$lUw|&4KhmZy^#Y>7 z40@$qW1wE6VCfe0SnDHEEg%xel;&9@-zZ)E?UBF@Nn- zjQNYhFf5H%&pR!(SI>FO4=;paX&iq!XsI24$zy(isu)Y>2;M16?Fe2TOXm>hGo8Uw z2q?qSIR<>$Qac8m$NV69F_z9x5wcs7^a9E-|1pc>IF92uj^j9v;}r7`Abc&?LsW9Q P00000NkvXXu0mjfA1Q01 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_txt_word_forward.png b/app/src/main/res/drawable-xxhdpi/ic_txt_word_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..ec63de07b6cd1e3c0af982d465c9272d886bc7e3 GIT binary patch literal 812 zcmV+{1JnG8P)Nkl zOQ=p!6bJC@P2>?N8IXaDBmtN-LXXYaLJb?>*&;~N_@#u#IaF~%5UawQ^D__HBk z9h3R9EMQ^FkekRtK9&;d-`BP9=YuO6q~0Q1_;@PC8a^Yt_}HLI4N`xQ^T>2QFSgPe z)HP%-pEC%V*dXT#vYOA0i5CDbkqvxaa^i208b=NxlXy%FT)glLIgL!^^OKS4203?- zB|I)PE=nFDD|t*3GP*(PBeI>xhQtLuz9IX0Ty)Z`LC$4lHqQ|P7pid!S;XU`k#-GI z&yjUJPhVVQgm=hRo+Aor-yro9IW7Tr;as2r-I82DX7F4=Nw)?$50K?NXBRwi3r~?X zJYO)y9L88l>JM+qk~NCr9!X*~_(SNxB9p z-ThufW_s3<(hX!jufa>wHpqE~tfh6N_8Qq#UW1nm(;%h0-=k9Oe)(V`x+OU)bz2e{ zwn4|Fc-+GW6VV2plMEY_!=Kn2WOI29UXr#!`cL{2GRw1$l(PIM{Xz2hn%~E@Ye||0 z=|*ue*Dm7$5oEbh{Ei&tnm#-*AidN$B^h37*u`pvTyA$n(w1~renc|7X>X5xD(F||3F-RP>A?jWa1RduyLA$jv~Q3-dahwFFI2GC zUuxJ3RJ0gpToQ_OvkX}dGYrtjluR*!INWPPg4XS96{uE&WA4^Fx{wYGR q-Ebe0>yKHCF~%5Uj4{TTT>J%9ge{{t|Kn%?0000 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:paddingBottom="@dimen/numpad_padding_bottom"/> diff --git a/app/src/main/res/layout/main_small.xml b/app/src/main/res/layout/main_small.xml index 562f155f..7957ca01 100644 --- a/app/src/main/res/layout/main_small.xml +++ b/app/src/main/res/layout/main_small.xml @@ -1,5 +1,6 @@ - @@ -35,105 +36,18 @@ android:id="@+id/separator_candidates_bottom" style="@style/numRowSeparator" /> - + - - - - - - - - - - - - - + android:visibility="gone" /> - - - - - - - - - - - - - - + diff --git a/app/src/main/res/layout/panel_command_palette.xml b/app/src/main/res/layout/panel_command_palette.xml new file mode 100644 index 00000000..1e05fce6 --- /dev/null +++ b/app/src/main/res/layout/panel_command_palette.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/panel_numpad.xml b/app/src/main/res/layout/panel_numpad.xml new file mode 100644 index 00000000..7115c23a --- /dev/null +++ b/app/src/main/res/layout/panel_numpad.xml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/panel_numpad_row_1.xml b/app/src/main/res/layout/panel_numpad_row_1.xml new file mode 100644 index 00000000..b50a2d8d --- /dev/null +++ b/app/src/main/res/layout/panel_numpad_row_1.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/main/res/layout/panel_numpad_row_2.xml b/app/src/main/res/layout/panel_numpad_row_2.xml new file mode 100644 index 00000000..bb98248c --- /dev/null +++ b/app/src/main/res/layout/panel_numpad_row_2.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/main/res/layout/panel_numpad_row_3.xml b/app/src/main/res/layout/panel_numpad_row_3.xml new file mode 100644 index 00000000..4f7e23e8 --- /dev/null +++ b/app/src/main/res/layout/panel_numpad_row_3.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/main/res/layout/panel_numpad_row_4.xml b/app/src/main/res/layout/panel_numpad_row_4.xml new file mode 100644 index 00000000..d1360a18 --- /dev/null +++ b/app/src/main/res/layout/panel_numpad_row_4.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/main/res/layout/panel_numpad_text_editing_row_1.xml b/app/src/main/res/layout/panel_numpad_text_editing_row_1.xml new file mode 100644 index 00000000..4a95fcf9 --- /dev/null +++ b/app/src/main/res/layout/panel_numpad_text_editing_row_1.xml @@ -0,0 +1,35 @@ + + + + + + + + diff --git a/app/src/main/res/layout/panel_numpad_text_editing_row_2.xml b/app/src/main/res/layout/panel_numpad_text_editing_row_2.xml new file mode 100644 index 00000000..898d905d --- /dev/null +++ b/app/src/main/res/layout/panel_numpad_text_editing_row_2.xml @@ -0,0 +1,35 @@ + + + + + + + + diff --git a/app/src/main/res/layout/panel_numpad_text_editing_row_3.xml b/app/src/main/res/layout/panel_numpad_text_editing_row_3.xml new file mode 100644 index 00000000..bf8c38a1 --- /dev/null +++ b/app/src/main/res/layout/panel_numpad_text_editing_row_3.xml @@ -0,0 +1,36 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/panel_small_function_keys.xml b/app/src/main/res/layout/panel_small_function_keys.xml new file mode 100644 index 00000000..84a50fee --- /dev/null +++ b/app/src/main/res/layout/panel_small_function_keys.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/panel_small_text_editing.xml b/app/src/main/res/layout/panel_small_text_editing.xml new file mode 100644 index 00000000..a5e10b98 --- /dev/null +++ b/app/src/main/res/layout/panel_small_text_editing.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 3e164c14..de83cc3d 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -6,6 +6,8 @@ 44dp 24sp + 6dp + 12sp 30dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b53ffbcf..c77e8fde 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -179,6 +179,7 @@ Del Mode Cfg + Copy Speak Turning off the microphone…