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 00000000..18fb2162 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_dpad_left.png differ 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 00000000..102347ef Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_dpad_right.png differ 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 00000000..47e2df15 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_txt_copy.png differ 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 00000000..8a4b6671 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_txt_cut.png differ 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 00000000..ff916fb6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_txt_paste.png differ 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 00000000..dac7a1d2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_txt_select_all.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_txt_select_none.png b/app/src/main/res/drawable-hdpi/ic_txt_select_none.png new file mode 100644 index 00000000..83272568 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_txt_select_none.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_txt_word_back.png b/app/src/main/res/drawable-hdpi/ic_txt_word_back.png new file mode 100644 index 00000000..07aa6d29 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_txt_word_back.png differ 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 00000000..952fb2a0 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_txt_word_forward.png differ 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 00000000..357f6a90 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_dpad_left.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_dpad_right.png b/app/src/main/res/drawable-mdpi/ic_dpad_right.png new file mode 100644 index 00000000..ad9c54e8 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_dpad_right.png differ 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 00000000..44df7a56 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_txt_copy.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_txt_cut.png b/app/src/main/res/drawable-mdpi/ic_txt_cut.png new file mode 100644 index 00000000..10f8bdc0 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_txt_cut.png differ 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 00000000..3e38eba1 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_txt_paste.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_txt_select_all.png b/app/src/main/res/drawable-mdpi/ic_txt_select_all.png new file mode 100644 index 00000000..4ee42467 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_txt_select_all.png differ 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 00000000..70e0f072 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_txt_select_none.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_txt_word_back.png b/app/src/main/res/drawable-mdpi/ic_txt_word_back.png new file mode 100644 index 00000000..a07d342d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_txt_word_back.png differ 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 00000000..ce1710a7 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_txt_word_forward.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_dpad_left.png b/app/src/main/res/drawable-xhdpi/ic_dpad_left.png new file mode 100644 index 00000000..b225ac15 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_dpad_left.png differ 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 00000000..a3ba833e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_dpad_right.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_txt_copy.png b/app/src/main/res/drawable-xhdpi/ic_txt_copy.png new file mode 100644 index 00000000..d236330c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_txt_copy.png differ 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 00000000..1cb1af03 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_txt_cut.png differ 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 00000000..413deeee Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_txt_paste.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_txt_select_all.png b/app/src/main/res/drawable-xhdpi/ic_txt_select_all.png new file mode 100644 index 00000000..430fc683 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_txt_select_all.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_txt_select_none.png b/app/src/main/res/drawable-xhdpi/ic_txt_select_none.png new file mode 100644 index 00000000..3e07bbbc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_txt_select_none.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_txt_word_back.png b/app/src/main/res/drawable-xhdpi/ic_txt_word_back.png new file mode 100644 index 00000000..50450520 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_txt_word_back.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_txt_word_forward.png b/app/src/main/res/drawable-xhdpi/ic_txt_word_forward.png new file mode 100644 index 00000000..d8daf542 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_txt_word_forward.png differ 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 00000000..e72d0fd7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_dpad_left.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_dpad_right.png b/app/src/main/res/drawable-xxhdpi/ic_dpad_right.png new file mode 100644 index 00000000..9dc94d44 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_dpad_right.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_txt_copy.png b/app/src/main/res/drawable-xxhdpi/ic_txt_copy.png new file mode 100644 index 00000000..ea02831b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_txt_copy.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_txt_cut.png b/app/src/main/res/drawable-xxhdpi/ic_txt_cut.png new file mode 100644 index 00000000..3d0a6a3d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_txt_cut.png differ 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 00000000..49e754e0 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_txt_paste.png differ 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 00000000..6c14303c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_txt_select_all.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_txt_select_none.png b/app/src/main/res/drawable-xxhdpi/ic_txt_select_none.png new file mode 100644 index 00000000..bb1aaaa3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_txt_select_none.png differ 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 00000000..2e0fda0f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_txt_word_back.png differ 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 00000000..ec63de07 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_txt_word_forward.png differ diff --git a/app/src/main/res/layout/main_numpad.xml b/app/src/main/res/layout/main_numpad.xml index 5c8309be..9c877785 100644 --- a/app/src/main/res/layout/main_numpad.xml +++ b/app/src/main/res/layout/main_numpad.xml @@ -86,248 +86,10 @@ android:id="@+id/separator_candidates_bottom" style="@style/numRowSeparator" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 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…