diff --git a/src/io/github/sspanak/tt9/ui/main/keys/SoftBackspaceKey.java b/src/io/github/sspanak/tt9/ui/main/keys/SoftBackspaceKey.java index e9a8e267..4a3196e2 100644 --- a/src/io/github/sspanak/tt9/ui/main/keys/SoftBackspaceKey.java +++ b/src/io/github/sspanak/tt9/ui/main/keys/SoftBackspaceKey.java @@ -2,15 +2,10 @@ 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.Logger; -import io.github.sspanak.tt9.preferences.SettingsStore; public class SoftBackspaceKey extends SoftKey { - private SettingsStore settings; - long lastBackspaceCall = 0; public SoftBackspaceKey(Context context) { super(context); @@ -24,52 +19,23 @@ public class SoftBackspaceKey extends SoftKey { super(context, attrs, defStyleAttr); } - private SettingsStore getSettings() { - if (settings == null) { - settings = new SettingsStore(getContext()); - } - - return settings; + @Override + final protected boolean handlePress() { + return handleHold(); } @Override - final public boolean onTouch(View view, MotionEvent event) { + final protected boolean handleHold() { if (tt9 == null) { Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press."); return false; } - int action = event.getAction() & MotionEvent.ACTION_MASK; - - if (action == MotionEvent.AXIS_PRESSURE) { - handleHold(); - } else if (action == MotionEvent.ACTION_UP) { - handleUp(); - } else if (action == MotionEvent.ACTION_DOWN) { - // Fallback for phones that do not report AXIS_PRESSURE, when a key is being held - handlePress(-1); - } - - return true; - } - - private void handleHold() { - if (System.currentTimeMillis() - lastBackspaceCall < getSettings().getSoftKeyRepeatDelay()) { - return; - } - - handlePress(-1); - - long now = System.currentTimeMillis(); - lastBackspaceCall = lastBackspaceCall == 0 ? getSettings().getSoftKeyInitialDelay() + now : now; - } - - private void handleUp() { - lastBackspaceCall = 0; + return tt9.onBackspace(); } @Override - final protected boolean handlePress(int b) { - return tt9.onBackspace(); + final protected boolean handleRelease() { + return false; } } diff --git a/src/io/github/sspanak/tt9/ui/main/keys/SoftKey.java b/src/io/github/sspanak/tt9/ui/main/keys/SoftKey.java index 60366490..3ed3d14f 100644 --- a/src/io/github/sspanak/tt9/ui/main/keys/SoftKey.java +++ b/src/io/github/sspanak/tt9/ui/main/keys/SoftKey.java @@ -2,6 +2,8 @@ package io.github.sspanak.tt9.ui.main.keys; import android.content.Context; import android.graphics.Typeface; +import android.os.Handler; +import android.os.Looper; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.RelativeSizeSpan; @@ -16,11 +18,17 @@ import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.ime.TraditionalT9; -public class SoftKey extends androidx.appcompat.widget.AppCompatButton implements View.OnTouchListener { +public class SoftKey extends androidx.appcompat.widget.AppCompatButton implements View.OnTouchListener, View.OnLongClickListener { protected TraditionalT9 tt9; + protected float COMPLEX_LABEL_TITLE_SIZE = 0.55f; protected float COMPLEX_LABEL_SUB_TITLE_SIZE = 0.8f; + private boolean hold = false; + private boolean repeat = false; + private final Handler repeatHandler = new Handler(Looper.getMainLooper()); + + public SoftKey(Context context) { super(context); } @@ -33,6 +41,7 @@ public class SoftKey extends androidx.appcompat.widget.AppCompatButton implement super(context, attrs, defStyleAttr); } + public void setTT9(TraditionalT9 tt9) { this.tt9 = tt9; } @@ -50,25 +59,82 @@ public class SoftKey extends androidx.appcompat.widget.AppCompatButton implement protected void onFinishInflate() { super.onFinishInflate(); getRootView().setOnTouchListener(this); + getRootView().setOnLongClickListener(this); } @Override public boolean onTouch(View view, MotionEvent event) { super.onTouchEvent(event); - if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) { - return handlePress(view.getId()); + int action = (event.getAction() & MotionEvent.ACTION_MASK); + + if (action == MotionEvent.ACTION_DOWN) { + return handlePress(); + } else if (action == MotionEvent.ACTION_UP) { + preventRepeat(); + if (!repeat) { + return handleRelease(); + } + repeat = false; } return false; } - protected boolean handlePress(int keyId) { + @Override + public boolean onLongClick(View view) { + hold = true; + + // sometimes this gets called twice, so we debounce the call to the repeating function + repeatHandler.removeCallbacks(this::repeatOnLongPress); + repeatHandler.postDelayed(this::repeatOnLongPress, 1); + return true; + } + + /** + * repeatOnLongPress + * Repeatedly calls "handleHold()" upon holding the respective SoftKey, to simulate physical keyboard behavior. + */ + private void repeatOnLongPress() { + if (tt9 == null) { + Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press."); + hold = false; + return; + } + + if (hold) { + repeat = true; + handleHold(); + repeatHandler.removeCallbacks(this::repeatOnLongPress); + repeatHandler.postDelayed(this::repeatOnLongPress, tt9.getSettings().getSoftKeyRepeatDelay()); + } + } + + /** + * preventRepeat + * Prevents "handleHold()" from being called repeatedly when the SoftKey is being held. + */ + protected void preventRepeat() { + hold = false; + repeatHandler.removeCallbacks(this::repeatOnLongPress); + } + + protected boolean handlePress() { + return false; + } + + protected boolean handleHold() { + return false; + } + + protected boolean handleRelease() { if (tt9 == null) { Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press."); return false; } + int keyId = getId(); + if (keyId == R.id.soft_key_add_word) return tt9.onKeyAddWord(); if (keyId == R.id.soft_key_input_mode) return tt9.onKeyNextInputMode(); if (keyId == R.id.soft_key_language) return tt9.onKeyNextLanguage(); @@ -99,7 +165,7 @@ public class SoftKey extends androidx.appcompat.widget.AppCompatButton implement /** * render - * Sets the key label using "getKeyNameLabel()" and "getKeyFunctionLabel()" or if they both + * Sets the key label using "getTitle()" and "getSubtitle()" or if they both * return NULL, the XML "text" attribute will be preserved. * * If there is only name label, it will be centered and at normal font size. @@ -107,19 +173,19 @@ public class SoftKey extends androidx.appcompat.widget.AppCompatButton implement * have their font size adjusted to fit inside the key. */ public void render() { - String name = getTitle(); - String func = getSubTitle(); + String title = getTitle(); + String subtitle = getSubTitle(); - if (name == null) { + if (title == null) { return; - } else if (func == null) { - setText(name); + } else if (subtitle == null) { + setText(title); return; } - SpannableStringBuilder sb = new SpannableStringBuilder(name); + SpannableStringBuilder sb = new SpannableStringBuilder(title); sb.append('\n'); - sb.append(func); + sb.append(subtitle); sb.setSpan(new RelativeSizeSpan(COMPLEX_LABEL_TITLE_SIZE), 0, 2, Spanned.SPAN_INCLUSIVE_INCLUSIVE); sb.setSpan(new StyleSpan(Typeface.ITALIC), 0, 2, Spanned.SPAN_EXCLUSIVE_INCLUSIVE); diff --git a/src/io/github/sspanak/tt9/ui/main/keys/SoftNumberKey.java b/src/io/github/sspanak/tt9/ui/main/keys/SoftNumberKey.java index 86a84de8..e67c937e 100644 --- a/src/io/github/sspanak/tt9/ui/main/keys/SoftNumberKey.java +++ b/src/io/github/sspanak/tt9/ui/main/keys/SoftNumberKey.java @@ -26,13 +26,26 @@ public class SoftNumberKey extends SoftKey { super(context, attrs, defStyleAttr); } - protected boolean handlePress(int keyId) { + @Override + protected boolean handleHold() { + if (tt9 == null || tt9.getSettings().getInputMode() != InputMode.MODE_123 || getId() != R.id.soft_key_0) { + return super.handleHold(); + } + + preventRepeat(); + int zeroCode = Key.numberToCode(0); + tt9.onKeyLongPress(zeroCode, new KeyEvent(KeyEvent.ACTION_DOWN, zeroCode)); + return true; + } + + @Override + protected boolean handleRelease() { if (tt9 == null) { Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press."); return false; } - int keyCode = Key.numberToCode(getNumber(keyId)); + int keyCode = Key.numberToCode(getNumber(getId())); if (keyCode < 0) { return false; } @@ -43,6 +56,58 @@ public class SoftNumberKey extends SoftKey { return true; } + @Override + protected String getTitle() { + return String.valueOf(getNumber(getId())); + } + + @Override + protected String getSubTitle() { + if (tt9 == null) { + return null; + } + + int number = getNumber(getId()); + + if (number == 0) { + if (tt9.getSettings().getInputMode() == InputMode.MODE_123) { + return "+"; + } else { + COMPLEX_LABEL_SUB_TITLE_SIZE = 1; + return "␣"; + } + } + + // no special labels in 123 mode + if (tt9.getSettings().getInputMode() == InputMode.MODE_123) { + return null; + } + + // 1 + if (number == 1) { + return ",:-)"; + } + + // 2-9 + int textCase = tt9.getSettings().getTextCase(); + Language language = LanguageCollection.getLanguage(tt9.getSettings().getInputLanguage()); + + if (language == null) { + Logger.d("SoftNumberKey.getLabel", "Cannot generate a label when the language is NULL."); + return ""; + } + + StringBuilder sb = new StringBuilder(); + ArrayList chars = language.getKeyCharacters(number, false); + for (int i = 0; i < 5 && i < chars.size(); i++) { + sb.append( + textCase == InputMode.CASE_UPPER ? chars.get(i).toUpperCase(language.getLocale()) : chars.get(i) + ); + } + + return sb.toString(); + } + private int getNumber(int keyId) { if (keyId == R.id.soft_key_0) return 0; if (keyId == R.id.soft_key_1) return 1; @@ -57,44 +122,4 @@ public class SoftNumberKey extends SoftKey { return -1; } - - @Override - protected String getTitle() { - return String.valueOf(getNumber(getId())); - } - - @Override - protected String getSubTitle() { - if (tt9 == null || tt9.getSettings().getInputMode() == InputMode.MODE_123) { - return null; - } - - int number = getNumber(getId()); - int textCase = tt9.getSettings().getTextCase(); - Language language = LanguageCollection.getLanguage(tt9.getSettings().getInputLanguage()); - - if (language == null) { - Logger.d("SoftNumberKey.getLabel", "Cannot generate a label when the language is NULL."); - return ""; - } - - if (number == 0) { - COMPLEX_LABEL_SUB_TITLE_SIZE = 1; - return "␣"; - } - - if (number == 1) { - return ",:-)"; - } - - StringBuilder sb = new StringBuilder(); - ArrayList chars = language.getKeyCharacters(number, false); - for (int i = 0; i < 5 && i < chars.size(); i++) { - sb.append( - textCase == InputMode.CASE_UPPER ? chars.get(i).toUpperCase(language.getLocale()) : chars.get(i) - ); - } - - return sb.toString(); - } } diff --git a/src/io/github/sspanak/tt9/ui/main/keys/SoftPunctuationKey.java b/src/io/github/sspanak/tt9/ui/main/keys/SoftPunctuationKey.java index afe6228b..aab414c0 100644 --- a/src/io/github/sspanak/tt9/ui/main/keys/SoftPunctuationKey.java +++ b/src/io/github/sspanak/tt9/ui/main/keys/SoftPunctuationKey.java @@ -21,12 +21,28 @@ public class SoftPunctuationKey extends SoftKey { super(context, attrs, defStyleAttr); } - protected boolean handlePress(int keyId) { + @Override + protected boolean handleHold() { + if (tt9 == null || tt9.getSettings().getInputMode() != InputMode.MODE_123) { + return super.handleHold(); + } + + preventRepeat(); + int keyId = getId(); + if (keyId == R.id.soft_key_punctuation_1) return tt9.onText(","); + if (keyId == R.id.soft_key_punctuation_2) return tt9.onText("."); + + return false; + } + + @Override + protected boolean handleRelease() { if (tt9 == null) { Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press."); return false; } + int keyId = getId(); if (tt9.getSettings().getInputMode() == InputMode.MODE_123) { if (keyId == R.id.soft_key_punctuation_1) return tt9.onOtherKey(KeyEvent.KEYCODE_STAR); if (keyId == R.id.soft_key_punctuation_2) return tt9.onOtherKey(KeyEvent.KEYCODE_POUND); @@ -40,8 +56,11 @@ public class SoftPunctuationKey extends SoftKey { @Override protected String getTitle() { - int keyId = getId(); + if (tt9 == null) { + return "PUNC"; + } + int keyId = getId(); if (tt9.getSettings().getInputMode() == InputMode.MODE_123) { if (keyId == R.id.soft_key_punctuation_1) return "✱"; if (keyId == R.id.soft_key_punctuation_2) return "#"; @@ -52,4 +71,15 @@ public class SoftPunctuationKey extends SoftKey { return "PUNC"; } + + @Override + protected String getSubTitle() { + int keyId = getId(); + if (tt9 != null && tt9.getSettings().getInputMode() == InputMode.MODE_123) { + if (keyId == R.id.soft_key_punctuation_1) return ","; + if (keyId == R.id.soft_key_punctuation_2) return "."; + } + + return null; + } }