Keypad navigation in the Settings (#499)
This commit is contained in:
parent
95e0eb4130
commit
c9e5707803
3 changed files with 176 additions and 17 deletions
|
|
@ -6,8 +6,8 @@ import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
@ -28,17 +28,14 @@ import io.github.sspanak.tt9.preferences.screens.hotkeys.HotkeysScreen;
|
||||||
import io.github.sspanak.tt9.preferences.screens.keypad.KeyPadScreen;
|
import io.github.sspanak.tt9.preferences.screens.keypad.KeyPadScreen;
|
||||||
import io.github.sspanak.tt9.preferences.screens.languages.LanguagesScreen;
|
import io.github.sspanak.tt9.preferences.screens.languages.LanguagesScreen;
|
||||||
import io.github.sspanak.tt9.preferences.screens.setup.SetupScreen;
|
import io.github.sspanak.tt9.preferences.screens.setup.SetupScreen;
|
||||||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
import io.github.sspanak.tt9.ui.ActivityWithNavigation;
|
||||||
import io.github.sspanak.tt9.util.Logger;
|
import io.github.sspanak.tt9.util.Logger;
|
||||||
import io.github.sspanak.tt9.util.SystemSettings;
|
import io.github.sspanak.tt9.util.SystemSettings;
|
||||||
|
|
||||||
public class PreferencesActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
|
public class PreferencesActivity extends ActivityWithNavigation implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
|
||||||
private SettingsStore settings;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
settings = new SettingsStore(this);
|
getSettings();
|
||||||
applyTheme();
|
applyTheme();
|
||||||
Logger.setLevel(settings.getLogLevel());
|
Logger.setLevel(settings.getLogLevel());
|
||||||
|
|
||||||
|
|
@ -87,6 +84,31 @@ public class PreferencesActivity extends AppCompatActivity implements Preference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
super.onBackPressed();
|
||||||
|
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.preferences_container);
|
||||||
|
if (fragment instanceof BaseScreenFragment) {
|
||||||
|
getOptionsCount = ((BaseScreenFragment) fragment)::getPreferenceCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void selectOption(int position, boolean click) {
|
||||||
|
// for convenience, scroll to the bottom on 0-key click
|
||||||
|
try {
|
||||||
|
if (position == 0) {
|
||||||
|
position = getOptionsCount.call();
|
||||||
|
resetKeyRepeat(); // ... but do not activate the last option on double click
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ignore) {}
|
||||||
|
|
||||||
|
super.selectOption(position, click);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getScreenName
|
* getScreenName
|
||||||
* Determines the name of the screen for the given preference, as defined in the preference's "fragment" attribute.
|
* Determines the name of the screen for the given preference, as defined in the preference's "fragment" attribute.
|
||||||
|
|
@ -136,6 +158,8 @@ public class PreferencesActivity extends AppCompatActivity implements Preference
|
||||||
* Replaces the currently displayed screen fragment with a new one.
|
* Replaces the currently displayed screen fragment with a new one.
|
||||||
*/
|
*/
|
||||||
private void displayScreen(BaseScreenFragment screen, boolean addToBackStack) {
|
private void displayScreen(BaseScreenFragment screen, boolean addToBackStack) {
|
||||||
|
getOptionsCount = screen::getPreferenceCount;
|
||||||
|
|
||||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||||
|
|
||||||
transaction.replace(R.id.preferences_container, screen);
|
transaction.replace(R.id.preferences_container, screen);
|
||||||
|
|
@ -168,15 +192,6 @@ public class PreferencesActivity extends AppCompatActivity implements Preference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public SettingsStore getSettings() {
|
|
||||||
if (settings == null) {
|
|
||||||
settings = new SettingsStore(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void applyTheme() {
|
private void applyTheme() {
|
||||||
AppCompatDelegate.setDefaultNightMode(settings.getTheme());
|
AppCompatDelegate.setDefaultNightMode(settings.getTheme());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,13 @@ import android.os.Bundle;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceCategory;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.util.Logger;
|
|
||||||
import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
||||||
|
import io.github.sspanak.tt9.util.Logger;
|
||||||
|
|
||||||
abstract public class BaseScreenFragment extends PreferenceFragmentCompat {
|
abstract public class BaseScreenFragment extends PreferenceFragmentCompat {
|
||||||
protected PreferencesActivity activity;
|
protected PreferencesActivity activity;
|
||||||
|
|
@ -69,6 +72,21 @@ abstract public class BaseScreenFragment extends PreferenceFragmentCompat {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getPreferenceCount() {
|
||||||
|
PreferenceScreen screen = getPreferenceScreen();
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (int i = screen.getPreferenceCount(); i > 0; i--) {
|
||||||
|
Preference pref = screen.getPreference(i - 1);
|
||||||
|
if (pref.isVisible()) {
|
||||||
|
count += pref instanceof PreferenceCategory ? ((PreferenceCategory) pref).getPreferenceCount() : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract public String getName();
|
abstract public String getName();
|
||||||
abstract protected int getTitle();
|
abstract protected int getTitle();
|
||||||
abstract protected int getXml();
|
abstract protected int getXml();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
package io.github.sspanak.tt9.ui;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.PersistableBundle;
|
||||||
|
import android.view.InputDevice;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.inputmethod.BaseInputConnection;
|
||||||
|
import android.view.inputmethod.InputConnection;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import io.github.sspanak.tt9.ime.helpers.Key;
|
||||||
|
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||||
|
import io.github.sspanak.tt9.util.Logger;
|
||||||
|
|
||||||
|
abstract public class ActivityWithNavigation extends AppCompatActivity {
|
||||||
|
public static final String LOG_TAG = ActivityWithNavigation.class.getSimpleName();
|
||||||
|
|
||||||
|
protected SettingsStore settings;
|
||||||
|
protected Callable<Integer> getOptionsCount;
|
||||||
|
private int lastKey = KeyEvent.KEYCODE_UNKNOWN;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
|
||||||
|
super.onCreate(savedInstanceState, persistentState);
|
||||||
|
getSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
final public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
|
// ignore our own key events
|
||||||
|
if (event.getSource() == InputDevice.SOURCE_UNKNOWN) {
|
||||||
|
return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the last key even if we are not going to process it. This is to avoid
|
||||||
|
// detecting a double click, when the user has pressed a different key in between.
|
||||||
|
boolean click = (keyCode == lastKey);
|
||||||
|
lastKey = keyCode;
|
||||||
|
|
||||||
|
if (!Key.isNumber(keyCode)) {
|
||||||
|
return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectOption(Key.codeToNumber(settings, keyCode), click);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
final public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||||
|
return Key.isNumber(keyCode) || super.onKeyUp(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final public SettingsStore getSettings() {
|
||||||
|
if (settings == null) {
|
||||||
|
settings = new SettingsStore(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void resetKeyRepeat() {
|
||||||
|
lastKey = KeyEvent.KEYCODE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates a click on the option at the given position. Positions are 1-based.
|
||||||
|
*/
|
||||||
|
protected void selectOption(int position, boolean click) {
|
||||||
|
int optionsCount;
|
||||||
|
|
||||||
|
try {
|
||||||
|
optionsCount = getOptionsCount.call();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(LOG_TAG, "Keypad navigation not possible. Failed to get options count. " + e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position <= 0 || position > optionsCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseInputConnection inputConnection = new BaseInputConnection(getWindow().getDecorView(), true);
|
||||||
|
|
||||||
|
// Scroll to the bottom to make sure we have a correct base for counting to the desired position
|
||||||
|
// Scrolling to top, then down to the position is not possible, because some phones allow
|
||||||
|
// selecting the Back button, but others don't.
|
||||||
|
scroll(inputConnection, optionsCount + 1, false);
|
||||||
|
scroll(inputConnection, optionsCount - position, true);
|
||||||
|
|
||||||
|
if (click) {
|
||||||
|
clickSelected(inputConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void scroll(@NonNull InputConnection connection, int positions, boolean up) {
|
||||||
|
KeyEvent press = new KeyEvent(KeyEvent.ACTION_DOWN, up ? KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN);
|
||||||
|
KeyEvent release = new KeyEvent(KeyEvent.ACTION_UP, up ? KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN);
|
||||||
|
press.setSource(InputDevice.SOURCE_UNKNOWN);
|
||||||
|
release.setSource(InputDevice.SOURCE_UNKNOWN);
|
||||||
|
|
||||||
|
for (int i = 0; i < positions; i++) {
|
||||||
|
connection.sendKeyEvent(press);
|
||||||
|
connection.sendKeyEvent(release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void clickSelected(@NonNull InputConnection connection) {
|
||||||
|
KeyEvent enterPress = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER);
|
||||||
|
KeyEvent enterRelease = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER);
|
||||||
|
connection.sendKeyEvent(enterPress);
|
||||||
|
connection.sendKeyEvent(enterRelease);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue