1
0
Fork 0

full hardware key support

This commit is contained in:
sspanak 2025-02-28 10:54:42 +02:00 committed by Dimo Karaivanov
parent ddabff76a4
commit 3626ac24ea
20 changed files with 542 additions and 494 deletions

View file

@ -142,15 +142,6 @@ To translate Traditional T9 menus and messages in your language, add: `res/value
Alternatively, if you don't have Android Studio, you could just use `res/values/strings.xml` as a reference and translate all strings in your file, skipping the ones that have the `translatable="false"` attribute. Alternatively, if you don't have Android Studio, you could just use `res/values/strings.xml` as a reference and translate all strings in your file, skipping the ones that have the `translatable="false"` attribute.
## Adding Support for New Hardware Keys (Hotkeys)
TT9 allows assigning hotkeys for performing different functions. If your phone has a special key that does not appear on the Hotkey configuration screen, you can easily add support for it.
- In `preferences/helpers/Hotkeys.java`, find the `generateList()` function.
- Add the new key there. The order of adding is used when displaying the dropdown options.
- Optionally, you can translate the name of the key in different languages in the `res/values-XX/strings.xml` files.
_You can find the key codes [in the Android docs](https://developer.android.com/reference/android/view/KeyEvent)._
## Contribution Process ## Contribution Process
### Before you start ### Before you start

View file

@ -7,7 +7,6 @@ import io.github.sspanak.tt9.db.words.DictionaryLoader;
import io.github.sspanak.tt9.ime.helpers.TextField; import io.github.sspanak.tt9.ime.helpers.TextField;
import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.ime.modes.InputModeKind; import io.github.sspanak.tt9.ime.modes.InputModeKind;
import io.github.sspanak.tt9.preferences.helpers.Hotkeys;
import io.github.sspanak.tt9.ui.UI; import io.github.sspanak.tt9.ui.UI;
import io.github.sspanak.tt9.util.Ternary; import io.github.sspanak.tt9.util.Ternary;
@ -16,7 +15,7 @@ public abstract class HotkeyHandler extends CommandHandler {
protected void onInit() { protected void onInit() {
super.onInit(); super.onInit();
if (settings.areHotkeysInitialized()) { if (settings.areHotkeysInitialized()) {
Hotkeys.setDefault(settings); settings.setDefaultKeys();
} }
} }

View file

@ -1,11 +1,19 @@
package io.github.sspanak.tt9.ime.helpers; package io.github.sspanak.tt9.ime.helpers;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.KeyEvent; import android.view.KeyEvent;
import androidx.annotation.NonNull;
import java.util.HashMap; import java.util.HashMap;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.preferences.settings.SettingsStore;
import io.github.sspanak.tt9.util.Ternary; import io.github.sspanak.tt9.util.Ternary;
import io.github.sspanak.tt9.util.Text;
public class Key { public class Key {
private static final HashMap<Integer, Ternary> handledKeys = new HashMap<>(); private static final HashMap<Integer, Ternary> handledKeys = new HashMap<>();
@ -107,4 +115,57 @@ public class Key {
return -1; return -1;
} }
} }
@SuppressLint("GestureBackNavigation") // we are not handling anything here, the warning makes no sense
public static String codeToName(@NonNull Context context, int keyCode) {
return switch (keyCode) {
case KeyEvent.KEYCODE_UNKNOWN -> context.getString(R.string.key_none);
case KeyEvent.KEYCODE_POUND -> "#";
case KeyEvent.KEYCODE_STAR -> "";
case KeyEvent.KEYCODE_BACK -> context.getString(R.string.key_back);
case KeyEvent.KEYCODE_CALL -> context.getString(R.string.key_call);
case KeyEvent.KEYCODE_CHANNEL_DOWN -> context.getString(R.string.key_channel_down);
case KeyEvent.KEYCODE_CHANNEL_UP -> context.getString(R.string.key_channel_up);
case KeyEvent.KEYCODE_DPAD_UP -> context.getString(R.string.key_dpad_up);
case KeyEvent.KEYCODE_DPAD_DOWN -> context.getString(R.string.key_dpad_down);
case KeyEvent.KEYCODE_DPAD_LEFT -> context.getString(R.string.key_dpad_left);
case KeyEvent.KEYCODE_DPAD_RIGHT -> context.getString(R.string.key_dpad_right);
case KeyEvent.KEYCODE_MENU -> context.getString(R.string.key_menu);
case KeyEvent.KEYCODE_NUMPAD_ADD -> "Num +";
case KeyEvent.KEYCODE_NUMPAD_DIVIDE -> "Num /";
case KeyEvent.KEYCODE_NUMPAD_DOT -> "Num .";
case KeyEvent.KEYCODE_NUMPAD_MULTIPLY -> "Num *";
case KeyEvent.KEYCODE_NUMPAD_SUBTRACT -> "Num -";
case KeyEvent.KEYCODE_PROG_RED -> context.getString(R.string.key_red);
case KeyEvent.KEYCODE_PROG_GREEN -> context.getString(R.string.key_green);
case KeyEvent.KEYCODE_PROG_YELLOW -> context.getString(R.string.key_yellow);
case KeyEvent.KEYCODE_PROG_BLUE -> context.getString(R.string.key_blue);
case KeyEvent.KEYCODE_SOFT_LEFT -> context.getString(R.string.key_soft_left);
case KeyEvent.KEYCODE_SOFT_RIGHT -> context.getString(R.string.key_soft_right);
case KeyEvent.KEYCODE_VOLUME_MUTE -> context.getString(R.string.key_volume_mute);
case KeyEvent.KEYCODE_VOLUME_DOWN -> context.getString(R.string.key_volume_down);
case KeyEvent.KEYCODE_VOLUME_UP -> context.getString(R.string.key_volume_up);
default -> codeToSystemName(context, keyCode);
};
}
private static String codeToSystemName(@NonNull Context context, int keyCode) {
String name = KeyEvent.keyCodeToString(keyCode).replace("KEYCODE_", "");
if (new Text(name).isNumeric()) {
return context.getString(R.string.key_key) + " #" + name;
}
Language english = LanguageCollection.getByLanguageCode("en");
String[] parts = name.split("_");
StringBuilder formattedName = new StringBuilder();
for (int i = 0; i < parts.length; i++) {
formattedName.append(new Text(english, parts[i].toLowerCase()).capitalize());
if (i < parts.length - 1) {
formattedName.append(" ");
}
}
return formattedName.toString();
}
} }

View file

@ -19,7 +19,6 @@ import io.github.sspanak.tt9.db.DataStore;
import io.github.sspanak.tt9.db.words.LegacyDb; import io.github.sspanak.tt9.db.words.LegacyDb;
import io.github.sspanak.tt9.ime.helpers.InputModeValidator; import io.github.sspanak.tt9.ime.helpers.InputModeValidator;
import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.preferences.helpers.Hotkeys;
import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment; import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment;
import io.github.sspanak.tt9.preferences.screens.UsageStatsScreen; import io.github.sspanak.tt9.preferences.screens.UsageStatsScreen;
import io.github.sspanak.tt9.preferences.screens.appearance.AppearanceScreen; import io.github.sspanak.tt9.preferences.screens.appearance.AppearanceScreen;
@ -225,7 +224,7 @@ public class PreferencesActivity extends ActivityWithNavigation implements Prefe
private void validateFunctionKeys() { private void validateFunctionKeys() {
if (settings.areHotkeysInitialized()) { if (settings.areHotkeysInitialized()) {
Hotkeys.setDefault(settings); settings.setDefaultKeys();
} }
} }
} }

View file

@ -1,216 +0,0 @@
package io.github.sspanak.tt9.preferences.helpers;
import android.content.Context;
import android.content.res.Resources;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.ViewConfiguration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Set;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.preferences.screens.hotkeys.SectionKeymap;
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
public class Hotkeys {
private final Context context;
private final Resources resources;
private final String holdKeyTranslation;
private final LinkedHashMap<String, String> HARDWARE_KEYS = new LinkedHashMap<>();
public Hotkeys(Context context) {
this.context = context;
resources = context.getResources();
holdKeyTranslation = resources.getString(R.string.key_hold_key);
addNoKey();
generateList();
}
public String getHardwareKeyName(String key) {
return HARDWARE_KEYS.get(key);
}
public Set<String> getHardwareKeys() {
return HARDWARE_KEYS.keySet();
}
/**
* setDefault
* Applies the default hotkey scheme.
*
* When a standard "Backspace" hardware key is available, "Backspace" hotkey association is not necessary,
* so it will be left out blank, to allow the hardware key do its job.
* When the on-screen keyboard is on, "Back" is also not associated, because it will cause weird user
* experience. Instead the on-screen "Backspace" key can be used.
*
* Arrow keys for manipulating suggestions are also assigned only if available.
*/
public static void setDefault(SettingsStore settings) {
HashMap<String, Integer> defaultKeys = new HashMap<>();
defaultKeys.put(SectionKeymap.ITEM_ADD_WORD, KeyEvent.KEYCODE_UNKNOWN); // unassigned
defaultKeys.put(SectionKeymap.ITEM_BACKSPACE, KeyEvent.KEYCODE_BACK);
if (
KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_CLEAR)
|| KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DEL)
|| settings.isMainLayoutNumpad()
) {
defaultKeys.put(SectionKeymap.ITEM_BACKSPACE, 0);
}
defaultKeys.put(SectionKeymap.ITEM_COMMAND_PALETTE, -KeyEvent.KEYCODE_STAR); // negative means "hold"
defaultKeys.put(SectionKeymap.ITEM_EDIT_TEXT, KeyEvent.KEYCODE_UNKNOWN);
defaultKeys.put(
SectionKeymap.ITEM_FILTER_CLEAR,
KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_DOWN) ? KeyEvent.KEYCODE_DPAD_DOWN : 0
);
defaultKeys.put(
SectionKeymap.ITEM_FILTER_SUGGESTIONS,
KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_UP) ? KeyEvent.KEYCODE_DPAD_UP : 0
);
defaultKeys.put(
SectionKeymap.ITEM_PREVIOUS_SUGGESTION,
KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_LEFT) ? KeyEvent.KEYCODE_DPAD_LEFT : 0
);
defaultKeys.put(
SectionKeymap.ITEM_NEXT_SUGGESTION,
KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_RIGHT) ? KeyEvent.KEYCODE_DPAD_RIGHT : 0
);
defaultKeys.put(SectionKeymap.ITEM_NEXT_INPUT_MODE, KeyEvent.KEYCODE_POUND);
defaultKeys.put(SectionKeymap.ITEM_NEXT_LANGUAGE, -KeyEvent.KEYCODE_POUND); // negative means "hold"
defaultKeys.put(SectionKeymap.ITEM_SELECT_KEYBOARD, KeyEvent.KEYCODE_UNKNOWN);
defaultKeys.put(SectionKeymap.ITEM_SHIFT, KeyEvent.KEYCODE_STAR);
defaultKeys.put(SectionKeymap.ITEM_SPACE_KOREAN, KeyEvent.KEYCODE_STAR);
defaultKeys.put(SectionKeymap.ITEM_SHOW_SETTINGS, KeyEvent.KEYCODE_UNKNOWN);
defaultKeys.put(SectionKeymap.ITEM_VOICE_INPUT, KeyEvent.KEYCODE_UNKNOWN);
settings.setDefaultKeys(defaultKeys);
}
/**
* addIfDeviceHasKey
* Add the key only if Android says the device has such keypad button or a permanent touch key.
*/
private void addIfDeviceHasKey(int code, String name, boolean allowHold) {
if (
(code == KeyEvent.KEYCODE_MENU && ViewConfiguration.get(context).hasPermanentMenuKey())
|| KeyCharacterMap.deviceHasKey(code)
) {
add(code, name, allowHold);
}
}
/**
* addIfDeviceHasKey
* Same as addIfDeviceHasKey, but accepts a Resource String as a key name.
*
*/
@SuppressWarnings("SameParameterValue")
private void addIfDeviceHasKey(int code, int nameResource, boolean allowHold) {
addIfDeviceHasKey(code, resources.getString(nameResource), allowHold);
}
/**
* add
* These key will be added as a selectable option, regardless if it exists or or not.
* No validation will be performed.
*/
private void add(int code, String name, boolean allowHold) {
HARDWARE_KEYS.put(String.valueOf(code), name);
if (allowHold) {
HARDWARE_KEYS.put(String.valueOf(-code), name + " " + holdKeyTranslation);
}
}
/**
* add
* Same as add(), but accepts a Resource String as a key name.
*/
@SuppressWarnings("SameParameterValue")
private void add(int code, int nameResource, boolean allowHold) {
add(code, resources.getString(nameResource), allowHold);
}
/**
* addNoKey
* This is the "--" option. The key code matches no key on the keypad.
*/
private void addNoKey() {
add(0, R.string.key_none, false);
}
/**
* generateList
* Generates a list of all supported hotkeys for associating functions in the Settings.
*
* NOTE: Some TT9 functions do not support all keys. Here you just list all possible options.
* Actual validation and assigning happens in SectionKeymap.populate().
*/
private void generateList() {
add(KeyEvent.KEYCODE_CALL, R.string.key_call, true);
addIfDeviceHasKey(KeyEvent.KEYCODE_BACK, R.string.key_back, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_F1, "F1", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_F2, "F2", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_F3, "F3", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_F4, "F4", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_F5, "F5", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_F6, "F6", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_F7, "F7", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_F8, "F8", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_F9, "F9", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_F10, "F10", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_F11, "F11", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_F12, "F12", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_MENU, R.string.key_menu, true);
addIfDeviceHasKey(KeyEvent.KEYCODE_SOFT_LEFT, R.string.key_soft_left, true);
addIfDeviceHasKey(KeyEvent.KEYCODE_SOFT_RIGHT, R.string.key_soft_right, true);
add(KeyEvent.KEYCODE_POUND, "#", true);
add(KeyEvent.KEYCODE_STAR, "", true);
addIfDeviceHasKey(KeyEvent.KEYCODE_DPAD_UP, R.string.key_dpad_up, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_DPAD_DOWN, R.string.key_dpad_down, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_DPAD_LEFT, R.string.key_dpad_left, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_DPAD_RIGHT, R.string.key_dpad_right, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_ADD, "Num +", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_SUBTRACT, "Num -", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_MULTIPLY, "Num *", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_DIVIDE, "Num /", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_DOT, "Num .", false);
addIfDeviceHasKey(KeyEvent.KEYCODE_CHANNEL_DOWN, R.string.key_channel_down, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_CHANNEL_UP, R.string.key_channel_up, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_PROG_RED, R.string.key_red, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_PROG_GREEN, R.string.key_green, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_PROG_YELLOW, R.string.key_yellow, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_PROG_BLUE, R.string.key_blue, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_VOLUME_MUTE, R.string.key_volume_mute, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_VOLUME_DOWN, R.string.key_volume_down, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_VOLUME_UP, R.string.key_volume_up, false);
}
}

View file

@ -55,6 +55,8 @@ public class PreferenceDeletableWord extends ScreenPreference {
context.getString(R.string.delete_words_deleted_confirm_deletion_question, word), context.getString(R.string.delete_words_deleted_confirm_deletion_question, word),
context.getString(R.string.delete_words_delete), context.getString(R.string.delete_words_delete),
this::onDeletionConfirmed, this::onDeletionConfirmed,
true,
null,
null null
); );
} }

View file

@ -1,15 +1,19 @@
package io.github.sspanak.tt9.preferences.screens.hotkeys; package io.github.sspanak.tt9.preferences.screens.hotkeys;
import androidx.preference.DropDownPreference; import androidx.annotation.NonNull;
import java.util.Arrays; import java.util.HashMap;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.preferences.PreferencesActivity; import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment; import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment;
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
public class HotkeysScreen extends BaseScreenFragment { public class HotkeysScreen extends BaseScreenFragment {
final public static String NAME = "Hotkeys"; public static final String NAME = "Hotkeys";
@NonNull static HashMap<String, PreferenceHotkey> hotkeys = new HashMap<>();
public HotkeysScreen() { init(); } public HotkeysScreen() { init(); }
public HotkeysScreen(PreferencesActivity activity) { init(activity); } public HotkeysScreen(PreferencesActivity activity) { init(activity); }
@ -19,29 +23,22 @@ public class HotkeysScreen extends BaseScreenFragment {
@Override @Override
public void onCreate() { public void onCreate() {
DropDownPreference[] dropDowns = { getHotkeys();
findPreference(SectionKeymap.ITEM_ADD_WORD), (new ItemResetKeys(findPreference(ItemResetKeys.NAME), activity, hotkeys.values())).enableClickHandler();
findPreference(SectionKeymap.ITEM_BACKSPACE),
findPreference(SectionKeymap.ITEM_COMMAND_PALETTE),
findPreference(SectionKeymap.ITEM_EDIT_TEXT),
findPreference(SectionKeymap.ITEM_FILTER_CLEAR),
findPreference(SectionKeymap.ITEM_FILTER_SUGGESTIONS),
findPreference(SectionKeymap.ITEM_PREVIOUS_SUGGESTION),
findPreference(SectionKeymap.ITEM_NEXT_SUGGESTION),
findPreference(SectionKeymap.ITEM_NEXT_INPUT_MODE),
findPreference(SectionKeymap.ITEM_NEXT_LANGUAGE),
findPreference(SectionKeymap.ITEM_SELECT_KEYBOARD),
findPreference(SectionKeymap.ITEM_SHIFT),
findPreference(SectionKeymap.ITEM_SPACE_KOREAN),
findPreference(SectionKeymap.ITEM_SHOW_SETTINGS),
findPreference(SectionKeymap.ITEM_VOICE_INPUT),
};
SectionKeymap section = new SectionKeymap(Arrays.asList(dropDowns), activity);
section.populate().activate();
(new ItemResetKeys(findPreference(ItemResetKeys.NAME), activity, section))
.enableClickHandler();
resetFontSize(false); resetFontSize(false);
} }
@Override
public int getPreferenceCount() {
return -1; // prevent scrolling and item selection using the number keys on this screen
}
private void getHotkeys() {
for (String function : SettingsStore.FUNCTIONS) {
PreferenceHotkey hotkey = findPreference(function);
if (hotkey != null) {
hotkeys.put(function, hotkey);
}
}
}
} }

View file

@ -1,10 +1,11 @@
package io.github.sspanak.tt9.preferences.screens.hotkeys; package io.github.sspanak.tt9.preferences.screens.hotkeys;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference; import androidx.preference.Preference;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.preferences.PreferencesActivity; import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.helpers.Hotkeys;
import io.github.sspanak.tt9.preferences.items.ItemClickable; import io.github.sspanak.tt9.preferences.items.ItemClickable;
import io.github.sspanak.tt9.ui.UI; import io.github.sspanak.tt9.ui.UI;
@ -13,19 +14,21 @@ class ItemResetKeys extends ItemClickable {
public static final String NAME = "reset_keys"; public static final String NAME = "reset_keys";
private final PreferencesActivity activity; private final PreferencesActivity activity;
private final SectionKeymap dropdowns; private final Iterable<PreferenceHotkey> hotkeys;
ItemResetKeys(Preference item, PreferencesActivity activity, SectionKeymap dropdowns) { ItemResetKeys(@Nullable Preference item, @NonNull PreferencesActivity activity, @NonNull Iterable<PreferenceHotkey> hotkeys) {
super(item); super(item);
this.activity = activity; this.activity = activity;
this.dropdowns = dropdowns; this.hotkeys = hotkeys;
} }
@Override @Override
protected boolean onClick(Preference p) { protected boolean onClick(Preference p) {
Hotkeys.setDefault(activity.getSettings()); activity.getSettings().setDefaultKeys();
dropdowns.reloadSettings(); for (PreferenceHotkey hotkey : hotkeys) {
hotkey.populate();
}
UI.toast(activity, R.string.function_reset_keys_done); UI.toast(activity, R.string.function_reset_keys_done);
return true; return true;
} }

View file

@ -0,0 +1,22 @@
package io.github.sspanak.tt9.preferences.screens.hotkeys;
import android.content.Context;
import android.content.DialogInterface;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class PreferenceBackspaceHotkey extends PreferenceHotkey {
public PreferenceBackspaceHotkey(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); }
public PreferenceBackspaceHotkey(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }
public PreferenceBackspaceHotkey(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); }
public PreferenceBackspaceHotkey(@NonNull Context context) { super(context); }
@Override
protected boolean onAssign(DialogInterface dialog, int keyCode) {
// backspace works both when pressed short and long,
// so separate "hold" and "not hold" options for it make no sense
return super.onAssign(dialog, Math.abs(keyCode));
}
}

View file

@ -0,0 +1,203 @@
package io.github.sspanak.tt9.preferences.screens.hotkeys;
import android.content.Context;
import android.content.DialogInterface;
import android.util.AttributeSet;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.ime.helpers.Key;
import io.github.sspanak.tt9.preferences.custom.ScreenPreference;
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
import io.github.sspanak.tt9.ui.UI;
public class PreferenceHotkey extends ScreenPreference implements DialogInterface.OnKeyListener{
private static final int CANCEL_KEY = 0;
private static final int UNASSIGN_KEY = 2;
private static SettingsStore settings;
private int lastKeyDownCode = KeyEvent.KEYCODE_UNKNOWN;
private int lastLongPressCode = KeyEvent.KEYCODE_UNKNOWN;
public PreferenceHotkey(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context); }
public PreferenceHotkey(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); }
public PreferenceHotkey(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context); }
public PreferenceHotkey(@NonNull Context context) { super(context); init(context); }
private void init(Context context) {
if (settings == null) {
settings = new SettingsStore(context);
}
}
@Override
public void onAttached() {
super.onAttached();
populate();
}
@Override protected int getLargeLayout() {
return DeviceInfo.AT_LEAST_ANDROID_12 ? getDefaultLayout() : R.layout.pref_default_large;
}
public void populate() {
populate(settings.getFunctionKey(getKey()));
}
private void populate(int keyCode) {
String holdSuffix = "";
if (keyCode < 0) {
holdSuffix = " " + getContext().getString(R.string.key_hold_key);
keyCode = -keyCode;
}
setSummary(Key.codeToName(getContext(), keyCode) + holdSuffix);
}
@Override
protected void onClick() {
super.onClick();
UI.confirm(
getContext(),
getKey(),
getContext().getString(R.string.function_assign_instructions, getTitle()),
null,
null,
false,
null,
this
);
}
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
onKeyDown(dialog, keyCode);
}
if (event.getAction() == KeyEvent.ACTION_UP) {
onKeyUp(dialog, keyCode);
}
// prevent passing the key event to the activity
return false;
}
private void onKeyDown(DialogInterface dialog, int keyCode) {
if (keyCode == lastKeyDownCode && keyCode != lastLongPressCode && onAssign(dialog, -keyCode)) {
lastLongPressCode = keyCode;
dialog.dismiss();
}
lastKeyDownCode = keyCode;
}
private void onKeyUp(DialogInterface dialog, int keyCode) {
lastKeyDownCode = KeyEvent.KEYCODE_UNKNOWN;
lastLongPressCode = KeyEvent.KEYCODE_UNKNOWN;
if (onIgnore(keyCode)) {
return;
}
if (onCancel(keyCode) || onUnassign(keyCode) || onAssign(dialog, keyCode)) {
dialog.dismiss();
}
}
protected boolean onAssign(DialogInterface dialog, int keyCode) {
if (Key.isNumber(keyCode)) {
return false;
}
if (onReassign(dialog, keyCode)) {
return true;
}
settings.setFunctionKey(getKey(), keyCode);
populate(keyCode);
return true;
}
private boolean onReassign(DialogInterface dialog, int keyCode) {
String otherFunction = settings.getFunction(keyCode);
if (otherFunction == null || otherFunction.equals(getKey())) {
return false;
}
// "Shift" and "Korean Space" can be the same key. It is properly handled in HotkeyHandler.
if (
(getKey().equals(SettingsStore.FUNC_SPACE_KOREAN) && otherFunction.equals(SettingsStore.FUNC_SHIFT))
|| (getKey().equals(SettingsStore.FUNC_SHIFT) && otherFunction.equals(SettingsStore.FUNC_SPACE_KOREAN))
) {
return false;
}
dialog.dismiss();
PreferenceHotkey otherHotkey = HotkeysScreen.hotkeys.get(otherFunction);
CharSequence prettyOtherFunction = otherHotkey != null ? otherHotkey.getTitle() : otherFunction;
String question = getContext().getString(
R.string.function_already_assigned,
Key.codeToName(getContext(), keyCode),
prettyOtherFunction,
getTitle()
);
UI.confirm(
getContext(),
getKey(),
question,
getContext().getString(R.string.function_reassign),
() -> {
settings.setFunctionKey(otherFunction, KeyEvent.KEYCODE_UNKNOWN);
onAssign(dialog, keyCode);
},
true,
null,
null
);
return true;
}
private boolean onUnassign(int keyCode) {
if (Key.codeToNumber(settings, keyCode) != UNASSIGN_KEY) {
return false;
}
settings.setFunctionKey(getKey(), KeyEvent.KEYCODE_UNKNOWN);
populate(KeyEvent.KEYCODE_UNKNOWN);
return true;
}
private boolean onCancel(int keyCode) {
return
(DeviceInfo.noKeyboard(getContext()) && Key.isBack(keyCode))
|| Key.codeToNumber(settings, keyCode) == CANCEL_KEY;
}
private boolean onIgnore(int keyCode) {
return Key.isOK(keyCode);
}
}

View file

@ -0,0 +1,36 @@
package io.github.sspanak.tt9.preferences.screens.hotkeys;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.github.sspanak.tt9.ime.voice.VoiceInputOps;
public class PreferenceVoiceInputHotkey extends PreferenceHotkey {
public PreferenceVoiceInputHotkey(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public PreferenceVoiceInputHotkey(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public PreferenceVoiceInputHotkey(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public PreferenceVoiceInputHotkey(@NonNull Context context) {
super(context);
}
@Override
public void populate() {
boolean isAvailable = new VoiceInputOps(getContext(), null, null, null).isAvailable();
setVisible(isAvailable);
if (isAvailable) {
super.populate();
}
}
}

View file

@ -1,182 +0,0 @@
package io.github.sspanak.tt9.preferences.screens.hotkeys;
import androidx.preference.DropDownPreference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import io.github.sspanak.tt9.ime.voice.VoiceInputOps;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.helpers.Hotkeys;
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
import io.github.sspanak.tt9.util.Logger;
public class SectionKeymap {
public static final String ITEM_ADD_WORD = "key_add_word";
public static final String ITEM_BACKSPACE = "key_backspace";
public static final String ITEM_COMMAND_PALETTE = "key_command_palette";
public static final String ITEM_EDIT_TEXT = "key_edit_text";
public static final String ITEM_FILTER_CLEAR = "key_filter_clear";
public static final String ITEM_FILTER_SUGGESTIONS = "key_filter_suggestions";
public static final String ITEM_PREVIOUS_SUGGESTION = "key_previous_suggestion";
public static final String ITEM_NEXT_SUGGESTION = "key_next_suggestion";
public static final String ITEM_NEXT_INPUT_MODE = "key_next_input_mode";
public static final String ITEM_NEXT_LANGUAGE = "key_next_language";
public static final String ITEM_SELECT_KEYBOARD = "key_select_keyboard";
public static final String ITEM_SHIFT = "key_shift";
public static final String ITEM_SPACE_KOREAN = "key_space_korean";
public static final String ITEM_SHOW_SETTINGS = "key_show_settings";
public static final String ITEM_VOICE_INPUT = "key_voice_input";
private final Hotkeys hotkeys;
private final Collection<DropDownPreference> items;
private final SettingsStore settings;
private final boolean isVoiceInputAvailable;
public SectionKeymap(Collection<DropDownPreference> dropDowns, PreferencesActivity activity) {
items = dropDowns;
hotkeys = new Hotkeys(activity);
this.settings = activity.getSettings();
isVoiceInputAvailable = new VoiceInputOps(activity, null, null, null).isAvailable();
}
public void reloadSettings() {
for (DropDownPreference dropDown : items) {
int keypadKey = settings.getFunctionKey(dropDown.getKey());
dropDown.setValue(String.valueOf(keypadKey));
}
populate();
}
public SectionKeymap populate() {
populateOtherItems(null);
return this;
}
public void activate() {
for (DropDownPreference item : items) {
onItemClick(item);
}
}
private void populateOtherItems(DropDownPreference itemToSkip) {
for (DropDownPreference item : items) {
if (itemToSkip != null && item != null && Objects.equals(itemToSkip.getKey(), item.getKey())) {
continue;
}
populateItem(item);
previewCurrentKey(item);
}
}
private void populateItem(DropDownPreference dropDown) {
if (dropDown == null) {
Logger.w("SectionKeymap.populateItem", "Cannot populate a NULL item. Ignoring.");
return;
}
ArrayList<String> keys = new ArrayList<>();
for (String key : hotkeys.getHardwareKeys()) {
if (
validateKey(dropDown, String.valueOf(key))
// backspace works both when pressed short and long,
// so separate "hold" and "not hold" options for it make no sense
&& !(dropDown.getKey().equals(ITEM_BACKSPACE) && Integer.parseInt(key) < 0)
) {
keys.add(String.valueOf(key));
}
}
ArrayList<String> values = new ArrayList<>();
for (String key : keys) {
values.add(hotkeys.getHardwareKeyName(key));
}
dropDown.setEntries(values.toArray(new CharSequence[0]));
dropDown.setEntryValues(keys.toArray(new CharSequence[0]));
}
private void onItemClick(DropDownPreference item) {
if (item == null) {
Logger.w("SectionKeymap.populateItem", "Cannot set a click listener a NULL item. Ignoring.");
return;
}
item.setOnPreferenceChangeListener((preference, newKey) -> {
if (!validateKey((DropDownPreference) preference, newKey.toString())) {
return false;
}
try {
((DropDownPreference) preference).setValue(newKey.toString());
previewCurrentKey((DropDownPreference) preference, newKey.toString());
populateOtherItems((DropDownPreference) preference);
return true;
} catch (Exception e) {
Logger.e("SectionKeymap.onItemClick", "Failed setting new hotkey. " + e.getMessage());
return false;
}
});
}
private void previewCurrentKey(DropDownPreference dropDown) {
previewCurrentKey(dropDown, dropDown != null ? dropDown.getValue() : null);
}
private void previewCurrentKey(DropDownPreference dropDown, String key) {
if (dropDown == null) {
return;
}
dropDown.setSummary(hotkeys.getHardwareKeyName(key));
if (dropDown.getKey().equals(ITEM_VOICE_INPUT)) {
dropDown.setVisible(isVoiceInputAvailable);
}
}
private boolean validateKey(DropDownPreference dropDown, String key) {
if (dropDown == null || key == null) {
return false;
}
if (key.equals("0")) {
return true;
}
for (DropDownPreference item : items) {
if (item == null) {
continue;
}
// "Shift" and "Korean Space" can be the same key. It is properly handled in HotkeyHandler.
if (
(
(dropDown.getKey().equals(ITEM_SHIFT) && item.getKey().equals(ITEM_SPACE_KOREAN))
|| (dropDown.getKey().equals(ITEM_SPACE_KOREAN) && item.getKey().equals(ITEM_SHIFT))
)
&& key.equals(item.getValue())
) {
continue;
}
if (!dropDown.getKey().equals(item.getKey()) && key.equals(item.getValue())) {
Logger.i("SectionKeymap.validateKey", "Key: '" + key + "' is already in use for function: " + item.getKey());
return false;
}
}
return true;
}
}

View file

@ -6,6 +6,8 @@ import android.content.SharedPreferences;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
class BaseSettings { class BaseSettings {
protected final String LOG_TAG = SettingsInput.class.getSimpleName();
protected final Context context; protected final Context context;
protected final SharedPreferences prefs; protected final SharedPreferences prefs;
protected final SharedPreferences.Editor prefsEditor; protected final SharedPreferences.Editor prefsEditor;

View file

@ -1,26 +1,113 @@
package io.github.sspanak.tt9.preferences.settings; package io.github.sspanak.tt9.preferences.settings;
import android.content.Context; import android.content.Context;
import android.view.KeyCharacterMap;
import android.view.KeyEvent; import android.view.KeyEvent;
import java.util.HashMap; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.preferences.screens.hotkeys.SectionKeymap; public class SettingsHotkeys extends SettingsUI {
class SettingsHotkeys extends SettingsHacks {
private static final String HOTKEY_VERSION = "hotkeys_v5"; private static final String HOTKEY_VERSION = "hotkeys_v5";
public static final String FUNC_ADD_WORD = "key_add_word";
public static final String FUNC_BACKSPACE = "key_backspace";
public static final String FUNC_COMMAND_PALETTE = "key_command_palette";
public static final String FUNC_EDIT_TEXT = "key_edit_text";
public static final String FUNC_FILTER_CLEAR = "key_filter_clear";
public static final String FUNC_FILTER_SUGGESTIONS = "key_filter_suggestions";
public static final String FUNC_PREVIOUS_SUGGESTION = "key_previous_suggestion";
public static final String FUNC_NEXT_SUGGESTION = "key_next_suggestion";
public static final String FUNC_NEXT_INPUT_MODE = "key_next_input_mode";
public static final String FUNC_NEXT_LANGUAGE = "key_next_language";
public static final String FUNC_SELECT_KEYBOARD = "key_select_keyboard";
public static final String FUNC_SHIFT = "key_shift";
public static final String FUNC_SPACE_KOREAN = "key_space_korean";
public static final String FUNC_SHOW_SETTINGS = "key_show_settings";
public static final String FUNC_VOICE_INPUT = "key_voice_input";
public static final String[] FUNCTIONS = {
FUNC_ADD_WORD,
FUNC_BACKSPACE,
FUNC_COMMAND_PALETTE,
FUNC_EDIT_TEXT,
FUNC_FILTER_CLEAR,
FUNC_FILTER_SUGGESTIONS,
FUNC_PREVIOUS_SUGGESTION,
FUNC_NEXT_SUGGESTION,
FUNC_NEXT_INPUT_MODE,
FUNC_NEXT_LANGUAGE,
FUNC_SELECT_KEYBOARD,
FUNC_SHIFT,
FUNC_SPACE_KOREAN,
FUNC_SHOW_SETTINGS,
FUNC_VOICE_INPUT,
};
SettingsHotkeys(Context context) { super(context); } SettingsHotkeys(Context context) { super(context); }
public boolean areHotkeysInitialized() { public boolean areHotkeysInitialized() {
return !prefs.getBoolean(HOTKEY_VERSION, false); return !prefs.getBoolean(HOTKEY_VERSION, false);
} }
public void setDefaultKeys(HashMap<String, Integer> defaultKeys) {
for (String key : defaultKeys.keySet()) { /**
prefsEditor.putString(key, String.valueOf(defaultKeys.get(key))); * Applies the default hotkey scheme.
* When a standard "Backspace" hardware key is available, "Backspace" hotkey association is not necessary,
* so it will be left out blank, to allow the hardware key do its job.
* When the on-screen keyboard is on, "Back" is also not associated, because it will cause weird user
* experience. Instead the on-screen "Backspace" key can be used.
* Arrow keys for manipulating suggestions are also assigned only if available.
*/
public void setDefaultKeys() {
// no default keys
String[] unassigned = { FUNC_ADD_WORD, FUNC_EDIT_TEXT, FUNC_SELECT_KEYBOARD, FUNC_SHOW_SETTINGS, FUNC_VOICE_INPUT };
for (String key : unassigned) {
prefsEditor.putString(key, String.valueOf(KeyEvent.KEYCODE_UNKNOWN));
} }
// backspace
if (
KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_CLEAR)
|| KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DEL)
|| isMainLayoutNumpad()
) {
prefsEditor.putString(FUNC_BACKSPACE, String.valueOf(KeyEvent.KEYCODE_UNKNOWN));
} else {
prefsEditor.putString(FUNC_BACKSPACE, String.valueOf(KeyEvent.KEYCODE_BACK));
}
// filter clear
prefsEditor.putString(
FUNC_FILTER_CLEAR,
String.valueOf(KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_DOWN) ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_UNKNOWN)
);
// filter
prefsEditor.putString(
FUNC_FILTER_SUGGESTIONS,
String.valueOf(KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_UP) ? KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_UNKNOWN)
);
// previous suggestion
prefsEditor.putString(
FUNC_PREVIOUS_SUGGESTION,
String.valueOf(KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_LEFT) ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_UNKNOWN)
);
// next suggestion
prefsEditor.putString(
FUNC_NEXT_SUGGESTION,
String.valueOf(KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_RIGHT) ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_UNKNOWN)
);
prefsEditor.putString(FUNC_COMMAND_PALETTE, String.valueOf(-KeyEvent.KEYCODE_STAR)); // negative means "hold"
prefsEditor.putString(FUNC_NEXT_INPUT_MODE, String.valueOf(KeyEvent.KEYCODE_POUND));
prefsEditor.putString(FUNC_NEXT_LANGUAGE, String.valueOf(-KeyEvent.KEYCODE_POUND)); // negative means "hold"
prefsEditor.putString(FUNC_SHIFT, String.valueOf(KeyEvent.KEYCODE_STAR));
prefsEditor.putString(FUNC_SPACE_KOREAN, String.valueOf(KeyEvent.KEYCODE_STAR));
prefsEditor.putBoolean(HOTKEY_VERSION, true).apply(); prefsEditor.putBoolean(HOTKEY_VERSION, true).apply();
} }
@ -30,49 +117,84 @@ class SettingsHotkeys extends SettingsHacks {
} }
public void setFunctionKey(String functionName, int keyCode) {
if (isValidFunction(functionName)) {
Logger.d(LOG_TAG, "Setting hotkey for function: '" + functionName + "' to " + keyCode);
prefsEditor.putString(functionName, String.valueOf(keyCode)).apply();
} else {
Logger.w(LOG_TAG,"Not setting a hotkey for invalid function: '" + functionName + "'");
}
}
public int getKeyAddWord() { public int getKeyAddWord() {
return getFunctionKey(SectionKeymap.ITEM_ADD_WORD); return getFunctionKey(FUNC_ADD_WORD);
} }
public int getKeyBackspace() { public int getKeyBackspace() {
return getFunctionKey(SectionKeymap.ITEM_BACKSPACE); return getFunctionKey(FUNC_BACKSPACE);
} }
public int getKeyCommandPalette() { public int getKeyCommandPalette() {
return getFunctionKey(SectionKeymap.ITEM_COMMAND_PALETTE); return getFunctionKey(FUNC_COMMAND_PALETTE);
} }
public int getKeyEditText() { public int getKeyEditText() {
return getFunctionKey(SectionKeymap.ITEM_EDIT_TEXT); return getFunctionKey(FUNC_EDIT_TEXT);
} }
public int getKeyFilterClear() { public int getKeyFilterClear() {
return getFunctionKey(SectionKeymap.ITEM_FILTER_CLEAR); return getFunctionKey(FUNC_FILTER_CLEAR);
} }
public int getKeyFilterSuggestions() { public int getKeyFilterSuggestions() {
return getFunctionKey(SectionKeymap.ITEM_FILTER_SUGGESTIONS); return getFunctionKey(FUNC_FILTER_SUGGESTIONS);
} }
public int getKeyPreviousSuggestion() { public int getKeyPreviousSuggestion() {
return getFunctionKey(SectionKeymap.ITEM_PREVIOUS_SUGGESTION); return getFunctionKey(FUNC_PREVIOUS_SUGGESTION);
} }
public int getKeyNextSuggestion() { public int getKeyNextSuggestion() {
return getFunctionKey(SectionKeymap.ITEM_NEXT_SUGGESTION); return getFunctionKey(FUNC_NEXT_SUGGESTION);
} }
public int getKeyNextInputMode() { public int getKeyNextInputMode() {
return getFunctionKey(SectionKeymap.ITEM_NEXT_INPUT_MODE); return getFunctionKey(FUNC_NEXT_INPUT_MODE);
} }
public int getKeyNextLanguage() { public int getKeyNextLanguage() {
return getFunctionKey(SectionKeymap.ITEM_NEXT_LANGUAGE); return getFunctionKey(FUNC_NEXT_LANGUAGE);
} }
public int getKeySelectKeyboard() { public int getKeySelectKeyboard() {
return getFunctionKey(SectionKeymap.ITEM_SELECT_KEYBOARD); return getFunctionKey(FUNC_SELECT_KEYBOARD);
} }
public int getKeyShift() { public int getKeyShift() {
return getFunctionKey(SectionKeymap.ITEM_SHIFT); return getFunctionKey(FUNC_SHIFT);
} }
public int getKeySpaceKorean() { public int getKeySpaceKorean() {
return getFunctionKey(SectionKeymap.ITEM_SPACE_KOREAN); return getFunctionKey(FUNC_SPACE_KOREAN);
} }
public int getKeyShowSettings() { public int getKeyShowSettings() {
return getFunctionKey(SectionKeymap.ITEM_SHOW_SETTINGS); return getFunctionKey(FUNC_SHOW_SETTINGS);
} }
public int getKeyVoiceInput() { public int getKeyVoiceInput() {
return getFunctionKey(SectionKeymap.ITEM_VOICE_INPUT); return getFunctionKey(FUNC_VOICE_INPUT);
}
public String getFunction(int keyCode) {
if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
return null;
}
for (String key : FUNCTIONS) {
if (keyCode == getFunctionKey(key)) {
return key;
}
}
return null;
}
private boolean isValidFunction(String functionName) {
for (String validName : FUNCTIONS) {
if (validName.equals(functionName)) {
return true;
}
}
return false;
} }
} }

View file

@ -10,9 +10,7 @@ import java.util.Set;
import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.util.Logger; import io.github.sspanak.tt9.util.Logger;
class SettingsInput extends SettingsHotkeys { class SettingsInput extends SettingsHacks {
private final String LOG_TAG = SettingsInput.class.getSimpleName();
SettingsInput(Context context) { super(context); } SettingsInput(Context context) { super(context); }

View file

@ -3,7 +3,7 @@ package io.github.sspanak.tt9.preferences.settings;
import android.content.Context; import android.content.Context;
public class SettingsStore extends SettingsUI { public class SettingsStore extends SettingsHotkeys {
public SettingsStore(Context context) { super(context); } public SettingsStore(Context context) { super(context); }
/************* internal settings *************/ /************* internal settings *************/

View file

@ -1,8 +1,10 @@
package io.github.sspanak.tt9.ui; package io.github.sspanak.tt9.ui;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.inputmethodservice.InputMethodService; import android.inputmethodservice.InputMethodService;
import android.os.Looper; import android.os.Looper;
@ -55,24 +57,28 @@ public class UI {
} }
public static void confirm(Context context, String title, String message, String OKLabel, Runnable onOk, Runnable onCancel) { public static void confirm(@NonNull Context context, String title, String message, String OKLabel, Runnable onOk, boolean cancelLabel, Runnable onCancel, DialogInterface.OnKeyListener onKey) {
Dialog dialogue;
if (DeviceInfo.AT_LEAST_ANDROID_12) { if (DeviceInfo.AT_LEAST_ANDROID_12) {
new MaterialAlertDialogBuilder(context) dialogue = new MaterialAlertDialogBuilder(context)
.setTitle(title)
.setMessage(message) .setMessage(message)
.setPositiveButton(OKLabel, (dialog, whichButton) -> { if (onOk != null) onOk.run(); }) .setPositiveButton(OKLabel, (dialog, whichButton) -> { if (onOk != null) onOk.run(); })
.setNegativeButton(android.R.string.cancel, (dialog, whichButton) -> { if (onCancel != null) onCancel.run(); }) .setNegativeButton(cancelLabel ? context.getString(android.R.string.cancel) : null, (dialog, whichButton) -> { if (onCancel != null) onCancel.run(); })
.setCancelable(false)
.show(); .show();
} else { } else {
new AlertDialog.Builder(context) dialogue = new AlertDialog.Builder(context)
.setTitle(title)
.setMessage(message) .setMessage(message)
.setPositiveButton(OKLabel, (dialog, whichButton) -> { if (onOk != null) onOk.run(); }) .setPositiveButton(OKLabel, (dialog, whichButton) -> { if (onOk != null) onOk.run(); })
.setNegativeButton(android.R.string.cancel, (dialog, whichButton) -> { if (onCancel != null) onCancel.run(); }) .setNegativeButton(cancelLabel ? context.getString(android.R.string.cancel) : null, (dialog, whichButton) -> { if (onCancel != null) onCancel.run(); })
.setCancelable(false)
.show(); .show();
} }
dialogue.setTitle(title);
dialogue.setCancelable(false);
if (onKey != null) {
dialogue.setOnKeyListener(onKey);
}
} }

View file

@ -30,7 +30,7 @@ abstract public class PopupDialog {
} }
protected void render(Runnable OKAction) { protected void render(Runnable OKAction) {
UI.confirm(context, title, message, OKLabel, OKAction, () -> activityFinisher.accept("")); UI.confirm(context, title, message, OKLabel, OKAction, true, () -> activityFinisher.accept(""), null);
} }
abstract void render(); abstract void render();

View file

@ -152,6 +152,10 @@
<string name="donate_hold_to_open">Press and hold to open in the browser.</string> <string name="donate_hold_to_open">Press and hold to open in the browser.</string>
<string name="donate_url" translatable="false">https://www.buymeacoffee.com/sspanak</string> <string name="donate_url" translatable="false">https://www.buymeacoffee.com/sspanak</string>
<string name="function_already_assigned">Key \"%1$s\" is already assigned to function \"%2$s\". Do you want to assign it to \"%3$s\"?</string>
<string name="function_assign_instructions">* Press or hold a key to assign it to the \"%1$s\" function.\n\n* Press 0 to cancel.\n\n* Press 2 to disable the function.</string>
<string name="function_reassign">Reassign</string>
<string name="function_add_word">Add Word</string> <string name="function_add_word">Add Word</string>
<string name="function_add_word_not_available">Adding words is not possible in this language.</string> <string name="function_add_word_not_available">Adding words is not possible in this language.</string>
<string name="function_backspace">Backspace</string> <string name="function_backspace">Backspace</string>
@ -195,6 +199,7 @@
<string name="setup_spell_checker_on">System spell checker may interfere with adding words. Click here to disable.</string> <string name="setup_spell_checker_on">System spell checker may interfere with adding words. Click here to disable.</string>
<string name="setup_spell_checker_off">Disabled</string> <string name="setup_spell_checker_off">Disabled</string>
<string name="key_key">Key</string>
<string name="key_hold_key">(hold)</string> <string name="key_hold_key">(hold)</string>
<string name="key_none" translatable="false">--</string> <string name="key_none" translatable="false">--</string>
<string name="key_back">Back</string> <string name="key_back">Back</string>

View file

@ -2,63 +2,63 @@
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto" <PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
app:orderingFromXml="true"> app:orderingFromXml="true">
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
app:key="key_add_word" app:key="key_add_word"
app:title="@string/function_add_word" /> app:title="@string/function_add_word" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceBackspaceHotkey
app:key="key_backspace" app:key="key_backspace"
app:title="@string/function_backspace" /> app:title="@string/function_backspace" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
app:key="key_command_palette" app:key="key_command_palette"
app:title="@string/function_show_command_palette" /> app:title="@string/function_show_command_palette" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
app:key="key_filter_clear" app:key="key_filter_clear"
app:title="@string/function_filter_clear" /> app:title="@string/function_filter_clear" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
app:key="key_edit_text" app:key="key_edit_text"
app:title="@string/function_edit_text" /> app:title="@string/function_edit_text" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
app:key="key_filter_suggestions" app:key="key_filter_suggestions"
app:title="@string/function_filter_suggestions" /> app:title="@string/function_filter_suggestions" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
app:key="key_previous_suggestion" app:key="key_previous_suggestion"
app:title="@string/function_previous_suggestion" /> app:title="@string/function_previous_suggestion" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
app:key="key_next_suggestion" app:key="key_next_suggestion"
app:title="@string/function_next_suggestion" /> app:title="@string/function_next_suggestion" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
app:key="key_next_language" app:key="key_next_language"
app:title="@string/function_next_language" /> app:title="@string/function_next_language" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
app:key="key_next_input_mode" app:key="key_next_input_mode"
app:title="@string/function_next_mode" /> app:title="@string/function_next_mode" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
app:key="key_select_keyboard" app:key="key_select_keyboard"
app:title="@string/function_select_keyboard" /> app:title="@string/function_select_keyboard" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
app:key="key_shift" app:key="key_shift"
app:title="@string/virtual_key_shift" /> app:title="@string/virtual_key_shift" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
app:key="key_space_korean" app:key="key_space_korean"
app:title="@string/virtual_key_space_korean" /> app:title="@string/virtual_key_space_korean" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
app:key="key_show_settings" app:key="key_show_settings"
app:title="@string/function_show_settings" /> app:title="@string/function_show_settings" />
<DropDownPreference <io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceVoiceInputHotkey
app:key="key_voice_input" app:key="key_voice_input"
app:title="@string/function_voice_input" /> app:title="@string/function_voice_input" />