Add side arrow navigation buttons

This commit is contained in:
ajp_anton
2026-06-22 00:42:17 +00:00
parent e658caa399
commit 0956bbc4d7
8 changed files with 1902 additions and 70 deletions
@@ -39,6 +39,22 @@ enum class NavButtonAction(
}
}
enum class SideArrowLongPressAction(
val storageValue: String,
@get:StringRes val labelRes: Int,
) {
NOTHING("nothing", R.string.side_arrow_long_press_nothing),
SINGLE_STEP("single_step", R.string.side_arrow_long_press_single_step),
MULTIPLE_STEPS("multiple_steps", R.string.side_arrow_long_press_multiple_steps),
;
companion object {
fun fromStorageValue(value: String?): SideArrowLongPressAction {
return entries.firstOrNull { it.storageValue == value } ?: MULTIPLE_STEPS
}
}
}
data class NavButtonConfig(
val pressAction: NavButtonAction,
val longPressAction: NavButtonAction,
@@ -91,6 +107,61 @@ class NavButtonSettingsStore(context: Context) {
}
}
fun isSideArrowsEnabled(): Boolean {
return currentPreferences().getBoolean(KEY_SIDE_ARROWS_ENABLED, false)
}
fun setSideArrowsEnabled(enabled: Boolean) {
persistChanges {
putBoolean(KEY_SIDE_ARROWS_ENABLED, enabled)
}
}
fun getSideArrowLongPressAction(): SideArrowLongPressAction {
return SideArrowLongPressAction.fromStorageValue(
currentPreferences().getString(KEY_SIDE_ARROW_LONG_PRESS_ACTION, null),
)
}
fun setSideArrowLongPressAction(action: SideArrowLongPressAction) {
persistChanges {
putString(KEY_SIDE_ARROW_LONG_PRESS_ACTION, action.storageValue)
}
}
fun getSideArrowRepeatStepsPerSecond(): Float {
return sanitizeSideArrowRepeatSpeed(
currentPreferences().getFloat(
KEY_SIDE_ARROW_REPEAT_STEPS_PER_SECOND,
DEFAULT_SIDE_ARROW_REPEAT_STEPS_PER_SECOND,
),
)
}
fun setSideArrowRepeatStepsPerSecond(stepsPerSecond: Float) {
persistChanges {
putFloat(
KEY_SIDE_ARROW_REPEAT_STEPS_PER_SECOND,
sanitizeSideArrowRepeatSpeed(stepsPerSecond),
)
}
}
fun getSideArrowLongPressDurationMs(): Int {
return sanitizeDuration(
currentPreferences().getInt(
KEY_SIDE_ARROW_LONG_PRESS_DURATION_MS,
defaultLongPressDurationMs(),
),
)
}
fun setSideArrowLongPressDurationMs(durationMs: Int) {
persistChanges {
putInt(KEY_SIDE_ARROW_LONG_PRESS_DURATION_MS, sanitizeDuration(durationMs))
}
}
fun defaultLongPressDurationMs(): Int {
return sanitizeDuration(ViewConfiguration.getLongPressTimeout())
}
@@ -99,6 +170,17 @@ class NavButtonSettingsStore(context: Context) {
return durationMs.coerceIn(MIN_LONG_PRESS_DURATION_MS, MAX_LONG_PRESS_DURATION_MS)
}
fun sanitizeSideArrowRepeatSpeed(stepsPerSecond: Float): Float {
return if (stepsPerSecond.isFinite()) {
stepsPerSecond.coerceIn(
MIN_SIDE_ARROW_REPEAT_STEPS_PER_SECOND,
MAX_SIDE_ARROW_REPEAT_STEPS_PER_SECOND,
)
} else {
DEFAULT_SIDE_ARROW_REPEAT_STEPS_PER_SECOND
}
}
private fun currentPreferences(): SharedPreferences {
return remotePreferences ?: localPreferences
}
@@ -129,9 +211,15 @@ class NavButtonSettingsStore(context: Context) {
companion object {
private const val TAG = "NavButtons"
const val PREFERENCES_NAME = "nav_button_settings"
const val ACTION_SETTINGS_CHANGED = "se.ajpanton.navbuttons.SETTINGS_CHANGED"
const val EXTRA_SIDE_ARROWS_ENABLED = "side_arrows_enabled"
const val KEY_PRESS_ACTION = "press_action"
const val KEY_LONG_PRESS_ACTION = "long_press_action"
const val KEY_LONG_PRESS_DURATION_MS = "long_press_duration_ms"
const val KEY_SIDE_ARROWS_ENABLED = "side_arrows_enabled"
const val KEY_SIDE_ARROW_LONG_PRESS_ACTION = "side_arrow_long_press_action"
const val KEY_SIDE_ARROW_REPEAT_STEPS_PER_SECOND = "side_arrow_repeat_steps_per_second"
const val KEY_SIDE_ARROW_LONG_PRESS_DURATION_MS = "side_arrow_long_press_duration_ms"
private const val KEY_LAST_UPDATED_AT_MS = "last_updated_at_ms"
const val MIN_LONG_PRESS_DURATION_MS = 100
@@ -141,6 +229,10 @@ class NavButtonSettingsStore(context: Context) {
const val DURATION_STEP_MS = 100
const val HOLD_REPEAT_START_DELAY_MS = 700L
const val HOLD_REPEAT_INTERVAL_MS = 100L
const val DEFAULT_SIDE_ARROW_REPEAT_STEPS_PER_SECOND = 20f
const val MIN_SIDE_ARROW_REPEAT_STEPS_PER_SECOND = 0.1f
const val MAX_SIDE_ARROW_REPEAT_STEPS_PER_SECOND = 100f
const val SIDE_ARROW_REPEAT_SPEED_STEP = 1f
fun pressActionKey(buttonId: NavButtonId): String = key(buttonId, KEY_PRESS_ACTION)
@@ -161,13 +253,15 @@ class NavButtonSettingsStore(context: Context) {
pressActionKey(buttonId),
longPressActionKey(buttonId),
)
}
} + KEY_SIDE_ARROW_LONG_PRESS_ACTION
}
private val durationKeys by lazy(LazyThreadSafetyMode.NONE) {
NavButtonId.entries.map(::longPressDurationKey)
NavButtonId.entries.map(::longPressDurationKey) + KEY_SIDE_ARROW_LONG_PRESS_DURATION_MS
}
private val floatKeys = listOf(KEY_SIDE_ARROW_REPEAT_STEPS_PER_SECOND)
private fun key(buttonId: NavButtonId, suffix: String): String {
return "${buttonId.preferencePrefix}_$suffix"
}
@@ -220,7 +314,10 @@ class NavButtonSettingsStore(context: Context) {
}
private fun hasStoredConfig(preferences: SharedPreferences): Boolean {
return actionKeys.any(preferences::contains) || durationKeys.any(preferences::contains)
return actionKeys.any(preferences::contains) ||
durationKeys.any(preferences::contains) ||
floatKeys.any(preferences::contains) ||
preferences.contains(KEY_SIDE_ARROWS_ENABLED)
}
private fun copyPreferences(source: SharedPreferences, target: SharedPreferences) {
@@ -242,6 +339,27 @@ class NavButtonSettingsStore(context: Context) {
}
}
for (key in floatKeys) {
if (source.contains(key)) {
editor.putFloat(
key,
source.getFloat(key, DEFAULT_SIDE_ARROW_REPEAT_STEPS_PER_SECOND)
.coerceIn(
MIN_SIDE_ARROW_REPEAT_STEPS_PER_SECOND,
MAX_SIDE_ARROW_REPEAT_STEPS_PER_SECOND,
),
)
} else {
editor.remove(key)
}
}
if (source.contains(KEY_SIDE_ARROWS_ENABLED)) {
editor.putBoolean(KEY_SIDE_ARROWS_ENABLED, source.getBoolean(KEY_SIDE_ARROWS_ENABLED, false))
} else {
editor.remove(KEY_SIDE_ARROWS_ENABLED)
}
if (source.contains(KEY_LAST_UPDATED_AT_MS)) {
editor.putLong(KEY_LAST_UPDATED_AT_MS, source.getLong(KEY_LAST_UPDATED_AT_MS, 0L))
} else {
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,7 @@
package se.ajpanton.navbuttons
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
@@ -13,10 +14,15 @@ import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.CheckBox
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import java.text.DecimalFormatSymbols
import java.util.Locale
import java.util.concurrent.TimeUnit
class SettingsActivity : Activity() {
private val handler = Handler(Looper.getMainLooper())
@@ -61,23 +67,36 @@ class SettingsActivity : Activity() {
false,
)
if (index > 0) {
val marginTop = resources.getDimensionPixelSize(R.dimen.section_spacing)
val layoutParams = sectionView.layoutParams as? ViewGroup.MarginLayoutParams
if (layoutParams != null) {
layoutParams.topMargin = marginTop
} else {
sectionView.layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
).apply {
topMargin = marginTop
}
}
applySectionTopMargin(sectionView)
}
NavButtonSectionController(sectionView, buttonId).bind()
sectionsContainer.addView(sectionView)
}
val sideArrowsSectionView = inflater.inflate(
R.layout.item_side_arrows_section,
sectionsContainer,
false,
)
applySectionTopMargin(sideArrowsSectionView)
SideArrowsSectionController(sideArrowsSectionView).bind()
sectionsContainer.addView(sideArrowsSectionView)
}
private fun applySectionTopMargin(sectionView: View) {
val marginTop = resources.getDimensionPixelSize(R.dimen.section_spacing)
val layoutParams = sectionView.layoutParams as? ViewGroup.MarginLayoutParams
if (layoutParams != null) {
layoutParams.topMargin = marginTop
} else {
sectionView.layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
).apply {
topMargin = marginTop
}
}
}
private fun startTestPreview(durationMs: Int) {
@@ -96,6 +115,45 @@ class SettingsActivity : Activity() {
previewOverlay.visibility = View.GONE
}
private fun attachRepeatButton(button: Button, action: () -> Unit) {
var repeated = false
val repeatRunnable = object : Runnable {
override fun run() {
repeated = true
action()
handler.postDelayed(this, NavButtonSettingsStore.HOLD_REPEAT_INTERVAL_MS)
}
}
button.setOnClickListener {
if (!repeated) {
action()
}
}
button.setOnTouchListener { _, event ->
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
repeated = false
handler.postDelayed(
repeatRunnable,
NavButtonSettingsStore.HOLD_REPEAT_START_DELAY_MS,
)
false
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL,
-> {
handler.removeCallbacks(repeatRunnable)
false
}
else -> false
}
}
}
private inner class NavButtonSectionController(
sectionView: View,
private val buttonId: NavButtonId,
@@ -228,45 +286,6 @@ class SettingsActivity : Activity() {
}
}
private fun attachRepeatButton(button: Button, action: () -> Unit) {
var repeated = false
val repeatRunnable = object : Runnable {
override fun run() {
repeated = true
action()
handler.postDelayed(this, NavButtonSettingsStore.HOLD_REPEAT_INTERVAL_MS)
}
}
button.setOnClickListener {
if (!repeated) {
action()
}
}
button.setOnTouchListener { view, event ->
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
repeated = false
handler.postDelayed(
repeatRunnable,
NavButtonSettingsStore.HOLD_REPEAT_START_DELAY_MS,
)
false
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL,
-> {
handler.removeCallbacks(repeatRunnable)
false
}
else -> false
}
}
}
private fun adjustDuration(deltaMs: Int) {
val current = resolveDurationForUse()
val updated = (current + deltaMs).coerceIn(
@@ -303,4 +322,346 @@ class SettingsActivity : Activity() {
}
}
}
private inner class SideArrowsSectionController(sectionView: View) {
private val sideArrowsCheckbox: CheckBox = sectionView.findViewById(R.id.side_arrows_checkbox)
private val optionsContainer: LinearLayout =
sectionView.findViewById(R.id.side_arrows_options_container)
private val longPressActionSpinner: Spinner =
sectionView.findViewById(R.id.side_arrow_long_press_action_spinner)
private val repeatSpeedContainer: LinearLayout =
sectionView.findViewById(R.id.side_arrow_repeat_speed_container)
private val repeatSpeedInput: EditText =
sectionView.findViewById(R.id.side_arrow_repeat_speed_input)
private val speedMinusButton: Button =
sectionView.findViewById(R.id.side_arrow_speed_minus_button)
private val speedPlusButton: Button =
sectionView.findViewById(R.id.side_arrow_speed_plus_button)
private val durationInput: EditText =
sectionView.findViewById(R.id.side_arrow_long_press_duration_input)
private val durationMinusButton: Button =
sectionView.findViewById(R.id.side_arrow_duration_minus_button)
private val durationPlusButton: Button =
sectionView.findViewById(R.id.side_arrow_duration_plus_button)
private val testButton: Button = sectionView.findViewById(R.id.side_arrow_test_button)
private val restartSamsungUiButton: Button =
sectionView.findViewById(R.id.restart_samsung_ui_button)
private val decimalSeparator = DecimalFormatSymbols.getInstance().decimalSeparator
private var lastValidDurationMs = settingsStore.defaultLongPressDurationMs()
private var lastValidRepeatSpeed = NavButtonSettingsStore.DEFAULT_SIDE_ARROW_REPEAT_STEPS_PER_SECOND
private var updatingDurationText = false
private var updatingRepeatSpeedText = false
fun bind() {
sideArrowsCheckbox.isChecked = settingsStore.isSideArrowsEnabled()
bindLongPressActionSpinner()
applyRepeatSpeed(settingsStore.getSideArrowRepeatStepsPerSecond(), persist = false)
bindRepeatSpeedInput()
bindRepeatSpeedButtons()
applyDuration(settingsStore.getSideArrowLongPressDurationMs(), persist = false)
bindDurationInput()
bindDurationButtons()
bindTestButton()
updateOptionsEnabled()
sideArrowsCheckbox.setOnCheckedChangeListener { _, isChecked ->
settingsStore.setSideArrowsEnabled(isChecked)
updateOptionsEnabled()
sendBroadcast(
Intent(NavButtonSettingsStore.ACTION_SETTINGS_CHANGED).putExtra(
NavButtonSettingsStore.EXTRA_SIDE_ARROWS_ENABLED,
isChecked,
),
)
}
restartSamsungUiButton.setOnClickListener {
restartSamsungUiButton.isEnabled = false
Toast.makeText(
this@SettingsActivity,
"Restarting One UI Home and SystemUI...",
Toast.LENGTH_SHORT,
).show()
Thread {
val result = restartSamsungUiProcesses()
handler.post {
restartSamsungUiButton.isEnabled = true
Toast.makeText(this@SettingsActivity, result, Toast.LENGTH_LONG).show()
}
}.start()
}
}
private fun bindLongPressActionSpinner() {
val labels = SideArrowLongPressAction.entries.map { getString(it.labelRes) }
val adapter = ArrayAdapter(
this@SettingsActivity,
R.layout.item_spinner_selected,
labels,
)
adapter.setDropDownViewResource(R.layout.item_spinner_dropdown)
longPressActionSpinner.adapter = adapter
longPressActionSpinner.setSelection(
SideArrowLongPressAction.entries.indexOf(settingsStore.getSideArrowLongPressAction()),
false,
)
longPressActionSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long,
) {
val action = SideArrowLongPressAction.entries[position]
settingsStore.setSideArrowLongPressAction(action)
updateRepeatSpeedVisibility(action)
}
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
}
updateRepeatSpeedVisibility(settingsStore.getSideArrowLongPressAction())
}
private fun bindRepeatSpeedInput() {
repeatSpeedInput.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
normalizeTypedRepeatSpeed()
}
}
repeatSpeedInput.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int,
) = Unit
override fun onTextChanged(
s: CharSequence?,
start: Int,
before: Int,
count: Int,
) = Unit
override fun afterTextChanged(editable: Editable?) {
if (updatingRepeatSpeedText) {
return
}
val parsed = parseRepeatSpeed(editable?.toString()) ?: return
if (parsed in NavButtonSettingsStore.MIN_SIDE_ARROW_REPEAT_STEPS_PER_SECOND..
NavButtonSettingsStore.MAX_SIDE_ARROW_REPEAT_STEPS_PER_SECOND
) {
lastValidRepeatSpeed = parsed
settingsStore.setSideArrowRepeatStepsPerSecond(parsed)
}
}
})
}
private fun bindRepeatSpeedButtons() {
attachRepeatButton(speedMinusButton) {
adjustRepeatSpeed(-NavButtonSettingsStore.SIDE_ARROW_REPEAT_SPEED_STEP)
}
attachRepeatButton(speedPlusButton) {
adjustRepeatSpeed(NavButtonSettingsStore.SIDE_ARROW_REPEAT_SPEED_STEP)
}
}
private fun bindDurationInput() {
durationInput.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
normalizeTypedDuration()
}
}
durationInput.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int,
) = Unit
override fun onTextChanged(
s: CharSequence?,
start: Int,
before: Int,
count: Int,
) = Unit
override fun afterTextChanged(editable: Editable?) {
if (updatingDurationText) {
return
}
val parsed = editable?.toString()?.toIntOrNull() ?: return
if (parsed in NavButtonSettingsStore.MIN_DURATION_MS..NavButtonSettingsStore.MAX_DURATION_MS) {
lastValidDurationMs = parsed
settingsStore.setSideArrowLongPressDurationMs(parsed)
}
}
})
}
private fun bindDurationButtons() {
attachRepeatButton(durationMinusButton) {
adjustDuration(-NavButtonSettingsStore.DURATION_STEP_MS)
}
attachRepeatButton(durationPlusButton) {
adjustDuration(NavButtonSettingsStore.DURATION_STEP_MS)
}
}
private fun bindTestButton() {
testButton.setOnTouchListener { view, event ->
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
view.isPressed = true
startTestPreview(resolveDurationForUse())
true
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL,
-> {
view.isPressed = false
stopTestPreview()
true
}
else -> false
}
}
}
private fun updateOptionsEnabled() {
val enabled = sideArrowsCheckbox.isChecked
optionsContainer.alpha = if (enabled) 1f else 0.45f
setEnabledRecursively(optionsContainer, enabled)
}
private fun setEnabledRecursively(view: View, enabled: Boolean) {
view.isEnabled = enabled
if (view is ViewGroup) {
repeat(view.childCount) { index ->
setEnabledRecursively(view.getChildAt(index), enabled)
}
}
}
private fun updateRepeatSpeedVisibility(action: SideArrowLongPressAction) {
repeatSpeedContainer.visibility =
if (action == SideArrowLongPressAction.MULTIPLE_STEPS) View.VISIBLE else View.GONE
}
private fun adjustRepeatSpeed(delta: Float) {
applyRepeatSpeed(resolveRepeatSpeedForUse() + delta, persist = true)
}
private fun normalizeTypedRepeatSpeed(): Float {
val parsed = parseRepeatSpeed(repeatSpeedInput.text.toString()) ?: lastValidRepeatSpeed
val normalized = settingsStore.sanitizeSideArrowRepeatSpeed(parsed)
applyRepeatSpeed(normalized, persist = true)
return normalized
}
private fun resolveRepeatSpeedForUse(): Float {
return normalizeTypedRepeatSpeed()
}
private fun applyRepeatSpeed(stepsPerSecond: Float, persist: Boolean) {
val sanitized = settingsStore.sanitizeSideArrowRepeatSpeed(stepsPerSecond)
val text = formatRepeatSpeed(sanitized)
updatingRepeatSpeedText = true
if (repeatSpeedInput.text.toString() != text) {
repeatSpeedInput.setText(text)
repeatSpeedInput.setSelection(repeatSpeedInput.text.length)
}
updatingRepeatSpeedText = false
lastValidRepeatSpeed = sanitized
if (persist) {
settingsStore.setSideArrowRepeatStepsPerSecond(sanitized)
}
}
private fun parseRepeatSpeed(text: String?): Float? {
return text
?.trim()
?.replace(',', '.')
?.toFloatOrNull()
}
private fun formatRepeatSpeed(stepsPerSecond: Float): String {
val raw = if (stepsPerSecond == stepsPerSecond.toInt().toFloat()) {
stepsPerSecond.toInt().toString()
} else {
String.format(Locale.US, "%.2f", stepsPerSecond)
.trimEnd('0')
.trimEnd('.')
}
return raw.replace('.', decimalSeparator)
}
private fun adjustDuration(deltaMs: Int) {
val current = resolveDurationForUse()
val updated = (current + deltaMs).coerceIn(
NavButtonSettingsStore.MIN_DURATION_MS,
NavButtonSettingsStore.MAX_DURATION_MS,
)
applyDuration(updated, persist = true)
}
private fun normalizeTypedDuration(): Int {
val parsed = durationInput.text.toString().toIntOrNull() ?: lastValidDurationMs
val normalized = settingsStore.sanitizeDuration(parsed)
applyDuration(normalized, persist = true)
return normalized
}
private fun resolveDurationForUse(): Int {
return normalizeTypedDuration()
}
private fun applyDuration(durationMs: Int, persist: Boolean) {
val sanitized = settingsStore.sanitizeDuration(durationMs)
val text = sanitized.toString()
updatingDurationText = true
if (durationInput.text.toString() != text) {
durationInput.setText(text)
durationInput.setSelection(durationInput.text.length)
}
updatingDurationText = false
lastValidDurationMs = sanitized
if (persist) {
settingsStore.setSideArrowLongPressDurationMs(sanitized)
}
}
private fun restartSamsungUiProcesses(): String {
return try {
val command = listOf(
"am force-stop com.sec.android.app.launcher",
"killall com.android.systemui",
"sleep 1",
"am start -a android.intent.action.MAIN -c android.intent.category.HOME",
).joinToString("; ")
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command))
val finished = process.waitFor(10, TimeUnit.SECONDS)
if (!finished) {
process.destroyForcibly()
"Restart command timed out."
} else if (process.exitValue() == 0) {
"Restart command completed."
} else {
"Restart command failed. Is root allowed?"
}
} catch (_: Throwable) {
"Restart command failed. Is root allowed?"
}
}
}
}
@@ -29,15 +29,6 @@
android:textSize="30sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:lineSpacingExtra="3dp"
android:text="@string/settings_summary"
android:textColor="@color/settings_text_secondary"
android:textSize="15sp" />
<LinearLayout
android:id="@+id/button_sections_container"
android:layout_width="match_parent"
@@ -0,0 +1,186 @@
<?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="wrap_content"
android:background="@drawable/bg_button_section"
android:elevation="2dp"
android:orientation="vertical"
android:padding="18dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/side_arrows_section_title"
android:textColor="@color/settings_text_primary"
android:textSize="22sp"
android:textStyle="bold" />
<CheckBox
android:id="@+id/side_arrows_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:buttonTint="@color/settings_label_text"
android:saveEnabled="false"
android:text="@string/side_arrows_enable_label"
android:textColor="@color/settings_text_primary"
android:textSize="16sp" />
<LinearLayout
android:id="@+id/side_arrows_options_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/long_press_action_label"
android:textColor="@color/settings_label_text"
android:textSize="13sp"
android:textStyle="bold" />
<Spinner
android:id="@+id/side_arrow_long_press_action_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/bg_spinner_field"
android:dropDownWidth="match_parent"
android:paddingStart="12dp"
android:paddingTop="6dp"
android:paddingEnd="12dp"
android:paddingBottom="6dp"
android:popupBackground="@drawable/bg_spinner_popup"
android:saveEnabled="false" />
<LinearLayout
android:id="@+id/side_arrow_repeat_speed_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/side_arrow_repeat_speed_label"
android:textColor="@color/settings_label_text"
android:textSize="13sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/side_arrow_speed_minus_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:minWidth="48dp"
android:text="@string/minus_button_label"
android:textAllCaps="false"
android:textSize="22sp" />
<EditText
android:id="@+id/side_arrow_repeat_speed_input"
android:layout_width="88dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:gravity="center"
android:importantForAutofill="no"
android:inputType="numberDecimal"
android:maxLength="6"
android:saveEnabled="false"
android:singleLine="true"
android:textColor="@color/settings_input_text"
android:textSize="18sp" />
<Button
android:id="@+id/side_arrow_speed_plus_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:minWidth="48dp"
android:text="@string/plus_button_label"
android:textAllCaps="false"
android:textSize="20sp" />
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/long_press_duration_label"
android:textColor="@color/settings_label_text"
android:textSize="13sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/side_arrow_duration_minus_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:minWidth="48dp"
android:text="@string/minus_button_label"
android:textAllCaps="false"
android:textSize="22sp" />
<EditText
android:id="@+id/side_arrow_long_press_duration_input"
android:layout_width="88dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:gravity="center"
android:importantForAutofill="no"
android:inputType="number"
android:maxLength="5"
android:saveEnabled="false"
android:singleLine="true"
android:textColor="@color/settings_input_text"
android:textSize="18sp" />
<Button
android:id="@+id/side_arrow_duration_plus_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:minWidth="48dp"
android:text="@string/plus_button_label"
android:textAllCaps="false"
android:textSize="20sp" />
</LinearLayout>
<Button
android:id="@+id/side_arrow_test_button"
android:layout_width="wrap_content"
android:layout_height="54dp"
android:layout_marginTop="14dp"
android:minWidth="160dp"
android:text="@string/test_button_label"
android:textAllCaps="false"
android:textSize="16sp" />
</LinearLayout>
<Button
android:id="@+id/restart_samsung_ui_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:text="@string/restart_samsung_ui_button_label" />
</LinearLayout>
+7 -1
View File
@@ -2,10 +2,16 @@
<string name="app_name">NavButtons</string>
<string name="xposed_desc">Customize navigation button press and long-press actions.</string>
<string name="settings_title">Nav Button Settings</string>
<string name="settings_summary">Choose what each navigation button should do on press and long press, then tune how long a long press should feel.</string>
<string name="button_back">Back Button</string>
<string name="button_home">Home Button</string>
<string name="button_recents">Recents Button</string>
<string name="side_arrows_section_title">Side arrows</string>
<string name="side_arrows_enable_label">Enable side arrows</string>
<string name="side_arrow_long_press_nothing">Nothing</string>
<string name="side_arrow_long_press_single_step">Single step</string>
<string name="side_arrow_long_press_multiple_steps">Multiple steps</string>
<string name="side_arrow_repeat_speed_label">Repeat speed (steps/sec)</string>
<string name="restart_samsung_ui_button_label">Restart One UI Home + SystemUI</string>
<string name="press_action_label">When pressed</string>
<string name="long_press_action_label">When long-pressed</string>
<string name="long_press_duration_label">Long-press duration (ms)</string>
@@ -1,3 +1,4 @@
android
com.android.systemui
com.sec.android.app.launcher
com.samsung.systemui.navillera