diff --git a/app/USER_EXPERIENCE.md b/app/USER_EXPERIENCE.md
new file mode 100644
index 0000000..5e29686
--- /dev/null
+++ b/app/USER_EXPERIENCE.md
@@ -0,0 +1,98 @@
+# 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. 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.
+7. 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 requires a visual keyboard, the UI of which is discussed later. Why does this require a
+ visual keyboard, and not some alternate "symbol" mode, perhaps accessible with some keyboard
+ shortcuts? That would make the learning curve of the keyboard higher, and it would be less
+ intuitive. One should make sure not to complicate the physical keyboard any more than it already
+ is,
+ and use all of its idiosyncrasies but add no more.
+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 print it. Ideally, there should also be a
+ popup
+ menu allowing for several characters under one key. Cycling through the keys would use the
+ shift/alt
+ keys (so that no matter what key you use, the other hand can cycle through) and selecting it
+ would
+ happen on release of the key.
+6. I have no plans on adding a control key, as it is largely unnecessary on phones.
+7. 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 will be used as the alt+gr key, providing diacritics
+and such. This is because in the official documentation and implementation the alt key is designed
+for the alternate keys printed on the buttons.
+
+# 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 a
+symbol bar and a suggestions bar, as well as some additional buttons such as language change or
+settings.
+
+Both the symbol bar and suggestions bar are scrollable. The suggestions being scrollable is a
+feature on some visual keyboards already, however, I have come to the decision to make this for the
+symbol bar as well. An expandable menu would make the keyboard occupy half, or more of the screen.
+A scrollable bar is more intuitive and can hold an indefinite amount of characters, possibly also
+user-defined.
+
+A simple divider between the suggestion and symbol bar is added to avoid accidental clicks.
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 92285ee..51983c2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -11,7 +11,7 @@ android {
minSdk 19
targetSdk 34
versionCode 1
- versionName "1.0"
+ versionName "0.1"
}
buildTypes {
@@ -28,3 +28,4 @@ android {
}
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a853978..2758b9f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,26 +3,36 @@
xmlns:tools="http://schemas.android.com/tools">
+
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/cfpi_fpsi/eu/titanboard/TitanboardIME.java b/app/src/main/java/cfpi_fpsi/eu/titanboard/IME.java
similarity index 74%
rename from app/src/main/java/cfpi_fpsi/eu/titanboard/TitanboardIME.java
rename to app/src/main/java/cfpi_fpsi/eu/titanboard/IME.java
index 923194e..54b1f38 100644
--- a/app/src/main/java/cfpi_fpsi/eu/titanboard/TitanboardIME.java
+++ b/app/src/main/java/cfpi_fpsi/eu/titanboard/IME.java
@@ -1,19 +1,42 @@
+/*
+ * An input method for the Unihertz Titan/physical keyboard phones
+ * Copyright (C) 2024 Emil Kosz
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
package cfpi_fpsi.eu.titanboard;
import android.annotation.SuppressLint;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
+import android.os.Vibrator;
import android.view.KeyEvent;
import android.view.View;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
-public class TitanboardIME extends InputMethodService {
- private final MetaKeyManager mMetaKeyManager = new MetaKeyManager();
+public class IME extends InputMethodService {
+ private MetaKeyManager mMetaKeyManager;
private Keymap mKeymap;
+
private LinearLayout mInputView;
+ private Vibrator mVibrator;
@Override
public void onCreate() {
@@ -23,6 +46,8 @@ public class TitanboardIME extends InputMethodService {
// Unihertz phones: the Pocket and the Slim. However, as I don't have them, it'll be guesswork.
// Sym key support is already implemented.
mKeymap = new Keymap(getResources().getXml(R.xml.keymap_titan));
+ mVibrator = (Vibrator) getBaseContext().getSystemService(VIBRATOR_SERVICE);
+ mMetaKeyManager = new MetaKeyManager(mVibrator);
}
@SuppressLint("InflateParams")
@@ -50,17 +75,20 @@ public class TitanboardIME extends InputMethodService {
}
switch (keyCode) {
- // The back key behaviour is handled by the IMS already.
+ // If the keyboard is visible, hide it, otherwise, let the system handle the key.
+ case KeyEvent.KEYCODE_BACK:
+ if (isInputViewShown()) {
+ requestHideSelf(0);
+ return true;
+ }
+ return false;
// If it's the Enter key, we let the application do it's job.
case KeyEvent.KEYCODE_ENTER:
return false;
// If it's the backspace key, we remove one character.
case KeyEvent.KEYCODE_DEL:
+ conn.deleteSurroundingText(1, 0);
mMetaKeyManager.adjustMetaStateAfterKeypress();
- if (conn.getSelectedText(0) == null) {
- conn.deleteSurroundingText(1, 0);
- return true;
- }
return false;
case KeyEvent.KEYCODE_SPACE:
conn.commitText(" ", 1);
@@ -68,18 +96,19 @@ public class TitanboardIME extends InputMethodService {
return true;
default:
String key = getKey(keyCode);
- if (key != null) {
- conn.commitText(getKey(keyCode), 1);
- mMetaKeyManager.adjustMetaStateAfterKeypress();
- return true;
- } else {
+
+ if (key == null) {
return false;
}
+
+ conn.commitText(getKey(keyCode), 1);
+ mMetaKeyManager.adjustMetaStateAfterKeypress();
+ return true;
}
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
- mMetaKeyManager.updateMetaStateUp(keyCode);
+ mMetaKeyManager.updateMetaStateUp(keyCode, event);
updateMetaStateTextView();
return false;
}
@@ -93,9 +122,6 @@ public class TitanboardIME extends InputMethodService {
public void onFinishInput() {
mMetaKeyManager.resetMetaKeyState();
updateMetaStateTextView();
- mInputView.findViewById(R.id.extra_view).setVisibility(View.GONE);
- Button key = mInputView.findViewById(R.id.expand_button);
- key.setText("▲");
}
private String getKey(int keyCode) {
@@ -144,20 +170,9 @@ public class TitanboardIME extends InputMethodService {
}
}
- /** @noinspection unused*/
- public void onInputViewExtraKeyClick(View v) {
- int visibility = mInputView.findViewById(R.id.extra_view).getVisibility();
- Button key = (Button) v;
- if (visibility == View.VISIBLE) {
- mInputView.findViewById(R.id.extra_view).setVisibility(View.GONE);
- key.setText("▲");
- } else {
- mInputView.findViewById(R.id.extra_view).setVisibility(View.VISIBLE);
- key.setText("▼");
- }
- }
-
- /** @noinspection unused*/
+ /**
+ * @noinspection unused
+ */
public void onInputViewKeyClick(View v) {
Button key = (Button) v;
InputConnection conn = getCurrentInputConnection();
diff --git a/app/src/main/java/cfpi_fpsi/eu/titanboard/Keymap.java b/app/src/main/java/cfpi_fpsi/eu/titanboard/Keymap.java
index c34fd5f..64bd364 100644
--- a/app/src/main/java/cfpi_fpsi/eu/titanboard/Keymap.java
+++ b/app/src/main/java/cfpi_fpsi/eu/titanboard/Keymap.java
@@ -1,3 +1,22 @@
+/*
+ * An input method for the Unihertz Titan/physical keyboard phones
+ * Copyright (C) 2024 Emil Kosz
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
package cfpi_fpsi.eu.titanboard;
import org.xmlpull.v1.XmlPullParser;
diff --git a/app/src/main/java/cfpi_fpsi/eu/titanboard/MetaKeyManager.java b/app/src/main/java/cfpi_fpsi/eu/titanboard/MetaKeyManager.java
index 6054c11..0325da3 100644
--- a/app/src/main/java/cfpi_fpsi/eu/titanboard/MetaKeyManager.java
+++ b/app/src/main/java/cfpi_fpsi/eu/titanboard/MetaKeyManager.java
@@ -1,62 +1,93 @@
+/*
+ * An input method for the Unihertz Titan/physical keyboard phones
+ * Copyright (C) 2024 Emil Kosz
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
package cfpi_fpsi.eu.titanboard;
+import android.os.Vibrator;
import android.view.KeyEvent;
public class MetaKeyManager {
- private static final int mRepeatCount = 5;
private MetaKey mMetaKeyPressed = MetaKey.NONE;
private MetaKey mMetaKeyLatched = MetaKey.NONE;
private MetaKey mMetaKeyLocked = MetaKey.NONE;
private boolean mMetaKeyUsed = false;
+ private long mKeyUpTime;
+
+ private Vibrator mVibrator;
+
+ public MetaKeyManager(Vibrator vibrator) {
+ mVibrator = vibrator;
+ }
public void updateMetaStateDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ALT_LEFT:
case KeyEvent.KEYCODE_ALT_RIGHT:
- updateMetaKeyDown(MetaKey.ALT, event.getRepeatCount());
+ updateMetaKeyDown(MetaKey.ALT, event);
break;
case KeyEvent.KEYCODE_SHIFT_LEFT:
case KeyEvent.KEYCODE_SHIFT_RIGHT:
- updateMetaKeyDown(MetaKey.SHIFT, event.getRepeatCount());
+ updateMetaKeyDown(MetaKey.SHIFT, event);
break;
case KeyEvent.KEYCODE_SYM:
- updateMetaKeyDown(MetaKey.SYM, event.getRepeatCount());
+ updateMetaKeyDown(MetaKey.SYM, event);
break;
default:
break;
}
}
- void updateMetaKeyDown(MetaKey key, int repeatCount) {
+ void updateMetaKeyDown(MetaKey key, KeyEvent event) {
// Perhaps this function should be renamed to something...
mMetaKeyUsed = false;
mMetaKeyPressed = key;
mMetaKeyLatched = mMetaKeyLocked != MetaKey.NONE ? mMetaKeyLocked : mMetaKeyLatched;
- mMetaKeyLocked = repeatCount > mRepeatCount ? key : MetaKey.NONE;
+ mMetaKeyLocked = MetaKey.NONE;
}
- public void updateMetaStateUp(int keyCode) {
+ public void updateMetaStateUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ALT_LEFT:
case KeyEvent.KEYCODE_ALT_RIGHT:
- updateMetaKeyUp(MetaKey.ALT);
+ updateMetaKeyUp(MetaKey.ALT, event);
break;
case KeyEvent.KEYCODE_SHIFT_LEFT:
case KeyEvent.KEYCODE_SHIFT_RIGHT:
- updateMetaKeyUp(MetaKey.SHIFT);
+ updateMetaKeyUp(MetaKey.SHIFT, event);
break;
case KeyEvent.KEYCODE_SYM:
- updateMetaKeyUp(MetaKey.SYM);
+ updateMetaKeyUp(MetaKey.SYM, event);
break;
default:
break;
}
}
- public void updateMetaKeyUp(MetaKey key) {
+ public void updateMetaKeyUp(MetaKey key, KeyEvent event) {
+ if (event.getEventTime() - mKeyUpTime < 400 && mMetaKeyLatched == key) {
+ mMetaKeyLocked = key;
+ mVibrator.vibrate(30);
+ }
+
+ mKeyUpTime = event.getEventTime();
mMetaKeyPressed = MetaKey.NONE;
- if (mMetaKeyLatched == MetaKey.NONE && mMetaKeyLocked == MetaKey.NONE
- && !mMetaKeyUsed) {
+ if (mMetaKeyLatched == MetaKey.NONE && mMetaKeyLocked == MetaKey.NONE && !mMetaKeyUsed) {
mMetaKeyLatched = key;
} else {
mMetaKeyLatched = MetaKey.NONE;
@@ -67,7 +98,9 @@ public class MetaKeyManager {
if (mMetaKeyLatched != MetaKey.NONE) {
mMetaKeyLatched = MetaKey.NONE;
mMetaKeyUsed = false;
- } else if (mMetaKeyPressed != MetaKey.NONE) {
+ }
+
+ if (mMetaKeyPressed != MetaKey.NONE) {
mMetaKeyUsed = true;
}
}
@@ -79,6 +112,11 @@ public class MetaKeyManager {
mMetaKeyUsed = false;
}
+ public void setMetaKeyLatched(MetaKey key) {
+ resetMetaKeyState();
+ mMetaKeyLatched = key;
+ }
+
public MetaKey getMetaKeyPressed() {
return mMetaKeyPressed;
}
diff --git a/app/src/main/java/cfpi_fpsi/eu/titanboard/PreferencesActivity.java b/app/src/main/java/cfpi_fpsi/eu/titanboard/PreferencesActivity.java
new file mode 100644
index 0000000..cbd8e38
--- /dev/null
+++ b/app/src/main/java/cfpi_fpsi/eu/titanboard/PreferencesActivity.java
@@ -0,0 +1,31 @@
+/*
+ * An input method for the Unihertz Titan/physical keyboard phones
+ * Copyright (C) 2024 Emil Kosz
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package cfpi_fpsi.eu.titanboard;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class PreferencesActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.preferences);
+ }
+}
diff --git a/app/src/main/res/drawable/key.xml b/app/src/main/res/drawable/key.xml
index bf4dc14..cfa2023 100644
--- a/app/src/main/res/drawable/key.xml
+++ b/app/src/main/res/drawable/key.xml
@@ -2,8 +2,10 @@
-
-
+
+
diff --git a/app/src/main/res/layout/input.xml b/app/src/main/res/layout/input.xml
index 3630bf5..b639ef3 100644
--- a/app/src/main/res/layout/input.xml
+++ b/app/src/main/res/layout/input.xml
@@ -14,264 +14,127 @@
android:orientation="horizontal"
android:visibility="visible">
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_weight="1">
-
+ android:orientation="horizontal" />
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="match_parent">
-
+ android:orientation="horizontal">
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/preferences.xml b/app/src/main/res/layout/preferences.xml
new file mode 100644
index 0000000..f34db40
--- /dev/null
+++ b/app/src/main/res/layout/preferences.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7c4ff72..a912810 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,4 +1,5 @@
TitanboardTitanboard
+ Preferences
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 870e2b7..8805bb5 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -18,8 +18,7 @@
18spcentermonospace
- 1
- 0px
+ 72dp
\ No newline at end of file
diff --git a/app/src/main/res/xml/keymap_titan.xml b/app/src/main/res/xml/keymap_titan.xml
index c0c0a32..5368579 100644
--- a/app/src/main/res/xml/keymap_titan.xml
+++ b/app/src/main/res/xml/keymap_titan.xml
@@ -3,7 +3,8 @@
+ valueAlt="@"
+ valueSym="ą" />
+ valueAlt=","
+ valueSym="ć" />
+ valueAlt="_"
+ valueSym="ę" />
+ valueAlt="6"
+ valueSym="ł" />
+ valueAlt="8"
+ valueSym="ń" />
+ valueAlt="3"
+ valueSym="ó" />
+ valueAlt="'"
+ valueSym="ś" />
+ valueAlt="?"
+ valueSym="ź" />
+ valueAlt="!"
+ valueSym="ż" />
\ No newline at end of file