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.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
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.languages.LanguagesScreen;
|
||||
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.SystemSettings;
|
||||
|
||||
public class PreferencesActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
|
||||
private SettingsStore settings;
|
||||
|
||||
|
||||
public class PreferencesActivity extends ActivityWithNavigation implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
settings = new SettingsStore(this);
|
||||
getSettings();
|
||||
applyTheme();
|
||||
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
|
||||
* 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.
|
||||
*/
|
||||
private void displayScreen(BaseScreenFragment screen, boolean addToBackStack) {
|
||||
getOptionsCount = screen::getPreferenceCount;
|
||||
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
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() {
|
||||
AppCompatDelegate.setDefaultNightMode(settings.getTheme());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ import android.os.Bundle;
|
|||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
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.util.Logger;
|
||||
|
||||
abstract public class BaseScreenFragment extends PreferenceFragmentCompat {
|
||||
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 protected int getTitle();
|
||||
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