diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 36617d9f..2288cd99 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/ime/AbstractHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/AbstractHandler.java index e9a9f9b3..729b5a6b 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 @@ -1,7 +1,6 @@ package io.github.sspanak.tt9.ime; import android.inputmethodservice.InputMethodService; -import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -24,7 +23,7 @@ abstract public class AbstractHandler extends InputMethodService { abstract protected void setInputField(InputConnection inputConnection, EditorInfo inputField); // UI - abstract protected void createSuggestionBar(View mainView); + abstract protected void createSuggestionBar(); abstract protected void resetStatus(); 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 8aa5c534..dc9bc242 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 @@ -6,6 +6,7 @@ import io.github.sspanak.tt9.ime.modes.ModeABC; import io.github.sspanak.tt9.ime.voice.VoiceInputOps; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.preferences.settings.SettingsStore; +import io.github.sspanak.tt9.ui.main.ResizableMainView; abstract public class MainViewHandler extends HotkeyHandler { /**** Informational methods for the on-screen keyboard ****/ @@ -46,6 +47,10 @@ abstract public class MainViewHandler extends HotkeyHandler { return mLanguage; } + public ResizableMainView getMainView() { + return mainView; + } + public SettingsStore getSettings() { return settings; } 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 7694a5bc..94a9f726 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 @@ -1,7 +1,6 @@ package io.github.sspanak.tt9.ime; import android.view.KeyEvent; -import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -42,8 +41,8 @@ public abstract class TypingHandler extends KeyPadHandler { protected Language mLanguage; - protected void createSuggestionBar(View mainView) { - suggestionOps = new SuggestionOps(settings, mainView, this::onAcceptSuggestionsDelayed, this::onOK); + protected void createSuggestionBar() { + suggestionOps = new SuggestionOps(settings, mainView, textField, this::onAcceptSuggestionsDelayed, this::onOK); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/UiHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/UiHandler.java index 6007b4cb..3148e6d4 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/UiHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/UiHandler.java @@ -7,19 +7,19 @@ import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.hacks.DeviceInfo; import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.preferences.settings.SettingsStore; -import io.github.sspanak.tt9.ui.main.MainView; +import io.github.sspanak.tt9.ui.main.ResizableMainView; import io.github.sspanak.tt9.ui.tray.StatusBar; abstract class UiHandler extends AbstractHandler { protected SettingsStore settings; - protected MainView mainView = null; + protected ResizableMainView mainView = null; protected StatusBar statusBar = null; @Override protected void onInit() { if (mainView == null) { - mainView = new MainView(getFinalContext()); + mainView = new ResizableMainView(getFinalContext()); initTray(); } } @@ -27,12 +27,12 @@ abstract class UiHandler extends AbstractHandler { protected void initTray() { setInputView(mainView.getView()); - createSuggestionBar(mainView.getView()); + createSuggestionBar(); statusBar = new StatusBar(mainView.getView()); } - protected void initUi() { + public void initUi() { if (mainView.createInputView()) { initTray(); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java index fd06d5db..f7cd1e18 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java @@ -2,13 +2,13 @@ package io.github.sspanak.tt9.ime.helpers; import android.os.Handler; import android.os.Looper; -import android.view.View; import androidx.annotation.NonNull; import java.util.ArrayList; import io.github.sspanak.tt9.preferences.settings.SettingsStore; +import io.github.sspanak.tt9.ui.main.ResizableMainView; import io.github.sspanak.tt9.ui.tray.SuggestionsBar; import io.github.sspanak.tt9.util.ConsumerCompat; @@ -19,12 +19,12 @@ public class SuggestionOps { @NonNull private TextField textField; - public SuggestionOps(@NonNull SettingsStore settings, View mainView, @NonNull ConsumerCompat onDelayedAccept, @NonNull Runnable onSuggestionClick) { + public SuggestionOps(@NonNull SettingsStore settings, @NonNull ResizableMainView mainView, @NonNull TextField textField, @NonNull ConsumerCompat onDelayedAccept, @NonNull Runnable onSuggestionClick) { delayedAcceptHandler = new Handler(Looper.getMainLooper()); this.onDelayedAccept = onDelayedAccept; suggestionBar = new SuggestionsBar(settings, mainView, onSuggestionClick); - textField = new TextField(null, null); + this.textField = textField; } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/items/ItemDropDown.java b/app/src/main/java/io/github/sspanak/tt9/preferences/items/ItemDropDown.java index 781afdc3..9c0cfb75 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/items/ItemDropDown.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/items/ItemDropDown.java @@ -9,7 +9,7 @@ import java.util.LinkedHashMap; import io.github.sspanak.tt9.util.Logger; abstract public class ItemDropDown { - private final DropDownPreference item; + protected final DropDownPreference item; private LinkedHashMap values; public ItemDropDown(DropDownPreference item) { diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/appearance/AppearanceScreen.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/appearance/AppearanceScreen.java index 910d5107..d5099e88 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/appearance/AppearanceScreen.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/appearance/AppearanceScreen.java @@ -26,10 +26,18 @@ public class AppearanceScreen extends BaseScreenFragment { (new ItemStatusIcon(findPreference(ItemStatusIcon.NAME), activity.getSettings())).populate(); ItemHapticFeedback hapticFeedback = (new ItemHapticFeedback(findPreference(ItemHapticFeedback.NAME), activity.getSettings())).populate(); + ItemNumpadKeyHeight numpadKeyHeight = new ItemNumpadKeyHeight(findPreference(ItemNumpadKeyHeight.NAME), activity.getSettings()); ItemDropDown[] items = { new ItemSelectTheme(findPreference(ItemSelectTheme.NAME), activity), - new ItemSelectLayoutType(findPreference(ItemSelectLayoutType.NAME), activity, hapticFeedback::populate), - new ItemSelectSettingsFontSize(findPreference(ItemSelectSettingsFontSize.NAME), this) + new ItemSelectLayoutType( + findPreference(ItemSelectLayoutType.NAME), + activity, + (layout) -> { + hapticFeedback.onLayoutChange(layout); + numpadKeyHeight.onLayoutChange(layout); + }), + new ItemSelectSettingsFontSize(findPreference(ItemSelectSettingsFontSize.NAME), this), + numpadKeyHeight }; for (ItemDropDown item : items) { diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/appearance/ItemHapticFeedback.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/appearance/ItemHapticFeedback.java index 34c12a63..6225ce25 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/appearance/ItemHapticFeedback.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/appearance/ItemHapticFeedback.java @@ -22,16 +22,14 @@ class ItemHapticFeedback extends ItemClickable { } ItemHapticFeedback populate() { - return populate(settings.getMainViewLayout()); + onLayoutChange(settings.getMainViewLayout()); + return this; } - - ItemHapticFeedback populate(int mainViewLayout) { + void onLayoutChange(int mainViewLayout) { if (item != null) { item.setEnabled(mainViewLayout == SettingsStore.LAYOUT_NUMPAD || mainViewLayout == SettingsStore.LAYOUT_SMALL); ((SwitchPreferenceCompat) item).setChecked(settings.getHapticFeedback()); } - - return this; } } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/appearance/ItemNumpadKeyHeight.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/appearance/ItemNumpadKeyHeight.java new file mode 100644 index 00000000..0e6dc16a --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/appearance/ItemNumpadKeyHeight.java @@ -0,0 +1,48 @@ +package io.github.sspanak.tt9.preferences.screens.appearance; + +import androidx.preference.DropDownPreference; + +import java.util.LinkedHashMap; + +import io.github.sspanak.tt9.preferences.items.ItemDropDown; +import io.github.sspanak.tt9.preferences.settings.SettingsStore; + +public class ItemNumpadKeyHeight extends ItemDropDown { + public static final String NAME = "pref_numpad_key_height"; + + private final SettingsStore settings; + + public ItemNumpadKeyHeight(DropDownPreference item, SettingsStore settings) { + super(item); + this.settings = settings; + } + + @Override + public ItemDropDown populate() { + int baseSize = settings.getNumpadKeyDefaultHeight(); + + LinkedHashMap options = new LinkedHashMap<>(); + options.put((int) Math.round(baseSize * 0.7), "70 %"); + options.put((int) Math.round(baseSize * 0.75), "75 %"); + options.put((int) Math.round(baseSize * 0.8), "80 %"); + options.put((int) Math.round(baseSize * 0.85), "85 %"); + options.put((int) Math.round(baseSize * 0.9), "90 %"); + options.put((int) Math.round(baseSize * 0.95), "95 %"); + options.put(baseSize, "100 %"); + options.put((int) Math.round(baseSize * 1.1), "110 %"); + options.put((int) Math.round(baseSize * 1.2), "120 %"); + options.put((int) Math.round(baseSize * 1.33), "133 %"); + + super.populateIntegers(options); + super.setValue(settings.getNumpadKeyHeight() + ""); + onLayoutChange(settings.getMainViewLayout()); + + return this; + } + + void onLayoutChange(int mainViewLayout) { + if (item != null) { + item.setEnabled(mainViewLayout == SettingsStore.LAYOUT_NUMPAD); + } + } +} 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 1590a357..2a79d483 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 @@ -13,7 +13,9 @@ public class SettingsStore extends SettingsUI { public final static int DICTIONARY_DOWNLOAD_READ_TIMEOUT = 10000; // ms public final static int DICTIONARY_IMPORT_BATCH_SIZE = 5000; // words public final static int DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME = 250; // ms + public final static int RESIZE_THROTTLING_TIME = 60; // ms public final static byte SLOW_QUERY_TIME = 50; // ms + public final static int SOFT_KEY_DOUBLE_CLICK_DELAY = 500; // ms public final static int SOFT_KEY_REPEAT_DELAY = 40; // ms public final static int SOFT_KEY_TITLE_SIZE = 18; // sp public final static float SOFT_KEY_COMPLEX_LABEL_TITLE_RELATIVE_SIZE = 0.55f; diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsUI.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsUI.java index 3700d6fb..fb84b2b3 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsUI.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsUI.java @@ -5,7 +5,9 @@ import android.content.res.Configuration; import androidx.appcompat.app.AppCompatDelegate; +import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.hacks.DeviceInfo; +import io.github.sspanak.tt9.util.Logger; public class SettingsUI extends SettingsTyping { public final static int FONT_SIZE_DEFAULT = 0; @@ -36,6 +38,14 @@ public class SettingsUI extends SettingsTyping { return prefs.getBoolean("pref_haptic_feedback", true); } + public int getNumpadKeyDefaultHeight() { + return context.getResources().getDimensionPixelSize(R.dimen.numpad_key_height); + } + + public int getNumpadKeyHeight() { + return getStringifiedInt("pref_numpad_key_height", getNumpadKeyDefaultHeight()); + } + public int getSettingsFontSize() { int defaultSize = DeviceInfo.isQinF21() || DeviceInfo.isLgX100S() ? FONT_SIZE_LARGE : FONT_SIZE_DEFAULT; return getStringifiedInt("pref_font_size", defaultSize); @@ -45,6 +55,16 @@ public class SettingsUI extends SettingsTyping { return getStringifiedInt("pref_theme", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); } + public void setMainViewLayout(int layout) { + if (layout != LAYOUT_STEALTH && layout != LAYOUT_TRAY && layout != LAYOUT_SMALL && layout != LAYOUT_NUMPAD) { + Logger.w(getClass().getSimpleName(), "Ignoring invalid main view layout: " + layout); + return; + } + + prefsEditor.putString("pref_layout_type", Integer.toString(layout)); + prefsEditor.apply(); + } + public int getMainViewLayout() { int defaultLayout = LAYOUT_SMALL; if (DeviceInfo.noTouchScreen(context)) { 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 7cc8c892..c9a75588 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,6 +77,12 @@ abstract class BaseMainLayout { } + int getHeight() { + return 0; + } + + void resetHeight() {} + /** * render * Do all the necessary stuff to display the View. 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 32af0cd9..b1515c98 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 @@ -1,5 +1,6 @@ package io.github.sspanak.tt9.ui.main; +import android.content.res.Resources; import android.view.View; import android.view.ViewGroup; @@ -12,8 +13,12 @@ import java.util.Arrays; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.ime.TraditionalT9; import io.github.sspanak.tt9.ui.main.keys.SoftKey; +import io.github.sspanak.tt9.ui.main.keys.SoftKeySettings; class MainLayoutNumpad extends BaseMainLayout { + private int height; + + MainLayoutNumpad(TraditionalT9 tt9) { super(tt9, R.layout.main_numpad); } @@ -34,6 +39,7 @@ class MainLayoutNumpad extends BaseMainLayout { ); } + @Override void setDarkTheme(boolean dark) { if (view == null) { @@ -57,15 +63,66 @@ class MainLayoutNumpad extends BaseMainLayout { } } + + void setKeyHeight(int height) { + if (view == null || height <= 0) { + return; + } + + ViewGroup table = view.findViewById(R.id.main_soft_keys); + int tableRowsCount = table.getChildCount(); + + for (int rowId = 0; rowId < tableRowsCount; rowId++) { + View row = table.getChildAt(rowId); + ViewGroup.LayoutParams layout = row.getLayoutParams(); + if (layout != null) { + layout.height = height; + row.setLayoutParams(layout); + } + } + } + + + int getHeight() { + if (height <= 0) { + Resources resources = tt9.getResources(); + height = tt9.getSettings().getNumpadKeyHeight() * 4 + + resources.getDimensionPixelSize(R.dimen.numpad_candidate_height) + + resources.getDimensionPixelSize(R.dimen.numpad_padding_bottom) * 4; + } + + return height; + } + + + void resetHeight() { + height = 0; + } + + @Override void render() { getView(); + setKeyHeight(tt9 != null ? tt9.getSettings().getNumpadKeyHeight() : -1); enableClickHandlers(); for (SoftKey key : getKeys()) { key.render(); } } + + @Override + protected void enableClickHandlers() { + super.enableClickHandlers(); + + for (SoftKey key : getKeys()) { + if (key instanceof SoftKeySettings) { + ((SoftKeySettings) key).setMainView(tt9.getMainView()); + } + } + } + + @NonNull @Override protected ArrayList getKeys() { @@ -89,6 +146,7 @@ class MainLayoutNumpad extends BaseMainLayout { return keys; } + protected ArrayList getSeparators() { // it's fine... it's shorter, faster and easier to read than searching with 3 nested loops return new ArrayList<>(Arrays.asList( 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 1450713a..b332eb98 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,5 +1,6 @@ package io.github.sspanak.tt9.ui.main; +import android.content.res.Resources; import android.view.View; import android.widget.LinearLayout; @@ -10,12 +11,25 @@ import java.util.ArrayList; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.ime.TraditionalT9; 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); + } + return height; + } + @Override protected void setSoftKeysVisibility() { if (view != null) { @@ -23,6 +37,17 @@ class MainLayoutSmall extends MainLayoutTray { } } + @Override + protected void enableClickHandlers() { + super.enableClickHandlers(); + + for (SoftKey key : getKeys()) { + if (key instanceof SoftKeyCommandPalette) { + ((SoftKeyCommandPalette) key).setMainView(tt9.getMainView()); + } + } + } + @NonNull @Override protected ArrayList getKeys() { 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 c042771c..0d6366ad 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 @@ -1,5 +1,6 @@ package io.github.sspanak.tt9.ui.main; +import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.view.View; import android.widget.LinearLayout; @@ -15,10 +16,20 @@ import io.github.sspanak.tt9.ime.TraditionalT9; import io.github.sspanak.tt9.ui.main.keys.SoftKey; class MainLayoutTray extends BaseMainLayout { + private int height; + MainLayoutTray(TraditionalT9 tt9) { super(tt9, R.layout.main_small); } + int getHeight() { + if (height <= 0) { + Resources resources = tt9.getResources(); + height = resources.getDimensionPixelSize(R.dimen.candidate_height); + } + return height; + } + protected void setSoftKeysVisibility() { if (view != null) { view.findViewById(R.id.main_soft_keys).setVisibility(LinearLayout.GONE); 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 68bace7b..be333779 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 @@ -7,10 +7,11 @@ import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.Logger; public class MainView { - private final TraditionalT9 tt9; - private BaseMainLayout main; + protected final TraditionalT9 tt9; + protected BaseMainLayout main; - public MainView(TraditionalT9 tt9) { + + protected MainView(TraditionalT9 tt9) { this.tt9 = tt9; forceCreateInputView(); @@ -32,6 +33,7 @@ public class MainView { } main.render(); + return true; } 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 new file mode 100644 index 00000000..ba7ce94c --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/ResizableMainView.java @@ -0,0 +1,200 @@ +package io.github.sspanak.tt9.ui.main; + +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; + +import io.github.sspanak.tt9.ime.TraditionalT9; +import io.github.sspanak.tt9.preferences.settings.SettingsStore; + +public class ResizableMainView extends MainView implements View.OnAttachStateChangeListener { + private int height; + private float resizeStartY; + private long lastResizeTime; + + private int heightNumpad; + private int heightSmall; + private int heightTray; + + + public ResizableMainView(TraditionalT9 tt9) { + super(tt9); + resetHeight(); + } + + + private void calculateSnapHeights() { + heightNumpad = new MainLayoutNumpad(tt9).getHeight(); + heightSmall = new MainLayoutSmall(tt9).getHeight(); + heightTray = new MainLayoutTray(tt9).getHeight(); + } + + + 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; + } + + main.getView().removeOnAttachStateChangeListener(this); + main.getView().addOnAttachStateChangeListener(this); + + return true; + } + + + private void onCreateAdjustHeight() { + if (tt9.getSettings().isMainLayoutNumpad() && height > heightSmall && height <= heightNumpad) { + setHeight(height, heightSmall, heightNumpad); + } + } + + + public void onResizeStart(float startY) { + resizeStartY = startY; + } + + + public void onResize(float currentY) { + int resizeDelta = (int) (resizeStartY - currentY); + resizeStartY = currentY; + + if (resizeDelta < 0) { + shrink(resizeDelta); + } else if (resizeDelta > 0) { + expand(resizeDelta); + } + } + + + public void onSnap() { + SettingsStore settings = tt9.getSettings(); + + if (settings.isMainLayoutTray()) { + expand(1); + } else if (settings.isMainLayoutSmall()) { + expand(heightNumpad); + } else { + shrink(-heightNumpad); + } + } + + + public void onResizeThrottled(float currentY) { + long now = System.currentTimeMillis(); + if (now - lastResizeTime > SettingsStore.RESIZE_THROTTLING_TIME) { + lastResizeTime = now; + onResize(currentY); + } + } + + + private void expand(int delta) { + SettingsStore settings = tt9.getSettings(); + + if (settings.isMainLayoutTray()) { + settings.setMainViewLayout(SettingsStore.LAYOUT_SMALL); + height = heightSmall; + tt9.onCreateInputView(); + vibrate(); + } else if (settings.isMainLayoutSmall()) { + settings.setMainViewLayout(SettingsStore.LAYOUT_NUMPAD); + height = (int) Math.max(Math.max(heightNumpad * 0.6, heightSmall * 1.1), height + delta); + tt9.onCreateInputView(); + vibrate(); + } else { + changeHeight(delta, heightSmall, heightNumpad); + } + } + + + private void shrink(int delta) { + SettingsStore settings = tt9.getSettings(); + + if (settings.isMainLayoutTray()) { + return; + } + + if (settings.isMainLayoutSmall()) { + settings.setMainViewLayout(SettingsStore.LAYOUT_TRAY); + height = heightTray; + tt9.onCreateInputView(); + vibrate(); + } else if (!changeHeight(delta, heightSmall, heightNumpad)) { + settings.setMainViewLayout(SettingsStore.LAYOUT_SMALL); + height = heightSmall; + tt9.onCreateInputView(); + vibrate(); + } + } + + + private boolean changeHeight(int delta, int minHeight, int maxHeight) { + if (main == null || main.getView() == null) { + return false; + } + + return setHeight(main.getView().getMeasuredHeight() + delta, minHeight, maxHeight); + } + + + private boolean setHeight(int height, int minHeight, int maxHeight) { + if (main == null || main.getView() == null || height < minHeight) { + return false; + } + + height = Math.min(height, maxHeight); + + ViewGroup.LayoutParams params = main.getView().getLayoutParams(); + if (params == null) { + return false; + } + + params.height = height; + main.getView().setLayoutParams(params); + this.height = height; + + return true; + } + + + private void resetHeight() { + if (main != null) { + main.resetHeight(); + } + + calculateSnapHeights(); + calculateInitialHeight(); + setHeight(height, heightSmall, heightNumpad); + } + + + private void vibrate() { + if (tt9.getSettings().getHapticFeedback() && main != null && main.getView() != null) { + main.getView().performHapticFeedback(Vibration.getPressVibration(null)); + } + } + + + @Override public void onViewAttachedToWindow(@NonNull View v) { onCreateAdjustHeight(); } + @Override public void onViewDetachedFromWindow(@NonNull View v) {} +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/Vibration.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/Vibration.java similarity index 61% rename from app/src/main/java/io/github/sspanak/tt9/ui/main/keys/Vibration.java rename to app/src/main/java/io/github/sspanak/tt9/ui/main/Vibration.java index ccc72316..52cead05 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/Vibration.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/Vibration.java @@ -1,18 +1,21 @@ -package io.github.sspanak.tt9.ui.main.keys; +package io.github.sspanak.tt9.ui.main; import android.os.Build; import android.view.HapticFeedbackConstants; -class Vibration { - static int getNoVibration() { +import io.github.sspanak.tt9.ui.main.keys.SoftKey; +import io.github.sspanak.tt9.ui.main.keys.SoftNumberKey; + +public class Vibration { + public static int getNoVibration() { return -1; } - static int getPressVibration(SoftKey key) { + public static int getPressVibration(SoftKey key) { return key instanceof SoftNumberKey ? HapticFeedbackConstants.KEYBOARD_TAP : HapticFeedbackConstants.VIRTUAL_KEY; } - static int getHoldVibration() { + public static int getHoldVibration() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { return HapticFeedbackConstants.CONFIRM; } else { @@ -20,8 +23,7 @@ class Vibration { } } - - static int getReleaseVibration() { + public static int getReleaseVibration() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { return HapticFeedbackConstants.KEYBOARD_RELEASE; } else { diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/ResizeHandle.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/ResizeHandle.java new file mode 100644 index 00000000..7b376d5f --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/ResizeHandle.java @@ -0,0 +1,73 @@ +package io.github.sspanak.tt9.ui.main.keys; + +import android.content.Context; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.NonNull; + +import io.github.sspanak.tt9.R; +import io.github.sspanak.tt9.ui.main.ResizableMainView; + +public class ResizeHandle implements View.OnTouchListener { + @NonNull private final Runnable onClick; + private ResizableMainView mainView; + + private final float RESIZE_THRESHOLD; + private boolean dragging; + private float startY; + + + ResizeHandle(@NonNull Context context, @NonNull Runnable onClick) { + RESIZE_THRESHOLD = context.getResources().getDimensionPixelSize(R.dimen.numpad_key_height) / 4.0f; + this.onClick = onClick; + } + + public void setMainView(ResizableMainView mainView) { + this.mainView = mainView; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + switch(event.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + handlePress(event); + break; + case MotionEvent.ACTION_MOVE: + handleDrag(event); + break; + case MotionEvent.ACTION_UP: + handleRelease(event); + break; + } + + return false; + } + + private void handlePress(MotionEvent event) { + startY = event.getRawY(); + } + + private void handleDrag(MotionEvent event) { + if (mainView == null) { + dragging = false; + return; + } + + if (!dragging && Math.abs(event.getRawY() - startY) >= RESIZE_THRESHOLD) { + mainView.onResizeStart(event.getRawY()); + dragging = true; + } else if (dragging) { + mainView.onResizeThrottled(event.getRawY()); + } + } + + private void handleRelease(MotionEvent event) { + if (mainView != null && dragging) { + mainView.onResize(event.getRawY()); + dragging = false; + } else { + onClick.run(); + } + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftBackspaceKey.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftBackspaceKey.java index 29da7472..4bb82c25 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftBackspaceKey.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftBackspaceKey.java @@ -6,6 +6,7 @@ import android.view.KeyEvent; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.languages.LanguageKind; +import io.github.sspanak.tt9.ui.main.Vibration; public class SoftBackspaceKey extends SoftKey { private boolean hold; diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftFilterKey.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftFilterKey.java index bda369fe..c198624c 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftFilterKey.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftFilterKey.java @@ -4,6 +4,7 @@ import android.content.Context; import android.util.AttributeSet; import io.github.sspanak.tt9.preferences.settings.SettingsStore; +import io.github.sspanak.tt9.ui.main.Vibration; public class SoftFilterKey extends SoftKey { public SoftFilterKey(Context context) { super(context); setFontSize(); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftInputModeKey.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftInputModeKey.java index 97c1c6c2..6da1d229 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftInputModeKey.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftInputModeKey.java @@ -4,6 +4,7 @@ import android.content.Context; import android.util.AttributeSet; import io.github.sspanak.tt9.R; +import io.github.sspanak.tt9.ui.main.Vibration; public class SoftInputModeKey extends SoftKey { public SoftInputModeKey(Context context) { 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 6dc54e9c..b1da6d37 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 @@ -18,6 +18,7 @@ import androidx.core.content.ContextCompat; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.ime.TraditionalT9; import io.github.sspanak.tt9.preferences.settings.SettingsStore; +import io.github.sspanak.tt9.ui.main.Vibration; import io.github.sspanak.tt9.util.Characters; import io.github.sspanak.tt9.util.Logger; @@ -38,16 +39,22 @@ public class SoftKey extends androidx.appcompat.widget.AppCompatButton implement public SoftKey(Context context) { super(context); setHapticFeedbackEnabled(false); + setOnTouchListener(this); + setOnLongClickListener(this); } public SoftKey(Context context, AttributeSet attrs) { super(context, attrs); setHapticFeedbackEnabled(false); + setOnTouchListener(this); + setOnLongClickListener(this); } public SoftKey(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setHapticFeedbackEnabled(false); + setOnTouchListener(this); + setOnLongClickListener(this); } @@ -66,19 +73,6 @@ public class SoftKey extends androidx.appcompat.widget.AppCompatButton implement } - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - View keyView = findViewById(getId()); - if (keyView != null) { - keyView.setOnTouchListener(this); - keyView.setOnLongClickListener(this); - } else { - Logger.e(LOG_TAG, "Failed settings touch listeners. Cannot find SoftKey with ID: " + getId()); - } - } - - @Override public boolean onTouch(View view, MotionEvent event) { super.onTouchEvent(event); @@ -164,7 +158,6 @@ public class SoftKey extends androidx.appcompat.widget.AppCompatButton implement } int keyId = getId(); - if (keyId == R.id.soft_key_command_palette) return tt9.onKeyCommandPalette(false); 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/SoftKeyArrow.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyArrow.java index 382fc731..cd0ff48e 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyArrow.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyArrow.java @@ -4,6 +4,7 @@ import android.content.Context; import android.util.AttributeSet; import io.github.sspanak.tt9.R; +import io.github.sspanak.tt9.ui.main.Vibration; public class SoftKeyArrow extends SoftKey { private boolean hold; diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyCommandPalette.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyCommandPalette.java index 3a2ff838..725d9e40 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyCommandPalette.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyCommandPalette.java @@ -2,14 +2,35 @@ package io.github.sspanak.tt9.ui.main.keys; import android.content.Context; import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; import io.github.sspanak.tt9.R; +import io.github.sspanak.tt9.ui.main.ResizableMainView; public class SoftKeyCommandPalette extends SoftKey { + private final ResizeHandle resizeHandle = new ResizeHandle(getContext(), this::showCommandPalette); + public SoftKeyCommandPalette(Context context) { super(context); } public SoftKeyCommandPalette(Context context, AttributeSet attrs) { super(context, attrs); } public SoftKeyCommandPalette(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } + public void setMainView(ResizableMainView mainView) { + resizeHandle.setMainView(mainView); + } + + @Override + public boolean onTouch(View view, MotionEvent event) { + resizeHandle.onTouch(view, event); + return super.onTouch(view, event); + } + + protected void showCommandPalette() { + if (validateTT9Handler()) { + tt9.onKeyCommandPalette(false); + } + } + @Override protected int getNoEmojiTitle() { return R.string.virtual_key_command_palette; diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeySettings.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeySettings.java index 44d52ecb..0cd65d6a 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeySettings.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeySettings.java @@ -2,22 +2,44 @@ package io.github.sspanak.tt9.ui.main.keys; import android.content.Context; import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; import io.github.sspanak.tt9.R; +import io.github.sspanak.tt9.ui.main.ResizableMainView; public class SoftKeySettings extends SoftKey { - public SoftKeySettings(Context context) { super(context); } - public SoftKeySettings(Context context, AttributeSet attrs) { super(context, attrs); } - public SoftKeySettings(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } + private final ResizeHandle resizeHandle = new ResizeHandle(getContext(), this::showSettings); + + public SoftKeySettings(Context context) { + super(context); + setOnLongClickListener(null); + } + + public SoftKeySettings(Context context, AttributeSet attrs) { + super(context, attrs); + setOnLongClickListener(null); + } + + public SoftKeySettings(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setOnLongClickListener(null); + } + + public void setMainView(ResizableMainView mainView) { + resizeHandle.setMainView(mainView); + } @Override - protected boolean handleRelease() { + public boolean onTouch(View view, MotionEvent event) { + resizeHandle.onTouch(view, event); + return super.onTouch(view, event); + } + + protected void showSettings() { if (validateTT9Handler()) { tt9.showSettings(); - return true; } - - return false; } @Override diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftNumberKey.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftNumberKey.java index 16d2a9fd..c2890036 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftNumberKey.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftNumberKey.java @@ -12,6 +12,7 @@ import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageKind; import io.github.sspanak.tt9.preferences.settings.SettingsStore; +import io.github.sspanak.tt9.ui.main.Vibration; import io.github.sspanak.tt9.util.Logger; public class SoftNumberKey extends SoftKey { diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java b/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java index c7fe6f62..d36f01bd 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java @@ -5,6 +5,7 @@ import android.content.Context; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.view.MotionEvent; import android.view.View; import androidx.annotation.NonNull; @@ -19,12 +20,15 @@ import java.util.List; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.preferences.settings.SettingsStore; +import io.github.sspanak.tt9.ui.main.ResizableMainView; public class SuggestionsBar { + private double lastClickTime = 0; private final List suggestions = new ArrayList<>(); protected int selectedIndex = 0; private boolean isDarkThemeEnabled = false; + private final ResizableMainView mainView; private final Runnable onItemClick; private final RecyclerView mView; private final SettingsStore settings; @@ -33,17 +37,23 @@ public class SuggestionsBar { private final Handler alternativeScrollingHandler = new Handler(); - public SuggestionsBar(@NonNull SettingsStore settings, @NonNull View mainView, @NonNull Runnable onItemClick) { + public SuggestionsBar(@NonNull SettingsStore settings, @NonNull ResizableMainView mainView, @NonNull Runnable onItemClick) { this.onItemClick = onItemClick; this.settings = settings; - mView = mainView.findViewById(R.id.suggestions_bar); + this.mainView = mainView; + mView = mainView.getView() != null ? mainView.getView().findViewById(R.id.suggestions_bar) : null; if (mView != null) { - mView.setLayoutManager(new LinearLayoutManager(mainView.getContext(), RecyclerView.HORIZONTAL, false)); + Context context = mainView.getView().getContext(); - initDataAdapter(mainView.getContext()); - initSeparator(mainView.getContext()); + mView.setLayoutManager(new LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)); + mView.setOnTouchListener(this::onTouch); + + initDataAdapter(context); + initSeparator(context); configureAnimation(); + + } } @@ -251,4 +261,35 @@ public class SuggestionsBar { selectedIndex = position; onItemClick.run(); } + + + private boolean onTouch(View view, MotionEvent event) { + if (!isEmpty()) { + return false; + } + + int action = event.getAction(); + + switch (action) { + case MotionEvent.ACTION_DOWN: + mainView.onResizeStart(event.getRawY()); + return true; + case MotionEvent.ACTION_MOVE: + mainView.onResizeThrottled(event.getRawY()); + return true; + case MotionEvent.ACTION_UP: + long now = System.currentTimeMillis(); + if (now - lastClickTime < SettingsStore.SOFT_KEY_DOUBLE_CLICK_DELAY) { + mainView.onSnap(); + } else { + mainView.onResize(event.getRawY()); + } + + lastClickTime = now; + + return true; + } + + return false; + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index edee5268..51aa0c9f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -72,6 +72,7 @@ Function keys Invisible Suggestion list only + Virtual Numpad Key Size Settings Font Size Default Large diff --git a/app/src/main/res/xml/prefs_screen_appearance.xml b/app/src/main/res/xml/prefs_screen_appearance.xml index 47983920..8fe6863e 100644 --- a/app/src/main/res/xml/prefs_screen_appearance.xml +++ b/app/src/main/res/xml/prefs_screen_appearance.xml @@ -15,6 +15,10 @@ app:title="@string/pref_haptic_feedback" app:summary="@string/pref_haptic_feedback_summary"/> + +