1
0
Fork 0

Keypad navigation in the Settings (#499)

This commit is contained in:
Dimo Karaivanov 2024-04-25 10:40:29 +03:00 committed by sspanak
parent 95e0eb4130
commit c9e5707803
3 changed files with 176 additions and 17 deletions

View file

@ -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());
}

View file

@ -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();

View file

@ -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);
}
}