6 Commits
27 changed files with 1975 additions and 1529 deletions
+34 -36
View File
@@ -21,12 +21,13 @@ isn't very reminiscent of a regular QWERTY keyboard which people are used to:
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
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.
7. Emojis.
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
@@ -38,31 +39,22 @@ and so on.
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.
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.
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 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.
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
@@ -73,9 +65,8 @@ fundamental issues, the keyboard should also:
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.
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
@@ -85,14 +76,21 @@ be a square, and so will the remaining view. On the Titan, the screen is already
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.
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.
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.
# 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.
A simple divider between the suggestion and symbol bar is added to avoid accidental clicks.
## 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.
+3 -2
View File
@@ -9,9 +9,10 @@ android {
defaultConfig {
applicationId "cfpi_fpsi.eu.titanboard"
minSdk 19
//noinspection OldTargetApi
targetSdk 34
versionCode 21
versionName "0.2.1"
versionCode 30
versionName "0.3"
}
buildTypes {
+3 -3
View File
@@ -10,7 +10,7 @@
android:supportsRtl="true"
tools:targetApi="31">
<service
android:name=".IME"
android:name=".InputMethod"
android:directBootAware="true"
android:exported="false"
android:icon="@drawable/icon"
@@ -26,8 +26,8 @@
</service>
<activity
android:name=".PreferencesActivity"
android:exported="true"
android:name=".SettingsActivity"
android:exported="false"
android:label="@string/preferences_label">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -1,208 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
*/
package cfpi_fpsi.eu.titanboard;
import android.annotation.SuppressLint;
import android.inputmethodservice.InputMethodService;
import android.os.Vibrator;
import android.util.Log;
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;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
// TODO: Implement long-click for extra keys.
// TODO: Implement auto-correct.
// TODO: Implement auto-caps.
// TODO: Implement device recognition and a keymap picking based on it.
// TODO: Make preferences accessible and saveable.
public class IME extends InputMethodService {
private MetaKeyManager mMetaKeyManager;
private KeymapManager mKeymapManager;
private Deque<String> mLanguageQueue = new ArrayDeque<>();
private String mCurrentLanguage;
private LinearLayout mInputView;
private Vibrator mVibrator;
@Override
public void onCreate() {
super.onCreate();
// Ideally, detect the device, but for now, load all available keymaps.
mKeymapManager = new KeymapManager(getResources().getXml(R.xml.keymaps_titan));
Collections.addAll(mLanguageQueue, mKeymapManager.getAvailableLanguages());
mCurrentLanguage = mLanguageQueue.pop();
mLanguageQueue.addLast(mCurrentLanguage);
mVibrator = (Vibrator) getBaseContext().getSystemService(VIBRATOR_SERVICE);
mMetaKeyManager = new MetaKeyManager(mVibrator);
}
@SuppressLint("InflateParams")
@Override
public void onInitializeInterface() {
mInputView = (LinearLayout) getLayoutInflater().inflate(R.layout.input, null);
}
@Override
public boolean onEvaluateFullscreenMode() {
// This is to override the default behaviour, which seems to do more than detect landscape
// mode.
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
mMetaKeyManager.updateMetaStateDown(keyCode, event);
updateMetaStateTextView();
InputConnection conn = getCurrentInputConnection();
if (conn == null) {
return false;
}
switch (keyCode) {
// 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;
case KeyEvent.KEYCODE_DEL:
case KeyEvent.KEYCODE_ENTER:
mMetaKeyManager.adjustMetaStateAfterKeypress();
return false;
case KeyEvent.KEYCODE_SPACE:
conn.commitText(" ", 1);
mMetaKeyManager.adjustMetaStateAfterKeypress();
return true;
default:
String key = getKey(keyCode);
if (key == null) {
return false;
}
Log.d("STATE", "Sending key: " + key + " whose keyCode is: " + keyCode);
conn.commitText(key, 1);
mMetaKeyManager.adjustMetaStateAfterKeypress();
return true;
}
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
mMetaKeyManager.updateMetaStateUp(keyCode, event);
updateMetaStateTextView();
return false;
}
@Override
public View onCreateInputView() {
return mInputView;
}
@Override
public void onStartInput(EditorInfo info, boolean restarting) {
super.onStartInput(info, restarting);
updateMetaStateTextView();
}
@Override
public void onFinishInput() {
mMetaKeyManager.resetMetaKeyState();
updateMetaStateTextView();
}
private String getKey(int keyCode) {
if (mMetaKeyManager.getAlt().isActive()) {
return mKeymapManager.getKeyAlt(mCurrentLanguage, keyCode);
}
if (mMetaKeyManager.getSym().isActive() && mMetaKeyManager.getShift().isActive()) {
return mKeymapManager.getKeyShiftSym(mCurrentLanguage, keyCode);
}
if (mMetaKeyManager.getSym().isActive()) {
return mKeymapManager.getKeySym(mCurrentLanguage, keyCode);
}
if (mMetaKeyManager.getShift().isActive()) {
return mKeymapManager.getKeyShift(mCurrentLanguage, keyCode);
}
// Nothing active.
return mKeymapManager.getKeyNone(mCurrentLanguage, keyCode);
}
@SuppressLint("SetTextI18n")
private void updateMetaStateTextView() {
TextView shiftStateView = mInputView.findViewById(R.id.state_shift);
TextView altStateView = mInputView.findViewById(R.id.state_alt);
TextView symStateView = mInputView.findViewById(R.id.state_sym);
Button languageButton = mInputView.findViewById(R.id.language_button);
shiftStateView.setText(getStringForMetaKey(
mMetaKeyManager.getShift(), "aa", "Aa", "AA"));
altStateView.setText(getStringForMetaKey(
mMetaKeyManager.getAlt(), "..", "#?", "#!"));
symStateView.setText(getStringForMetaKey(
mMetaKeyManager.getSym(), "..", "þπ", "ÞΩ"));
languageButton.setText(mKeymapManager.getShortLabel(mCurrentLanguage));
}
private String getStringForMetaKey(MetaKeyManager.MetaKey key, String normal, String latched, String pressed) {
if (key.isLatched()) {
return latched;
}
if (key.isLocked() || key.isPressed()) {
return pressed;
}
return normal;
}
public void onInputViewLanguageClick(View v) {
mCurrentLanguage = mLanguageQueue.pop();
mLanguageQueue.addLast(mCurrentLanguage);
updateMetaStateTextView();
}
/**
* @noinspection unused
*/
public void onInputViewKeyClick(View v) {
Button key = (Button) v;
InputConnection conn = getCurrentInputConnection();
if (conn != null) {
conn.commitText(key.getText(), 1);
}
}
}
@@ -0,0 +1,216 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
*/
package cfpi_fpsi.eu.titanboard;
import android.annotation.SuppressLint;
import android.inputmethodservice.InputMethodService;
import android.os.Vibrator;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// TODO: Implement auto-correct.
// TODO: Implement device recognition and a keymap picking based on it.
// TODO: Mode without the bottom input view, in case the user does not care about completions.
// This will involve making a popup screen and a shortcut for changing languages.
public class InputMethod extends InputMethodService {
public MetaKeyManager mMetaKeyManager;
public KeymapManager mKeymapManager;
public InputView mInputView;
public Settings mSettings;
public int mCurrentCursorPosition;
private SymbolCycler mSymbolCycler;
private long mLastKeyTime;
private int mSymbolPressed;
@Override
public void onCreate() {
super.onCreate();
mSettings = new Settings(this);
Vibrator mVibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
mMetaKeyManager = new MetaKeyManager(mVibrator);
mKeymapManager = new KeymapManager(this);
mSymbolCycler = new SymbolCycler(this);
mKeymapManager.cycleKeymaps(mSettings.getSelectedKeymaps());
}
@SuppressLint("InflateParams")
@Override
public void onInitializeInterface() {
mInputView = (InputView) getLayoutInflater().inflate(R.layout.input, null);
mInputView.updateStateText();
}
@Override
public boolean onEvaluateFullscreenMode() {
// This is to override the default behaviour, which seems to do more than detect landscape
// mode.
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
mMetaKeyManager.updateMetaStateDown(keyCode);
mInputView.updateStateText();
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
if (isInputViewShown()) {
requestHideSelf(0);
return true;
}
return false;
case KeyEvent.KEYCODE_DEL:
case KeyEvent.KEYCODE_ENTER:
mMetaKeyManager.adjustMetaStateAfterKeypress();
return false;
case KeyEvent.KEYCODE_SPACE:
handleSpacebar(event);
return true;
default:
ArrayList<String> symbols = mKeymapManager.getSymbols(keyCode, mMetaKeyManager);
if (symbols == null) {
return false;
}
if (event.getRepeatCount() == 0 && !symbols.isEmpty()) {
getCurrentInputConnection().commitText(symbols.get(0), 1);
mMetaKeyManager.adjustMetaStateAfterKeypress();
mInputView.updateStateText();
mSymbolPressed = keyCode;
mLastKeyTime = event.getEventTime();
mSymbolCycler.stopCycling();
mSymbolCycler.startCycling(symbols, getCurrentInputConnection());
if (!mSettings.isCyclingWithSpaceEnabled()) {
mSymbolCycler.startAutomaticCycling();
}
}
return true;
}
}
public void handleSpacebar(KeyEvent event) {
InputConnection conn = getCurrentInputConnection();
if (mSettings.isCyclingWithSpaceEnabled()
&& mSymbolPressed != -1 && event.getEventTime() - mLastKeyTime > 200) {
mSymbolCycler.cycle();
return;
}
if (mSettings.isAutoPeriodsEnabled()) {
String text = getTextBeforeCursor(2);
if (!text.isEmpty()) {
Pattern pattern = Pattern.compile("[^\\.… ]+ $");
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
// InputConnection.replaceSelection needs a higher API, so we just do exactly
// what it does to support more devices.
conn.finishComposingText();
conn.setSelection(mCurrentCursorPosition - 1, mCurrentCursorPosition);
conn.commitText(".", 1);
}
}
}
conn.commitText(" ", 1);
mMetaKeyManager.adjustMetaStateAfterKeypress();
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (mSymbolPressed == keyCode) {
mSymbolPressed = -1;
mSymbolCycler.stopCycling();
mInputView.updateSymbolsView(null, 0);
}
// TODO: Not ideal. Should be handled on key down ideally.
if (keyCode == KeyEvent.KEYCODE_DEL) {
detectAutoCapitalization();
}
getCurrentInputConnection().finishComposingText();
mMetaKeyManager.updateMetaStateUp(keyCode, event);
mInputView.updateStateText();
return false;
}
@Override
public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd,
int candidatesStart, int candidatesEnd) {
mCurrentCursorPosition = newSelStart;
detectAutoCapitalization();
}
public String getTextBeforeCursor(int n) {
CharSequence chars = getCurrentInputConnection().getTextBeforeCursor(n, 0);
return chars == null ? "" : String.valueOf(chars);
}
public void detectAutoCapitalization() {
if (!mSettings.isAutoCapsEnabled()) {
return;
}
// Get a bunch of text, run a regex through it, and bam. Is there a better way to do this?
String text = getTextBeforeCursor(16);
if (text.isEmpty()) {
mMetaKeyManager.getShift().setLatched(true);
mInputView.updateStateText();
} else {
// If, on the other hand, we have some characters before the cursor, we scan.
Pattern pattern = Pattern.compile("[\\.…\\!\\?] +$");
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
mMetaKeyManager.getShift().setLatched(true);
mInputView.updateStateText();
}
}
}
@Override
public View onCreateInputView() {
return mInputView;
}
@Override
public void onStartInput(EditorInfo info, boolean restarting) {
super.onStartInput(info, restarting);
detectAutoCapitalization();
mKeymapManager.checkCurrentKeymap(mSettings.getSelectedKeymaps());
mInputView.updateStateText();
}
@Override
public void onFinishInput() {
mMetaKeyManager.resetMetaKeyState();
mInputView.updateStateText();
}
}
@@ -0,0 +1,126 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
*/
package cfpi_fpsi.eu.titanboard;
import android.content.Context;
import android.content.Intent;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.ArrayList;
public class InputView extends RelativeLayout {
private final InputMethod mInputMethod;
private Button mStateButton;
private LinearLayout mMultiList;
public InputView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
mInputMethod = (InputMethod) context;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMultiList = findViewById(R.id.multi_list);
mStateButton = findViewById(R.id.input_state);
mStateButton.setOnClickListener(button -> {
MetaKeyManager metaManager = mInputMethod.mMetaKeyManager;
if (metaManager.getAlt().isActive()) {
metaManager.getAlt().resetState();
} else {
mInputMethod.mKeymapManager.cycleKeymaps(mInputMethod.mSettings.getSelectedKeymaps());
}
updateStateText();
});
mStateButton.setOnLongClickListener(button -> {
Intent intent = new Intent(getContext(), SettingsActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(intent);
return true;
});
}
public void updateStateText() {
MetaKeyManager metaManager = mInputMethod.mMetaKeyManager;
KeymapManager keymapManager = mInputMethod.mKeymapManager;
mStateButton.setPaintFlags(mStateButton.getPaintFlags() & ~(Paint.UNDERLINE_TEXT_FLAG));
if (metaManager.getAlt().isPressed()) {
mStateButton.setText(R.string.inputview_symbols_locked_state);
return;
}
if (metaManager.getAlt().isLocked()) {
mStateButton.setText(R.string.inputview_symbols_locked_state);
mStateButton.setPaintFlags(mStateButton.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
return;
}
if (metaManager.getAlt().isLatched()) {
mStateButton.setText(R.string.inputview_symbols_latched_state);
return;
}
if (metaManager.getShift().isPressed()) {
mStateButton.setText(keymapManager.getCurrentKeymap().getLockedCode());
return;
}
if (metaManager.getShift().isLocked()) {
mStateButton.setText(keymapManager.getCurrentKeymap().getLockedCode());
mStateButton.setPaintFlags(mStateButton.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
return;
}
if (metaManager.getShift().isLatched()) {
mStateButton.setText(keymapManager.getCurrentKeymap().getLatchedCode());
return;
}
mStateButton.setText(keymapManager.getCurrentKeymap().getCode());
}
public void updateSymbolsView(ArrayList<String> symbols, int index) {
for (int i = 0; i < mMultiList.getChildCount(); ++i) {
TextView view = (TextView) mMultiList.getChildAt(i);
view.setText("");
}
if (symbols != null) {
for (int i = 0; i < Math.min(mMultiList.getChildCount(), symbols.size()); ++i) {
TextView view = (TextView) mMultiList.getChildAt(i);
view.setText(symbols.get(i));
if (i == index) {
view.setTextAppearance(getContext(), R.style.InputViewSymbolSelected);
} else {
view.setTextAppearance(getContext(), R.style.InputViewSymbol);
}
}
}
}
}
@@ -19,86 +19,93 @@
package cfpi_fpsi.eu.titanboard;
import android.util.Log;
import android.content.Context;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
public class KeymapManager {
// Yeah... I know this is a little wacky.
private final Context mContext;
private final HashMap<String, Keymap> mKeymaps = new HashMap<>();
public Keymap mCurrentKeymap;
public KeymapManager(XmlPullParser parser) {
public KeymapManager(Context context) {
mContext = context;
loadDeviceKeymaps(mContext.getResources().getXml(R.xml.keymaps_titan));
}
private void loadDeviceKeymaps(XmlPullParser parser) {
Keymap keymap = null;
String lang = null, shortLabel = null, label = null;
int code = 0;
String none = null, shift = null, alt = null, sym = null, shiftSym = null;
String name = null, latchedName = null, lockedName = null;
Key key = null;
try {
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
int type = parser.getEventType();
// We encounter a new keymap.
if (type == XmlPullParser.START_TAG && parser.getName().equals("keymap")) {
keymap = new Keymap();
lang = null;
shortLabel = null;
label = null;
for (int i = 0; i < parser.getAttributeCount(); ++i) {
switch (parser.getAttributeName(i)) {
case "lang":
lang = parser.getAttributeValue(i);
case "name":
name = parser.getAttributeValue(i);
break;
case "short-label":
shortLabel = parser.getAttributeValue(i);
case "latched-name":
latchedName = parser.getAttributeValue(i);
break;
case "label":
label = parser.getAttributeValue(i);
case "locked-name":
lockedName = parser.getAttributeValue(i);
break;
}
}
Log.d("STATE", "BEGINNING OF KEYMAP: " + label);
} else if (type == XmlPullParser.END_TAG && parser.getName().equals("keymap")) {
Log.d("STATE", "END OF KEYMAP: " + label);
keymap.mShortLabel = shortLabel;
keymap.mLabel = label;
mKeymaps.put(lang, keymap);
} else if (type == XmlPullParser.END_TAG && parser.getName().equals("keymap")
&& keymap != null) {
keymap.setStrings(name, latchedName, lockedName);
mKeymaps.put(name, keymap);
}
// We got a new key.
if (type == XmlPullParser.START_TAG && parser.getName().equals("key")) {
none = null;
shift = null;
alt = null;
sym = null;
shiftSym = null;
key = new Key();
for (int i = 0; i < parser.getAttributeCount(); ++i) {
String attr = parser.getAttributeName(i);
switch (attr) {
case "code":
code = Integer.parseInt(parser.getAttributeValue(i));
break;
case "none":
none = parser.getAttributeValue(i);
break;
case "shift":
shift = parser.getAttributeValue(i);
break;
case "alt":
alt = parser.getAttributeValue(i);
break;
case "sym":
sym = parser.getAttributeValue(i);
break;
case "shiftSym":
shiftSym = parser.getAttributeValue(i);
break;
if (parser.getAttributeName(i).equals("code")) {
code = Integer.parseInt(parser.getAttributeValue(i));
}
}
} else if (type == XmlPullParser.END_TAG && parser.getName().equals("key")
&& keymap != null) {
keymap.put(code, key);
}
// We encountered a base value.
if (type == XmlPullParser.START_TAG && parser.getName().equals("base")) {
for (int i = 0; i < parser.getAttributeCount(); ++i) {
if (parser.getAttributeName(i).startsWith("value") && key != null) {
key.getSymbolsBase().add(parser.getAttributeValue(i));
}
}
}
// We encountered a shift value.
if (type == XmlPullParser.START_TAG && parser.getName().equals("shift")) {
for (int i = 0; i < parser.getAttributeCount(); ++i) {
if (parser.getAttributeName(i).startsWith("value") && key != null) {
key.getSymbolsShift().add(parser.getAttributeValue(i));
}
}
}
// We encountered an alt value.
if (type == XmlPullParser.START_TAG && parser.getName().equals("alt")) {
for (int i = 0; i < parser.getAttributeCount(); ++i) {
if (parser.getAttributeName(i).startsWith("value") && key != null) {
key.getSymbolsAlt().add(parser.getAttributeValue(i));
}
}
Log.d("STATE", "Key code: " + code + ". Value sym: " + sym);
} else if (type == XmlPullParser.END_TAG && parser.getName().equals("key")) {
keymap.put(code, new Key(none, shift, alt, sym, shiftSym));
}
parser.next();
@@ -108,96 +115,93 @@ public class KeymapManager {
}
}
public String[] getAvailableLanguages() {
return mKeymaps.keySet().toArray(new String[0]);
public ArrayList<String> getSymbols(int keyCode, MetaKeyManager metaKeyManager) {
if (metaKeyManager.getAlt().isActive()) {
return getCurrentKeymap().getSymbolsAlt(keyCode);
}
if (metaKeyManager.getShift().isActive()) {
return getCurrentKeymap().getSymbolsShift(keyCode);
}
return getCurrentKeymap().getSymbolsBase(keyCode);
}
public String getShortLabel(String lang) {
Keymap keymap = mKeymaps.get(lang);
if (keymap != null) {
return keymap.mShortLabel;
} else {
return null;
public void checkCurrentKeymap(ArrayList<String> selectedKeymaps) {
if (!selectedKeymaps.contains(mCurrentKeymap.mCode)) {
cycleKeymaps(selectedKeymaps);
}
}
public String getKeyNone(String lang, int keyCode) {
Key key = getKey(lang, keyCode);
if (key != null) {
return key.mNone;
} else {
return null;
public void cycleKeymaps(ArrayList<String> selectedKeymaps) {
if (selectedKeymaps.isEmpty() || mCurrentKeymap == null) {
mCurrentKeymap = mKeymaps.get(mContext.getResources().getString(R.string.keymap_default));
return;
}
int index = selectedKeymaps.indexOf(mCurrentKeymap.mCode);
index++;
if (index == selectedKeymaps.size()) {
index = 0;
}
mCurrentKeymap = mKeymaps.get(selectedKeymaps.get(index));
}
public String getKeyShift(String lang, int keyCode) {
Key key = getKey(lang, keyCode);
if (key != null) {
return key.mShift;
} else {
return null;
}
}
public Keymap getCurrentKeymap() { return mCurrentKeymap; }
public String getKeyAlt(String lang, int keyCode) {
Key key = getKey(lang, keyCode);
if (key != null) {
return key.mAlt;
} else {
return null;
}
}
public static class Keymap extends HashMap<Integer, Key> {
private String mCode; // A two-letter code for when the keyboard is lowercase.
private String mLatchedCode; // Above, but when the keyboard is shift latched.
private String mLockedCode; // Above, but when the shift is locked/pressed.
public String getKeySym(String lang, int keyCode) {
Key key = getKey(lang, keyCode);
if (key != null) {
return key.mSym;
} else {
return null;
public String getCode() {
return mCode;
}
}
public String getKeyShiftSym(String lang, int keyCode) {
Key key = getKey(lang, keyCode);
if (key != null) {
return key.mShiftSym;
} else {
return null;
public String getLatchedCode() {
return mLatchedCode;
}
}
public Key getKey(String lang, int keyCode) {
HashMap<Integer, Key> keymap = mKeymaps.get(lang);
if (keymap != null) {
Key key = keymap.get(keyCode);
if (key != null) {
return key;
} else {
return null;
}
} else {
return null;
public String getLockedCode() {
return mLockedCode;
}
}
private static class Keymap extends HashMap<Integer, Key> {
public String mShortLabel;
public String mLabel;
public void setStrings(String code, String latched, String locked) {
mCode = code;
mLatchedCode = latched;
mLockedCode = locked;
}
public ArrayList<String> getSymbolsBase(int keyCode) {
return get(keyCode) != null ? get(keyCode).getSymbolsBase() : null;
}
public ArrayList<String> getSymbolsAlt(int keyCode) {
return get(keyCode) != null ? get(keyCode).getSymbolsAlt() : null;
}
public ArrayList<String> getSymbolsShift(int keyCode) {
return get(keyCode) != null ? get(keyCode).getSymbolsShift() : null;
}
}
public static class Key {
public String mNone;
public String mShift;
public String mAlt;
public String mSym;
public String mShiftSym;
private final ArrayList<String> mSymbolsBase = new ArrayList<>();
private final ArrayList<String> mSymbolsShift = new ArrayList<>();
private final ArrayList<String> mSymbolsAlt = new ArrayList<>();
public Key(String none, String shift, String alt, String sym, String shiftSym) {
mNone = none;
mShift = shift;
mAlt = alt;
mSym = sym;
mShiftSym = shiftSym;
public ArrayList<String> getSymbolsBase() {
return mSymbolsBase;
}
public ArrayList<String> getSymbolsShift() {
return mSymbolsShift;
}
public ArrayList<String> getSymbolsAlt() {
return mSymbolsAlt;
}
}
}
@@ -23,47 +23,36 @@ import android.os.Vibrator;
import android.view.KeyEvent;
public class MetaKeyManager {
public MetaKey mShift = new MetaKey();
public MetaKey mAlt = new MetaKey();
public MetaKey mSym = new MetaKey();
public final MetaKey mShift = new MetaKey();
public final MetaKey mAlt = new MetaKey();
private Vibrator mVibrator;
private final Vibrator mVibrator;
public MetaKeyManager(Vibrator vibrator) {
mVibrator = vibrator;
}
public void updateMetaStateDown(int keyCode, KeyEvent event) {
public void updateMetaStateDown(int keyCode) {
switch (keyCode) {
case KeyEvent.KEYCODE_ALT_LEFT:
case KeyEvent.KEYCODE_ALT_RIGHT:
updateMetaKeyDown(mAlt, event);
updateMetaKeyDown(mAlt);
break;
case KeyEvent.KEYCODE_SHIFT_LEFT:
case KeyEvent.KEYCODE_SHIFT_RIGHT:
updateMetaKeyDown(mShift, event);
break;
case KeyEvent.KEYCODE_SYM:
updateMetaKeyDown(mSym, event);
updateMetaKeyDown(mShift);
break;
default:
break;
}
}
void updateMetaKeyDown(MetaKey key, KeyEvent event) {
void updateMetaKeyDown(MetaKey key) {
// Perhaps this function should be renamed to something...
key.setUsed(false);
key.setPressed(true);
key.setLatched(key.isLocked() ? key.isLocked() : key.isLatched());
key.setLocked(false);
if (mShift.isPressed() && mAlt.isPressed()) {
mSym.setLatched(!mSym.isLatched());
mShift.setUsed(true);
mAlt.setUsed(true);
mVibrator.vibrate(20);
}
}
public void updateMetaStateUp(int keyCode, KeyEvent event) {
@@ -76,9 +65,6 @@ public class MetaKeyManager {
case KeyEvent.KEYCODE_SHIFT_RIGHT:
updateMetaKeyUp(mShift, event);
break;
case KeyEvent.KEYCODE_SYM:
updateMetaKeyUp(mSym, event);
break;
default:
break;
}
@@ -99,7 +85,6 @@ public class MetaKeyManager {
public void adjustMetaStateAfterKeypress() {
adjustMetaStateHelper(mShift);
adjustMetaStateHelper(mAlt);
adjustMetaStateHelper(mSym);
}
public void adjustMetaStateHelper(MetaKey key) {
@@ -116,7 +101,6 @@ public class MetaKeyManager {
public void resetMetaKeyState() {
mShift.resetState();
mAlt.resetState();
mSym.resetState();
}
public MetaKey getShift() {
@@ -127,10 +111,6 @@ public class MetaKeyManager {
return mAlt;
}
public MetaKey getSym() {
return mSym;
}
public static class MetaKey {
private boolean mPressed = false;
private boolean mLatched = false;
@@ -188,11 +168,5 @@ public class MetaKeyManager {
public void setKeyUpTime(long time) {
mKeyUpTime = time;
}
public enum MetaKeyType {
ALT,
SHIFT,
SYM
}
}
}
@@ -0,0 +1,91 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
*/
package cfpi_fpsi.eu.titanboard;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
/**
* @noinspection deprecation
*/
public class Settings implements SharedPreferences.OnSharedPreferenceChangeListener {
private final Map<String, Object> mPreferenceValues;
private final Context mContext;
@SuppressWarnings("unchecked")
public Settings(Context context) {
mContext = context;
SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
PreferenceManager.setDefaultValues(mContext, R.xml.preferences, false);
mPreferences.registerOnSharedPreferenceChangeListener(this);
mPreferenceValues = (Map<String, Object>) mPreferences.getAll();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Object value = sharedPreferences.getAll().get(key);
if (value != null) {
mPreferenceValues.put(key, value);
}
}
public Object getOrDefault(String key, Object defaultValue) {
Object value = mPreferenceValues.get(key);
return value == null ? defaultValue : value;
}
public boolean isAutoCapsEnabled() {
return (boolean) getOrDefault("auto_caps",
mContext.getResources().getBoolean(R.bool.auto_caps_default));
}
public boolean isAutoPeriodsEnabled() {
return (boolean) getOrDefault("auto_periods",
mContext.getResources().getBoolean(R.bool.auto_periods_default));
}
public boolean isAutocorrectEnabled() {
return (boolean) getOrDefault("auto_correct",
mContext.getResources().getBoolean(R.bool.autocorrect_default));
}
public boolean isCyclingWithSpaceEnabled() {
return (boolean) getOrDefault("cycle_with_space",
mContext.getResources().getBoolean(R.bool.cycle_with_space_default));
}
public int getCycleSpeed() {
return Integer.parseInt((String) getOrDefault("cycle_speed",
mContext.getResources().getString(R.string.cycle_speed_default)));
}
@SuppressWarnings("unchecked")
public ArrayList<String> getSelectedKeymaps() {
Set<String> selectedKeymaps = (Set<String>)
getOrDefault("selected_keymaps",
mContext.getResources().getStringArray(R.array.selected_keymap_defaults));
return new ArrayList<>(selectedKeymaps);
}
}
@@ -19,13 +19,19 @@
package cfpi_fpsi.eu.titanboard;
import android.app.Activity;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class PreferencesActivity extends Activity {
import java.util.Objects;
/**
* @noinspection deprecation
*/
public class SettingsActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.preferences);
addPreferencesFromResource(R.xml.preferences);
Objects.requireNonNull(getActionBar()).setDisplayHomeAsUpEnabled(true);
}
}
@@ -0,0 +1,74 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
*/
package cfpi_fpsi.eu.titanboard;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.view.inputmethod.InputConnection;
import java.util.ArrayList;
public class SymbolCycler extends Handler {
private final InputMethod mInputMethod;
private final SymbolCyclerRunnable mRunnable;
private int mIndex;
private ArrayList<String> mSymbols;
private InputConnection mConn;
public SymbolCycler(Context context) {
super(Looper.getMainLooper());
mInputMethod = (InputMethod) context;
mRunnable = new SymbolCyclerRunnable();
}
public void startCycling(ArrayList<String> symbols, InputConnection conn) {
mSymbols = symbols;
mConn = conn;
mIndex = 0;
}
public void startAutomaticCycling() {
postDelayed(mRunnable, mInputMethod.mSettings.getCycleSpeed());
}
public void stopCycling() {
removeCallbacks(mRunnable);
}
public void cycle() {
mIndex++;
if (mIndex == mSymbols.size()) {
mIndex = 0;
}
mInputMethod.mInputView.updateSymbolsView(mSymbols, mIndex);
mConn.setComposingRegion(mInputMethod.mCurrentCursorPosition - 1, mInputMethod.mCurrentCursorPosition);
mConn.setComposingText(mSymbols.get(mIndex), 1);
}
private class SymbolCyclerRunnable implements Runnable {
@Override
public void run() {
cycle();
postDelayed(this, mInputMethod.mSettings.getCycleSpeed());
}
}
}
@@ -0,0 +1,35 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
*/
package cfpi_fpsi.eu.titanboard;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
public class SymbolsView extends RelativeLayout {
public SymbolsView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
}
}
+33
View File
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <https://www.gnu.org/licenses/>.
~
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<item android:state_pressed="false">
<shape android:shape="rectangle">
<solid android:color="#333" />
<corners android:radius="8dp" />
</shape>
</item>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#555" />
<corners android:radius="8dp" />
</shape>
</item>
</selector>
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false">
<shape>
<solid android:color="#333" />
</shape>
</item>
<item android:state_pressed="true">
<shape>
<solid android:color="#555" />
</shape>
</item>
</selector>
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false">
<shape>
<solid android:color="#222" />
</shape>
</item>
<item android:state_pressed="true">
<shape>
<solid android:color="#333" />
</shape>
</item>
</selector>
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <https://www.gnu.org/licenses/>.
~
-->
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="45"
android:pivotX="100%"
android:pivotY="-50%">
<shape android:shape="rectangle">
<solid android:color="#333" />
</shape>
</rotate>
+46 -130
View File
@@ -1,155 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/KeyboardBackground"
<cfpi_fpsi.eu.titanboard.InputView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/InputViewBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="vertical">
<LinearLayout
android:id="@+id/candidates"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
android:visibility="visible">
<TextView
android:id="@+id/state_shift"
style="@style/KeyboardState"
android:layout_height="wrap_content"
android:text="aa"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/state_alt"
style="@style/KeyboardState"
android:layout_height="wrap_content"
android:text=".."
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/state_sym"
style="@style/KeyboardState"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text=".."
tools:ignore="HardcodedText" />
<Button
android:id="@+id/language_button"
style="@style/KeyboardExtraButton"
android:layout_height="wrap_content"
android:onClick="onInputViewLanguageClick" />
</LinearLayout>
<View
style="@style/KeyboardDivider"
android:layout_width="match_parent" />
android:layout_gravity="bottom">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:layout_alignTop="@id/input_state"
android:layout_alignBottom="@id/input_state"
android:layout_toStartOf="@id/input_state">
<LinearLayout
android:id="@+id/multi_list"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:text="^"
tools:ignore="HardcodedText" />
<TextView
style="@style/InputViewSymbol"
android:layout_height="wrap_content" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:text="~"
tools:ignore="HardcodedText" />
<TextView
style="@style/InputViewSymbol"
android:layout_height="wrap_content" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:text="\\"
tools:ignore="HardcodedText" />
<TextView
style="@style/InputViewSymbol"
android:layout_height="wrap_content" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:text="="
tools:ignore="HardcodedText" />
<TextView
style="@style/InputViewSymbol"
android:layout_height="wrap_content" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:text="&amp;"
tools:ignore="HardcodedText" />
<TextView
style="@style/InputViewSymbol"
android:layout_height="wrap_content" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:text=";"
tools:ignore="HardcodedText" />
<TextView
style="@style/InputViewSymbol"
android:layout_height="wrap_content" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:text="&lt;"
tools:ignore="HardcodedText" />
<TextView
style="@style/InputViewSymbol"
android:layout_height="wrap_content" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:text="&gt;"
tools:ignore="HardcodedText" />
<TextView
style="@style/InputViewSymbol"
android:layout_height="wrap_content" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="¿"
tools:ignore="HardcodedText" />
<TextView
style="@style/InputViewSymbol"
android:layout_height="wrap_content" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="¡"
tools:ignore="HardcodedText" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:text="|"
tools:ignore="HardcodedText" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:text="["
tools:ignore="HardcodedText" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:text="]"
tools:ignore="HardcodedText" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:text="{"
tools:ignore="HardcodedText" />
<Button
style="@style/KeyboardKey"
android:layout_height="wrap_content"
android:text="}"
tools:ignore="HardcodedText" />
<TextView
style="@style/InputViewSymbol"
android:layout_height="wrap_content" />
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
<Button
android:id="@+id/input_state"
style="@style/InputViewButton"
android:layout_alignParentEnd="true"
android:layout_marginStart="8dp"
android:text="@string/inputview_default_state" />
</cfpi_fpsi.eu.titanboard.InputView>
-28
View File
@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Settings:" />
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Auto-caps" />
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Auto-period" />
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Languages:" />
</LinearLayout>
+56
View File
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <https://www.gnu.org/licenses/>.
~
-->
<cfpi_fpsi.eu.titanboard.SymbolsView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageView"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_below="@id/textView"
android:src="@drawable/triangle" />
<TextView
android:id="@+id/textView"
style="@style/SymbolsViewSymbol"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:text="A" />
<TextView
android:id="@+id/textView2"
style="@style/SymbolsViewSymbol"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/textView"
android:text="B" />
<TextView
android:id="@+id/textView3"
style="@style/SymbolsViewSymbol"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/textView2"
android:text="C" />
</cfpi_fpsi.eu.titanboard.SymbolsView>
+59
View File
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <https://www.gnu.org/licenses/>.
~
-->
<resources>
<string-array name="available_keymap_labels">
<item>English</item>
<item>Russian (Phonetic)</item>
<item>French</item>
<item>Polish</item>
<item>Greek</item>
</string-array>
<string-array name="available_keymaps">
<item>en</item>
<item>ru</item>
<item>fr</item>
<item>pl</item>
<item>el</item>
</string-array>
<string-array name="selected_keymap_defaults">
<item>en</item>
<item>ru</item>
</string-array>
<string-array name="cycle_speed_labels">
<item>200 ms</item>
<item>300 ms</item>
<item>400 ms</item>
<item>500 ms</item>
<item>750 ms</item>
<item>1000 ms</item>
</string-array>
<string-array name="cycle_speeds">
<item>200</item>
<item>300</item>
<item>400</item>
<item>500</item>
<item>750</item>
<item>1000</item>
</string-array>
</resources>
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <https://www.gnu.org/licenses/>.
~
-->
<resources>
<bool name="auto_caps_default">true</bool>
<bool name="auto_periods_default">true</bool>
<bool name="autocorrect_default">false</bool>
<bool name="cycle_with_space_default">false</bool>
</resources>
+16
View File
@@ -1,5 +1,21 @@
<resources>
<string name="app_name">Titanboard</string>
<string name="ime_label">Titanboard</string>
<string name="preferences_label">Titanboard preferences</string>
<string name="preferences_general_settings">General</string>
<string name="preferences_auto_caps">Automatic capitalization</string>
<string name="preferences_auto_periods">Automatic periods</string>
<string name="preferences_auto_correct">Autocorrect</string>
<string name="preferences_editing_settings">Editing and keymaps</string>
<string name="preferences_cycle_speed">Cycling speed</string>
<string name="preferences_cycle_with_space">Cycle symbols with the space key</string>
<string name="preferences_keymaps">Keymaps</string>
<string name="inputview_default_state">\?\?</string>
<string name="inputview_symbols_latched_state">#?</string>
<string name="inputview_symbols_locked_state">#!</string>
<string name="keymap_default">en</string>
<string name="cycle_speed_default">500</string>
</resources>
+27 -42
View File
@@ -1,59 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="KeyboardBackground">
<style name="InputViewBackground">
<item name="android:background">#222</item>
<item name="android:padding">2dp</item>
<item name="android:padding">8dp</item>
</style>
<style name="KeyboardDivider">
<item name="android:layout_height">3dp</item>
<item name="android:layout_marginTop">4dp</item>
<item name="android:layout_marginBottom">2dp</item>
<item name="android:background">#444</item>
</style>
<style name="KeyboardState">
<style name="InputViewButton">
<item name="android:background">@drawable/button</item>
<item name="android:textColor">#AAA</item>
<item name="android:textSize">18sp</item>
<item name="android:textAlignment">center</item>
<item name="android:typeface">monospace</item>
<item name="android:layout_width">40dp</item>
<item name="android:layout_gravity">center</item>
</style>
<style name="KeyboardExtraButton">
<item name="android:background">@drawable/key</item>
<item name="android:textColor">#FFF</item>
<item name="android:textSize">18sp</item>
<item name="android:layout_marginLeft">1dp</item>
<item name="android:layout_marginRight">1dp</item>
<item name="android:layout_marginTop">2dp</item>
<item name="android:layout_marginBottom">2dp</item>
<item name="android:gravity">center_vertical</item>
<item name="android:layout_width">48dp</item>
<item name="android:layout_height">48dp</item>
<item name="android:clickable">true</item>
</style>
<style name="KeyboardStateLocked" parent="KeyboardState">
<style name="InputViewSymbol">
<item name="android:textColor">#AAA</item>
<item name="android:textSize">18sp</item>
<item name="android:textAlignment">center</item>
<item name="android:layout_width">32dp</item>
<item name="android:layout_height">48dp</item>
<item name="android:layout_marginStart">8dp</item>
<item name="android:layout_weight">0</item>
<item name="android:layout_gravity">center_vertical</item>
</style>
<style name="InputViewSymbolSelected" parent="InputViewSymbol">
<item name="android:textColor">#DDD</item>
<item name="android:textStyle">bold</item>
</style>
<style name="KeyboardCandidate">
<item name="android:background">@drawable/suggestion</item>
<item name="android:textColor">#FFF</item>
<item name="android:textSize">18sp</item>
<item name="android:layout_marginTop">2dp</item>
<item name="android:layout_width">wrap_content</item>
</style>
<style name="KeyboardKey">
<item name="android:background">@drawable/key</item>
<item name="android:textColor">#FFF</item>
<item name="android:textSize">18sp</item>
<item name="android:layout_marginLeft">1dp</item>
<item name="android:layout_marginRight">1dp</item>
<item name="android:layout_marginTop">2dp</item>
<item name="android:layout_marginBottom">2dp</item>
<item name="android:onClick">onInputViewKeyClick</item>
<item name="android:layout_width">72dp</item>
<style name="SymbolsViewSymbol">
<item name="android:background">#333</item>
<item name="android:textColor">#AAA</item>
<item name="android:textSize">16sp</item>
<item name="android:textAlignment">center</item>
<item name="android:padding">8dp</item>
<item name="android:layout_gravity">center_vertical</item>
</style>
</resources>
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="cfpi_fpsi.eu.titanboard.PreferencesActivity"
android:supportsSwitchingToNextInputMethod="false ">
android:settingsActivity="cfpi_fpsi.eu.titanboard.SettingsActivity"
android:supportsSwitchingToNextInputMethod="false">
<subtype
android:imeSubtypeMode="keyboard"
android:isAsciiCapable="true"
+57
View File
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <https://www.gnu.org/licenses/>.
~
-->
<!--suppress DeprecatedClassUsageInspection -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/preferences_general_settings">
<SwitchPreference
android:defaultValue="@bool/auto_caps_default"
android:key="auto_caps"
android:title="@string/preferences_auto_caps" />
<SwitchPreference
android:defaultValue="@bool/auto_periods_default"
android:key="auto_periods"
android:title="@string/preferences_auto_periods" />
<SwitchPreference
android:defaultValue="@bool/autocorrect_default"
android:enabled="false"
android:key="autocorrect"
android:title="@string/preferences_auto_correct" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/preferences_editing_settings">
<MultiSelectListPreference
android:defaultValue="@array/selected_keymap_defaults"
android:entries="@array/available_keymap_labels"
android:entryValues="@array/available_keymaps"
android:key="selected_keymaps"
android:title="@string/preferences_keymaps" />
<SwitchPreference
android:defaultValue="@bool/cycle_with_space_default"
android:key="cycle_with_space"
android:title="@string/preferences_cycle_with_space" />
<ListPreference
android:defaultValue="@string/cycle_speed_default"
android:entries="@array/cycle_speed_labels"
android:entryValues="@array/cycle_speeds"
android:key="cycle_speed"
android:title="@string/preferences_cycle_speed" />
</PreferenceCategory>
</PreferenceScreen>
+1
View File
@@ -1,4 +1,5 @@
[versions]
#noinspection AndroidGradlePluginVersion
agp = "8.4.1"
[libraries]