Add first settings UI for nav button configuration
This commit is contained in:
@@ -11,6 +11,19 @@
|
|||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true" />
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.NavButtons">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".SettingsActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/settings_title">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package se.ajpanton.navbuttons
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.ViewConfiguration
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
|
||||||
|
enum class NavButtonId(
|
||||||
|
val preferencePrefix: String,
|
||||||
|
@get:StringRes val titleRes: Int,
|
||||||
|
) {
|
||||||
|
BACK("back", R.string.button_back),
|
||||||
|
HOME("home", R.string.button_home),
|
||||||
|
RECENTS("recents", R.string.button_recents),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class NavButtonAction(
|
||||||
|
val storageValue: String,
|
||||||
|
@get:StringRes val labelRes: Int,
|
||||||
|
) {
|
||||||
|
STOCK("stock", R.string.action_stock),
|
||||||
|
KILL_FOREGROUND_APP("kill_foreground_app", R.string.action_kill_foreground_app),
|
||||||
|
NONE("none", R.string.action_none),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromStorageValue(value: String?): NavButtonAction {
|
||||||
|
return entries.firstOrNull { it.storageValue == value } ?: STOCK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class NavButtonConfig(
|
||||||
|
val pressAction: NavButtonAction,
|
||||||
|
val longPressAction: NavButtonAction,
|
||||||
|
val longPressDurationMs: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
class NavButtonSettingsStore(context: Context) {
|
||||||
|
private val preferences = context.applicationContext.getSharedPreferences(
|
||||||
|
PREFERENCES_NAME,
|
||||||
|
Context.MODE_PRIVATE,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getConfig(buttonId: NavButtonId): NavButtonConfig {
|
||||||
|
return NavButtonConfig(
|
||||||
|
pressAction = NavButtonAction.fromStorageValue(
|
||||||
|
preferences.getString(pressActionKey(buttonId), null),
|
||||||
|
),
|
||||||
|
longPressAction = NavButtonAction.fromStorageValue(
|
||||||
|
preferences.getString(longPressActionKey(buttonId), null),
|
||||||
|
),
|
||||||
|
longPressDurationMs = sanitizeDuration(
|
||||||
|
preferences.getInt(
|
||||||
|
longPressDurationKey(buttonId),
|
||||||
|
defaultLongPressDurationMs(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPressAction(buttonId: NavButtonId, action: NavButtonAction) {
|
||||||
|
preferences.edit()
|
||||||
|
.putString(pressActionKey(buttonId), action.storageValue)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setLongPressAction(buttonId: NavButtonId, action: NavButtonAction) {
|
||||||
|
preferences.edit()
|
||||||
|
.putString(longPressActionKey(buttonId), action.storageValue)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setLongPressDurationMs(buttonId: NavButtonId, durationMs: Int) {
|
||||||
|
preferences.edit()
|
||||||
|
.putInt(longPressDurationKey(buttonId), sanitizeDuration(durationMs))
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defaultLongPressDurationMs(): Int {
|
||||||
|
return sanitizeDuration(ViewConfiguration.getLongPressTimeout())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sanitizeDuration(durationMs: Int): Int {
|
||||||
|
return durationMs.coerceIn(MIN_LONG_PRESS_DURATION_MS, MAX_LONG_PRESS_DURATION_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PREFERENCES_NAME = "nav_button_settings"
|
||||||
|
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 MIN_LONG_PRESS_DURATION_MS = 100
|
||||||
|
const val MAX_LONG_PRESS_DURATION_MS = 10_000
|
||||||
|
const val MIN_DURATION_MS = 100
|
||||||
|
const val MAX_DURATION_MS = 10_000
|
||||||
|
const val DURATION_STEP_MS = 100
|
||||||
|
const val HOLD_REPEAT_START_DELAY_MS = 700L
|
||||||
|
const val HOLD_REPEAT_INTERVAL_MS = 100L
|
||||||
|
|
||||||
|
fun pressActionKey(buttonId: NavButtonId): String = key(buttonId, KEY_PRESS_ACTION)
|
||||||
|
|
||||||
|
fun longPressActionKey(buttonId: NavButtonId): String = key(buttonId, KEY_LONG_PRESS_ACTION)
|
||||||
|
|
||||||
|
fun longPressDurationKey(buttonId: NavButtonId): String =
|
||||||
|
key(buttonId, KEY_LONG_PRESS_DURATION_MS)
|
||||||
|
|
||||||
|
private fun key(buttonId: NavButtonId, suffix: String): String {
|
||||||
|
return "${buttonId.preferencePrefix}_$suffix"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
package se.ajpanton.navbuttons
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.TextView
|
||||||
|
|
||||||
|
class SettingsActivity : Activity() {
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
private lateinit var settingsStore: NavButtonSettingsStore
|
||||||
|
private lateinit var sectionsContainer: LinearLayout
|
||||||
|
private lateinit var previewOverlay: View
|
||||||
|
private lateinit var previewRunnable: Runnable
|
||||||
|
|
||||||
|
private var previewScheduled = false
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_settings)
|
||||||
|
|
||||||
|
title = getString(R.string.settings_title)
|
||||||
|
settingsStore = NavButtonSettingsStore(applicationContext)
|
||||||
|
sectionsContainer = findViewById(R.id.button_sections_container)
|
||||||
|
previewOverlay = findViewById(R.id.test_hold_overlay)
|
||||||
|
previewRunnable = Runnable {
|
||||||
|
previewScheduled = false
|
||||||
|
previewOverlay.visibility = View.VISIBLE
|
||||||
|
previewOverlay.animate()
|
||||||
|
.alpha(1f)
|
||||||
|
.setDuration(120)
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
addButtonSections()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
stopTestPreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addButtonSections() {
|
||||||
|
val inflater = LayoutInflater.from(this)
|
||||||
|
NavButtonId.entries.forEachIndexed { index, buttonId ->
|
||||||
|
val sectionView = inflater.inflate(
|
||||||
|
R.layout.item_nav_button_section,
|
||||||
|
sectionsContainer,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavButtonSectionController(sectionView, buttonId).bind()
|
||||||
|
sectionsContainer.addView(sectionView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startTestPreview(durationMs: Int) {
|
||||||
|
stopTestPreview()
|
||||||
|
previewScheduled = true
|
||||||
|
handler.postDelayed(previewRunnable, durationMs.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopTestPreview() {
|
||||||
|
if (previewScheduled) {
|
||||||
|
handler.removeCallbacks(previewRunnable)
|
||||||
|
previewScheduled = false
|
||||||
|
}
|
||||||
|
previewOverlay.animate().cancel()
|
||||||
|
previewOverlay.alpha = 0f
|
||||||
|
previewOverlay.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class NavButtonSectionController(
|
||||||
|
sectionView: View,
|
||||||
|
private val buttonId: NavButtonId,
|
||||||
|
) {
|
||||||
|
private val titleView: TextView = sectionView.findViewById(R.id.section_title)
|
||||||
|
private val pressActionSpinner: Spinner = sectionView.findViewById(R.id.press_action_spinner)
|
||||||
|
private val longPressActionSpinner: Spinner =
|
||||||
|
sectionView.findViewById(R.id.long_press_action_spinner)
|
||||||
|
private val durationInput: EditText = sectionView.findViewById(R.id.long_press_duration_input)
|
||||||
|
private val minusButton: Button = sectionView.findViewById(R.id.minus_button)
|
||||||
|
private val plusButton: Button = sectionView.findViewById(R.id.plus_button)
|
||||||
|
private val testButton: Button = sectionView.findViewById(R.id.test_button)
|
||||||
|
|
||||||
|
private var lastValidDurationMs = settingsStore.defaultLongPressDurationMs()
|
||||||
|
private var updatingDurationText = false
|
||||||
|
|
||||||
|
fun bind() {
|
||||||
|
val config = settingsStore.getConfig(buttonId)
|
||||||
|
titleView.setText(buttonId.titleRes)
|
||||||
|
|
||||||
|
bindActionSpinner(
|
||||||
|
pressActionSpinner,
|
||||||
|
config.pressAction,
|
||||||
|
settingsStore::setPressAction,
|
||||||
|
)
|
||||||
|
bindActionSpinner(
|
||||||
|
longPressActionSpinner,
|
||||||
|
config.longPressAction,
|
||||||
|
settingsStore::setLongPressAction,
|
||||||
|
)
|
||||||
|
|
||||||
|
applyDuration(config.longPressDurationMs)
|
||||||
|
bindDurationInput()
|
||||||
|
bindStepButtons()
|
||||||
|
bindTestButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindActionSpinner(
|
||||||
|
spinner: Spinner,
|
||||||
|
selectedAction: NavButtonAction,
|
||||||
|
storeAction: (NavButtonId, NavButtonAction) -> Unit,
|
||||||
|
) {
|
||||||
|
val labels = NavButtonAction.entries.map { getString(it.labelRes) }
|
||||||
|
val adapter = ArrayAdapter(
|
||||||
|
this@SettingsActivity,
|
||||||
|
R.layout.item_spinner_selected,
|
||||||
|
labels,
|
||||||
|
)
|
||||||
|
adapter.setDropDownViewResource(R.layout.item_spinner_dropdown)
|
||||||
|
spinner.adapter = adapter
|
||||||
|
spinner.setSelection(NavButtonAction.entries.indexOf(selectedAction), false)
|
||||||
|
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(
|
||||||
|
parent: AdapterView<*>?,
|
||||||
|
view: View?,
|
||||||
|
position: Int,
|
||||||
|
id: Long,
|
||||||
|
) {
|
||||||
|
storeAction(buttonId, NavButtonAction.entries[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.setLongPressDurationMs(buttonId, parsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindStepButtons() {
|
||||||
|
attachRepeatButton(minusButton) {
|
||||||
|
adjustDuration(-NavButtonSettingsStore.DURATION_STEP_MS)
|
||||||
|
}
|
||||||
|
attachRepeatButton(plusButton) {
|
||||||
|
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 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(
|
||||||
|
NavButtonSettingsStore.MIN_DURATION_MS,
|
||||||
|
NavButtonSettingsStore.MAX_DURATION_MS,
|
||||||
|
)
|
||||||
|
applyDuration(updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun normalizeTypedDuration(): Int {
|
||||||
|
val parsed = durationInput.text.toString().toIntOrNull() ?: lastValidDurationMs
|
||||||
|
val normalized = settingsStore.sanitizeDuration(parsed)
|
||||||
|
applyDuration(normalized)
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveDurationForUse(): Int {
|
||||||
|
return normalizeTypedDuration()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyDuration(durationMs: Int) {
|
||||||
|
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
|
||||||
|
settingsStore.setLongPressDurationMs(buttonId, sanitized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="22dp" />
|
||||||
|
<solid android:color="@color/settings_card_fill" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="@color/settings_card_stroke" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<gradient
|
||||||
|
android:angle="270"
|
||||||
|
android:endColor="@color/settings_window_gradient_end"
|
||||||
|
android:startColor="@color/settings_window_gradient_start" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="12dp" />
|
||||||
|
<solid android:color="@color/settings_spinner_fill" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="@color/settings_spinner_stroke" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="14dp" />
|
||||||
|
<solid android:color="@color/settings_spinner_popup_fill" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="@color/settings_spinner_popup_stroke" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@color/settings_test_overlay" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="@dimen/screen_padding"
|
||||||
|
android:paddingTop="@dimen/screen_padding"
|
||||||
|
android:paddingEnd="@dimen/screen_padding"
|
||||||
|
android:paddingBottom="@dimen/screen_padding_large">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/settings_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/settings_title"
|
||||||
|
android:textColor="@color/settings_text_primary"
|
||||||
|
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"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/test_hold_overlay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:alpha="0"
|
||||||
|
android:background="@drawable/bg_test_hold_overlay"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
<?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:id="@+id/section_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/settings_text_primary"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
|
android:text="@string/press_action_label"
|
||||||
|
android:textColor="@color/settings_label_text"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/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" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/long_press_action_label"
|
||||||
|
android:textColor="@color/settings_label_text"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/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" />
|
||||||
|
|
||||||
|
<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/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/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:singleLine="true"
|
||||||
|
android:textColor="@color/settings_input_text"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/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/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>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textColor="@color/settings_text_primary"
|
||||||
|
android:textSize="16sp" />
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="10dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textColor="@color/settings_text_primary"
|
||||||
|
android:textSize="16sp" />
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<resources>
|
||||||
|
<color name="settings_text_primary">#EAF2FF</color>
|
||||||
|
<color name="settings_text_secondary">#C4D0E1</color>
|
||||||
|
<color name="settings_text_tertiary">#9EB0C5</color>
|
||||||
|
<color name="settings_label_text">#B2C1D3</color>
|
||||||
|
<color name="settings_input_text">#F3F7FF</color>
|
||||||
|
|
||||||
|
<color name="settings_window_gradient_start">#0F1722</color>
|
||||||
|
<color name="settings_window_gradient_end">#1B2433</color>
|
||||||
|
<color name="settings_card_fill">#CC162231</color>
|
||||||
|
<color name="settings_card_stroke">#4B8FAED1</color>
|
||||||
|
<color name="settings_spinner_fill">#223346</color>
|
||||||
|
<color name="settings_spinner_stroke">#5F8AAAC7</color>
|
||||||
|
<color name="settings_spinner_popup_fill">#1A2635</color>
|
||||||
|
<color name="settings_spinner_popup_stroke">#7098B6D3</color>
|
||||||
|
<color name="settings_test_overlay">#2EDAE8FF</color>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<resources>
|
||||||
|
<color name="settings_text_primary">#102135</color>
|
||||||
|
<color name="settings_text_secondary">#4F5F73</color>
|
||||||
|
<color name="settings_text_tertiary">#64768A</color>
|
||||||
|
<color name="settings_label_text">#506174</color>
|
||||||
|
<color name="settings_input_text">#102135</color>
|
||||||
|
|
||||||
|
<color name="settings_window_gradient_start">#EAF6FF</color>
|
||||||
|
<color name="settings_window_gradient_end">#FFF4EC</color>
|
||||||
|
<color name="settings_card_fill">#F2FFFFFF</color>
|
||||||
|
<color name="settings_card_stroke">#1F27425C</color>
|
||||||
|
<color name="settings_spinner_fill">#F9FCFF</color>
|
||||||
|
<color name="settings_spinner_stroke">#33536A82</color>
|
||||||
|
<color name="settings_spinner_popup_fill">#FFFFFFFF</color>
|
||||||
|
<color name="settings_spinner_popup_stroke">#264E657D</color>
|
||||||
|
<color name="settings_test_overlay">#70FFFFFF</color>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<resources>
|
||||||
|
<dimen name="screen_padding">20dp</dimen>
|
||||||
|
<dimen name="screen_padding_large">28dp</dimen>
|
||||||
|
<dimen name="section_spacing">16dp</dimen>
|
||||||
|
</resources>
|
||||||
@@ -1,4 +1,18 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">NavButtons</string>
|
<string name="app_name">NavButtons</string>
|
||||||
<string name="xposed_desc">Kill foreground app on "Back" button long-press.</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="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>
|
||||||
|
<string name="action_stock">Stock</string>
|
||||||
|
<string name="action_kill_foreground_app">Kill foreground app</string>
|
||||||
|
<string name="action_none">None</string>
|
||||||
|
<string name="minus_button_label">-</string>
|
||||||
|
<string name="plus_button_label">+</string>
|
||||||
|
<string name="test_button_label">Duration test (hold)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<resources>
|
||||||
|
<style name="Theme.NavButtons" parent="@android:style/Theme.DeviceDefault.DayNight">
|
||||||
|
<item name="android:windowBackground">@drawable/bg_settings_window</item>
|
||||||
|
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
Reference in New Issue
Block a user