Add first settings UI for nav button configuration
This commit is contained in:
@@ -11,6 +11,19 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
<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>
|
||||
|
||||
@@ -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