commit 5ff0c95e5e678b5eb3e639443da57dc84ef0536d truex <truex@equwal.com> 2026-09-21 03:48:48 -0700 Let the user change a button from the Done step The Done step has two buttons: Change what it does, and Change how you press it. They go back to those steps with the button and the gesture kept, so that the user can adjust a binding while he tests it. Back on the Done step goes to the actions too. Route.back holds the rule for Back as a pure function, with a test for each branch. The step that follows a return from the action list now runs only after the screen was really left for that list.
CHANGELOG.md | 6 ++++ .../equwal/assistkey/setup/GuidedSetupActivity.kt | 38 ++++++++++++++++------ .../main/java/dev/equwal/assistkey/setup/Route.kt | 19 +++++++++++ .../java/dev/equwal/assistkey/setup/RouteTest.kt | 28 ++++++++++++++++ 4 files changed, 81 insertions(+), 10 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md index 7659600..33a20d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +- **Change a button from the Done step.** "Change what it does" and "Change + how you press it" go back to those steps with the button kept. Back on the + Done step goes to the actions too. Tests: `Route.back`. + ## 0.0.12-alpha - 2026-09-21 Version code 12. diff --git a/app/src/main/java/dev/equwal/assistkey/setup/GuidedSetupActivity.kt b/app/src/main/java/dev/equwal/assistkey/setup/GuidedSetupActivity.kt index 85bb9c5..bf5f38d 100644 --- a/app/src/main/java/dev/equwal/assistkey/setup/GuidedSetupActivity.kt +++ b/app/src/main/java/dev/equwal/assistkey/setup/GuidedSetupActivity.kt @@ -20,6 +20,7 @@ import dev.equwal.assistkey.model.Trigger import dev.equwal.assistkey.route.ActionRouter import dev.equwal.assistkey.route.ServiceHolder import dev.equwal.assistkey.setup.Route.Need +import dev.equwal.assistkey.setup.Route.Step import dev.equwal.assistkey.shell.PowerControl import dev.equwal.assistkey.shell.Shell import dev.equwal.assistkey.store.Store @@ -52,8 +53,6 @@ import dev.equwal.assistkey.voice.Dictation */ class GuidedSetupActivity : Activity() { - private enum class Step { BUTTON, PRESS, ACTION, ALLOW, DONE } - private var step = Step.BUTTON private var keys: List<HwKey> = emptyList() private var trigger: Trigger? = null @@ -64,6 +63,13 @@ class GuidedSetupActivity : Activity() { /** True when another screen made the binding. This screen then only asks, and leaves. */ private var askOnly = false + /** + * True after the menu editor or the full action list is opened from the + * ACTION step. It marks a real trip away from this screen, so onResume + * can tell that from a return to ACTION chosen on purpose (from DONE). + */ + private var leftForPicker = false + private fun tokens(text: String?): List<HwKey> = text.orEmpty().split('+').mapNotNull(HwKey::fromToken) @@ -76,6 +82,7 @@ class GuidedSetupActivity : Activity() { keys = tokens(s.getString("keys")) trigger = s.getString("trigger")?.let(Trigger::parse) fromHub = s.getBoolean("fromHub") + leftForPicker = s.getBoolean("leftForPicker") } ?: intent.getStringExtra(EXTRA_ASK)?.let(Trigger::parse)?.let { // Another screen made this binding. Ask for what it needs, and nothing else. trigger = it @@ -95,6 +102,7 @@ class GuidedSetupActivity : Activity() { out.putString("step", step.name) out.putString("keys", keys.joinToString("+") { it.token }) out.putBoolean("fromHub", fromHub) + out.putBoolean("leftForPicker", leftForPicker) trigger?.let { out.putString("trigger", it.id) } } @@ -102,18 +110,19 @@ class GuidedSetupActivity : Activity() { super.onResume() Shell.connect(this) // Back from the action list or the menu editor: a binding now exists, or not. - if (step == Step.ACTION && trigger?.let { Store.bindings(this).isBound(it) } == true) step = Step.ALLOW + // This runs only after a real trip to one of those screens, not after + // a plain return to ACTION chosen from the DONE step. + if (leftForPicker) { + leftForPicker = false + if (step == Step.ACTION && trigger?.let { Store.bindings(this).isBound(it) } == true) step = Step.ALLOW + } build() } @Deprecated("Deprecated in Java") override fun onBackPressed() { - when (step) { - Step.BUTTON, Step.DONE -> finish() - Step.ALLOW -> if (askOnly) finish() else go(Step.ACTION) - Step.PRESS -> if (fromHub) finish() else go(Step.BUTTON) - else -> { step = Step.entries[step.ordinal - 1]; build() } - } + val next = Route.back(step, fromHub, askOnly) + if (next == null) finish() else go(next) } private fun go(next: Step) { @@ -228,8 +237,12 @@ class GuidedSetupActivity : Activity() { go(Step.ALLOW) } } - col.row("A menu of actions", "One press, many choices") { startActivity(MenuEditActivity.intent(this, t)) } + col.row("A menu of actions", "One press, many choices") { + leftForPicker = true + startActivity(MenuEditActivity.intent(this, t)) + } col.row("Advanced", "Every action") { + leftForPicker = true startActivity(ActionPickerActivity.intent(this, t).putExtra(ActionPickerActivity.EXTRA_NO_ASK, true)) } } @@ -316,6 +329,11 @@ class GuidedSetupActivity : Activity() { Channels.safeStart(this, Channels.claimIntent(this, Channel.WALLET)) } } + col.button("Change what it does") { go(Step.ACTION) } + // The on-screen button has one gesture, so there is nothing to change here. + if (keys.singleOrNull() != HwKey.SCREEN) { + col.button("Change how you press it") { go(Step.PRESS) } + } col.button("Set up another button") { keys = emptyList() trigger = null diff --git a/app/src/main/java/dev/equwal/assistkey/setup/Route.kt b/app/src/main/java/dev/equwal/assistkey/setup/Route.kt index 00dbabf..a4e1897 100644 --- a/app/src/main/java/dev/equwal/assistkey/setup/Route.kt +++ b/app/src/main/java/dev/equwal/assistkey/setup/Route.kt @@ -15,6 +15,25 @@ import dev.equwal.assistkey.model.Trigger */ object Route { + /** A step of the guided setup screen. The single definition; the activity uses this one. */ + enum class Step { BUTTON, PRESS, ACTION, ALLOW, DONE } + + /** + * Where hardware Back leads from [step]. Null means leave the screen. + * + * [fromHub] is true when the main screen chose the button, so Back from + * PRESS leaves instead of going to BUTTON. [askOnly] is true when another + * screen made the binding and this screen only asks, so Back from ALLOW + * or DONE leaves instead of going back into the flow. + */ + fun back(step: Step, fromHub: Boolean, askOnly: Boolean): Step? = when (step) { + Step.BUTTON -> null + Step.PRESS -> if (fromHub) null else Step.BUTTON + Step.ACTION -> Step.PRESS + Step.ALLOW -> if (askOnly) null else Step.ACTION + Step.DONE -> if (askOnly) null else Step.ACTION + } + /** One thing the user must allow. The order of the entries is the order to ask in. */ enum class Need(val title: String, val why: String) { KEY_FILTER( diff --git a/app/src/test/java/dev/equwal/assistkey/setup/RouteTest.kt b/app/src/test/java/dev/equwal/assistkey/setup/RouteTest.kt index 884d849..15f6bb2 100644 --- a/app/src/test/java/dev/equwal/assistkey/setup/RouteTest.kt +++ b/app/src/test/java/dev/equwal/assistkey/setup/RouteTest.kt @@ -4,8 +4,10 @@ import dev.equwal.assistkey.model.GestureType import dev.equwal.assistkey.model.HwKey import dev.equwal.assistkey.model.Trigger import dev.equwal.assistkey.setup.Route.Need +import dev.equwal.assistkey.setup.Route.Step import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test @@ -130,4 +132,30 @@ class RouteTest { assertEquals(listOf(Need.ASSISTANT), Route.plan(Trigger(power, GestureType.HOLD), false, fullBuild).needs) assertEquals(Trigger.MAX_TAPS + 1, Route.gestures(power, fullBuild).size) } + + @Test fun `Back on the button step always leaves`() { + assertNull(Route.back(Step.BUTTON, fromHub = false, askOnly = false)) + assertNull(Route.back(Step.BUTTON, fromHub = true, askOnly = true)) + } + + @Test fun `Back on the press step leaves when the hub chose the button, else goes to the button step`() { + assertNull(Route.back(Step.PRESS, fromHub = true, askOnly = false)) + assertEquals(Step.BUTTON, Route.back(Step.PRESS, fromHub = false, askOnly = false)) + } + + @Test fun `Back on the action step always goes to the press step`() { + assertEquals(Step.PRESS, Route.back(Step.ACTION, fromHub = false, askOnly = false)) + assertEquals(Step.PRESS, Route.back(Step.ACTION, fromHub = true, askOnly = true)) + } + + @Test fun `Back on the allow step leaves for an ask-only screen, else goes to the action step`() { + assertNull(Route.back(Step.ALLOW, fromHub = false, askOnly = true)) + assertEquals(Step.ACTION, Route.back(Step.ALLOW, fromHub = false, askOnly = false)) + } + + @Test fun `Back on the done step leaves for an ask-only screen, else goes to the action step to edit again`() { + assertNull(Route.back(Step.DONE, fromHub = false, askOnly = true)) + assertEquals(Step.ACTION, Route.back(Step.DONE, fromHub = false, askOnly = false)) + assertEquals(Step.ACTION, Route.back(Step.DONE, fromHub = true, askOnly = false)) + } }