From ec3e21d8cf4a8fb0fc00105a73d2cd185ab1cbfb Mon Sep 17 00:00:00 2001 From: Emil Kosz Date: Fri, 1 Nov 2024 00:57:27 +0100 Subject: [PATCH] Interface code improvements --- README.md | 35 +++++++ USER_EXPERIENCE.md | 96 ------------------- .../eu/titanboard/ClipboardHandler.java | 11 ++- .../cfpi_fpsi/eu/titanboard/InputMethod.java | 2 + .../cfpi_fpsi/eu/titanboard/InputView.java | 56 +++++++---- app/src/main/res/layout/candidate.xml | 29 ++++++ app/src/main/res/layout/input.xml | 29 +++--- app/src/main/res/layout/symbol.xml | 25 +++++ app/src/main/res/values/arrays.xml | 2 + app/src/main/res/values/dimens.xml | 14 +-- app/src/main/res/xml/keymaps_titan.xml | 21 ++++ 11 files changed, 178 insertions(+), 142 deletions(-) create mode 100644 README.md delete mode 100644 USER_EXPERIENCE.md create mode 100644 app/src/main/res/layout/candidate.xml create mode 100644 app/src/main/res/layout/symbol.xml diff --git a/README.md b/README.md new file mode 100644 index 0000000..6db4e17 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Titanboard +Titanboard is an input method for physical keyboard phones, supporting as low as Android 1.5 +Cupcake. + +## Features +- Support for very low Android versions, down to 1.5 +- Nimble and small; I set out to have the APK be at most 100 kB +- Each physical key can have several additional symbols, which the user can cycle by long pressing +a given key. This also applies for alternate (alt) symbols +- 9 supported languages, with more planned (which are trivial to add) +- No hard to memorize key combinations; reasonably uses what the physical keyboard has to offer +- Pasting from clipboard akin to Gboard +- Occupied screen estate is minimized; on Android 5.0 and higher, an additional floating interface +mode is supported, which floats above the text you're typing, thus making the keyboard occupy no +space at all + +## Planned features +- Autocorrect +- Emojis +- More sophisticated clipboard management + +## Supported languages +- English +- German +- French +- Italian +- Spanish +- Polish +- Russian (Phonetic) +- Greek (polytonic diacritics under Q) +- Arabic (Intellark) + +## Supported devices +Thus far, the supported devices are: +- Unihertz Titan (additional diacritics under 1 key for all languages) \ No newline at end of file diff --git a/USER_EXPERIENCE.md b/USER_EXPERIENCE.md deleted file mode 100644 index 645fc74..0000000 --- a/USER_EXPERIENCE.md +++ /dev/null @@ -1,96 +0,0 @@ -# Titanboard user experience - -Keyboards are an integral part of the functioning of any computer, including the smartphone. Care -should then be taken to design keyboards to be as intuitive, easy to use, and unobstructed as -possible. - -The Unihertz Titan already has a physical keyboard. It's poorly designed in many aspects. Mainly, it -isn't very reminiscent of a regular QWERTY keyboard which people are used to: - -1. The worst part is arguably the shift and alt keys being at the top of the keyboard, instead of on - the bottom. -2. The spacebar key is on the same row as the bottom QWERTY alphabet keys. -3. The numerical keys, which are some of the most commonly used keys in phones (passwords, PIN - codes) - and in regular language are right below the alt key, which makes typing them very awkward. One - could - also argue that the numpad is in a "reversed" layout, but this is consistent with the usual - layout - found on phones. -4. It lacks some commonly used symbols: ';', '&', '=', '<', '>', '$'. Other than that, it lacks some - other - symbols from regular QWERTY keyboards. -5. There is no extra alt-gr key to type in localized letters. -6. The keyboard is too big for one hand typing. -7. In my opinion, it also lacks a control key, but that is unnecessary on smartphones, unless you - use the terminal or modify spreadsheets. However, providing appropriate UI elements for the - actions - possible on regular computers should be the responsibility of the application. Smartphones are - simply different devices, with smaller screens and less space for complexity. -8. Emojis. - -However, when designing a keyboard app for it one should already use all pre-existing quirks and -decisions which materialized in the physical keyboard. For example, the alt key should be used for -the alternate characters printed on the keyboard, the shift key should give us uppercase characters -and so on. - -# Solutions - -Here are my solutions for the above listed problems: - -1. This is solvable with shift/alt latching. This makes it so when you press these keys once, the -next key pressed will be either uppercased or the alternate key visible on the physical button. -Additionally, the shift and alt key can be toggled by double-clicking in a fast succession, similar -to regular visual keyboards on Android. -2. This is unfortunately unsolvable with software. -3. See 1., latching the keys solves this issue. -4. This is solved by assigning alternate symbols when alt is pressed/latched/locked and the given -key is long pressed. A menu pops up, and as you hold the key the symbols are cycled through. -5. Similar to regular visual keyboards on Android, a diacritic/alternate key is assigned to each key -according to the language. Long pressing a key will allow you to cycle through available -alternatives. -6. A toggleable UI that shows the left/right hand side keys, depending on a customizable setting for -left handed people. -7. I have no plans on adding a control key, as it is largely unnecessary on phones. -8. I do not really use emojis, but I am aware that they are used by the majority of people. Most -IM apps provide their own emoji list, so this isn't a priority. Designing a good UX for it is also -hard on the Titan. - -Taking into account typing ergonomics and pre-existing UX choices of other keyboard designers, this -input method should make typing on the Unihertz Titan a pleasant experience. Other than fixing the -fundamental issues, the keyboard should also: - -1. Provide a way of managing the clipboard, or at the very least pasting things with an UI element. - This is to make the usage intuitive and not introduce any additional keys such as a ctrl key. -2. Autocorrect, language switching, auto caps and periods. -3. Some minimal customization, but just enough not to overwhelm the user. - -For the Unihertz Titan Pocket/Slim, the sym key is unused. I have no ideas what it could be useful -for. It can be set in Unihertz's software to do operating system actions, which I think is good. - -# User interface - -Google recommends that visual keyboards should not occupy more than half of the screen estate. -However, most phones have long, vertical screens, which means essentially that the keyboard will -be a square, and so will the remaining view. On the Titan, the screen is already square, so the -remaining space will be proportionally much smaller. Therefore, the keyboard should occupy very -minimal screen estate, as to allow the user to see the application they are interacting with. - -For this reason, I make sure the keyboard occupies 20–25 % screen estate. This is enough for simple -status bar, which shows the current shift/alt state, the current language, and a symbol bar which -appears when long pressing keys. - -# Keymaps -Latin keymaps use the regular QWERTY layout of the Titan. There is one, large, general European key -layout, which contains all characters used in Europe. - -## European -German has the most speakers, therefore it's diacritics are the first in the list. Then French and -Italian, which have similar diacritics. These come next. Afterwards, Spanish, then Polish, and the -rest. - -## Cyrillic -Regular JCUKEN layout, with additional characters for other non-Russian languages. - -## Greek -Works mostly like a regular Greek QWERTY keyboard. \ No newline at end of file diff --git a/app/src/main/java/cfpi_fpsi/eu/titanboard/ClipboardHandler.java b/app/src/main/java/cfpi_fpsi/eu/titanboard/ClipboardHandler.java index 7b92454..3e5f036 100644 --- a/app/src/main/java/cfpi_fpsi/eu/titanboard/ClipboardHandler.java +++ b/app/src/main/java/cfpi_fpsi/eu/titanboard/ClipboardHandler.java @@ -25,13 +25,10 @@ import android.content.ClipboardManager; import android.content.Context; import android.os.Build; -import java.util.ArrayList; - @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class ClipboardHandler implements ClipboardManager.OnPrimaryClipChangedListener { - private final static int MAX_CLIPS = 4; - private final ArrayList mClips = new ArrayList<>(); private final ClipboardManager mClipboardManager; + private ClipData mCurrentClip; public ClipboardHandler(Context ctx) { mClipboardManager = (ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE); @@ -39,6 +36,10 @@ public class ClipboardHandler implements ClipboardManager.OnPrimaryClipChangedLi @Override public void onPrimaryClipChanged() { - ClipData newClip = mClipboardManager.getPrimaryClip(); + mCurrentClip = mClipboardManager.getPrimaryClip(); + } + + public ClipData getCurrentClip() { + return mCurrentClip; } } diff --git a/app/src/main/java/cfpi_fpsi/eu/titanboard/InputMethod.java b/app/src/main/java/cfpi_fpsi/eu/titanboard/InputMethod.java index 528d5e3..81b1132 100644 --- a/app/src/main/java/cfpi_fpsi/eu/titanboard/InputMethod.java +++ b/app/src/main/java/cfpi_fpsi/eu/titanboard/InputMethod.java @@ -45,6 +45,7 @@ public class InputMethod extends InputMethodService { public MetaKeyManager mMetaKeyManager; public KeymapManager mKeymapManager; public PreferenceHolder mPreferenceHolder; + private ClipboardHandler mClipboardHandler; private InputView mInputView; private int mCurrentCursorPosition; private Handler mHandler; @@ -58,6 +59,7 @@ public class InputMethod extends InputMethodService { mVibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); mMetaKeyManager = new MetaKeyManager(mVibrator); mKeymapManager = new KeymapManager(this); + mClipboardHandler = new ClipboardHandler(this); mHandler = new Handler(Looper.getMainLooper()); mSymbolCycler = new SymbolCycler(); mKeymapManager.cycleKeymaps(mPreferenceHolder.getSelectedKeymaps()); diff --git a/app/src/main/java/cfpi_fpsi/eu/titanboard/InputView.java b/app/src/main/java/cfpi_fpsi/eu/titanboard/InputView.java index 9c25ab8..34874f0 100644 --- a/app/src/main/java/cfpi_fpsi/eu/titanboard/InputView.java +++ b/app/src/main/java/cfpi_fpsi/eu/titanboard/InputView.java @@ -25,10 +25,8 @@ import android.graphics.Paint; import android.graphics.Typeface; import android.os.Build; import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.ContextThemeWrapper; -import android.view.Gravity; import android.widget.Button; +import android.widget.HorizontalScrollView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; @@ -40,13 +38,32 @@ import java.util.ArrayList; public class InputView extends RelativeLayout { private final InputMethod mInputMethod; + private final LinearLayout.LayoutParams mSymbolLayout; + private final LinearLayout.LayoutParams mCandidateLayout; private LinearLayout mSymbolList; private LinearLayout mCandidateList; private Button mStateButton; + private Button mClipboardButton; public InputView(Context context, AttributeSet attrs) { super(context, attrs, 0); mInputMethod = (InputMethod) context; + mSymbolLayout = new LinearLayout.LayoutParams( + getResources().getDimensionPixelSize(R.dimen.symbol_width), + LinearLayout.LayoutParams.MATCH_PARENT, 0); + mCandidateLayout = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.MATCH_PARENT, 0); + + mSymbolLayout.setMargins(0, 0, + getResources().getDimensionPixelSize(R.dimen.default_space), 0); + mCandidateLayout.setMargins(0, 0, + getResources().getDimensionPixelSize(R.dimen.default_space), 0); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + mSymbolLayout.setMarginEnd(getResources().getDimensionPixelSize(R.dimen.default_space)); + mCandidateLayout.setMarginEnd(getResources().getDimensionPixelSize(R.dimen.default_space)); + } } @Override @@ -55,6 +72,10 @@ public class InputView extends RelativeLayout { mSymbolList = findViewById(R.id.symbol_list); mCandidateList = findViewById(R.id.candidate_list); mStateButton = findViewById(R.id.state_button); + mClipboardButton = (Button) inflate(this.getContext(), R.layout.candidate, null); + mClipboardButton.setTag(0); + mClipboardButton.setLayoutParams(mCandidateLayout); + mCandidateList.addView(mClipboardButton); mStateButton.setOnClickListener(button -> { MetaKeyManager metaManager = mInputMethod.mMetaKeyManager; @@ -127,27 +148,13 @@ public class InputView extends RelativeLayout { * @param strings the strings to be displayed in the CyclerView */ public void prepareSymbols(ArrayList strings) { - ContextThemeWrapper ctw = new ContextThemeWrapper(this.getContext(), R.style.InputViewCandidate); - LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - getResources().getDimensionPixelSize(R.dimen.view_symbol_width), - LinearLayout.LayoutParams.MATCH_PARENT, 0); - - lp.setMargins(0, 0, - getResources().getDimensionPixelSize(R.dimen.view_default_space), 0); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - lp.setMarginEnd(getResources().getDimensionPixelSize(R.dimen.view_default_space)); - } - for (int i = 0; i < strings.size(); ++i) { TextView tv = (TextView) mSymbolList.getChildAt(i); if (tv == null) { - tv = new TextView(ctw, null, 0); - tv.setGravity(Gravity.CENTER); - tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, - getResources().getDimensionPixelSize(R.dimen.view_default_text_size)); - tv.setLayoutParams(lp); + tv = (TextView) inflate(this.getContext(), R.layout.symbol, null); + tv.setTag(i); + tv.setLayoutParams(mSymbolLayout); mSymbolList.addView(tv); } @@ -165,8 +172,17 @@ public class InputView extends RelativeLayout { TextView tv = (TextView) mSymbolList.getChildAt(i); if (i == index) { + HorizontalScrollView hsw = findViewById(R.id.symbol_list_container); tv.setPressed(true); tv.setTypeface(null, Typeface.BOLD); + // This, if the symbol list is larger than the scroll view, scrolls it just to show + // the selected symbol at the very end. It isn't really necessary, as it's a rarity + // that a key has more than five symbols, but perhaps someone will want to add a + // keymap that has a lot of symbols under one key, and also this is a safeguard for + // smaller screen sizes. + hsw.smoothScrollTo(tv.getRight() + + getResources().getDimensionPixelSize(R.dimen.default_space) * 2 + - hsw.getRight(), 0); } else { tv.setPressed(false); tv.setTypeface(null, Typeface.NORMAL); diff --git a/app/src/main/res/layout/candidate.xml b/app/src/main/res/layout/candidate.xml new file mode 100644 index 0000000..850e199 --- /dev/null +++ b/app/src/main/res/layout/candidate.xml @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/input.xml b/app/src/main/res/layout/input.xml index a28e3b6..b8b1f38 100644 --- a/app/src/main/res/layout/input.xml +++ b/app/src/main/res/layout/input.xml @@ -22,20 +22,20 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" - android:paddingLeft="@dimen/view_default_space" - android:paddingTop="@dimen/view_default_space" - android:paddingRight="@dimen/view_default_space"> + android:paddingLeft="@dimen/default_space" + android:paddingTop="@dimen/default_space" + android:paddingRight="@dimen/default_space"> + android:layout_toLeftOf="@id/state_button"> + android:textSize="@dimen/default_text_size" /> \ No newline at end of file diff --git a/app/src/main/res/layout/symbol.xml b/app/src/main/res/layout/symbol.xml new file mode 100644 index 0000000..4ebe280 --- /dev/null +++ b/app/src/main/res/layout/symbol.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index f517acb..3243aae 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -19,6 +19,7 @@ + Test English German French @@ -31,6 +32,7 @@ + tt en de fr diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 1ebd712..1c8506b 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -18,10 +18,12 @@ --> - 8dp - 56dp - 48dp - 48dp - 40dp - 18sp + 8dp + 56dp + 48dp + 48dp + 196dp + 12dp + 40dp + 18sp \ No newline at end of file diff --git a/app/src/main/res/xml/keymaps_titan.xml b/app/src/main/res/xml/keymaps_titan.xml index 22e7337..76e84ab 100644 --- a/app/src/main/res/xml/keymaps_titan.xml +++ b/app/src/main/res/xml/keymaps_titan.xml @@ -18,6 +18,27 @@ --> + + + + + + + + + + + + + + + + + +