feat(timers): redo the add-timer flow as a full-screen calculator keypad
The Timers tab gets a full rewrite of how a timer is created, after Google Clock as the interaction reference (PLAN.md §11): - A dedicated TimerSetupScreen replaces the bottom-sheet/inline setup panel: a per-unit "00h 00m 00s" readout, a "00" key, and a big centred play button that never shifts position when Clear fades in beside it. - The keypad grows into the thumb zone on its own screen (92dp keys, pushed toward the bottom), and both it and the readout read bold. - Presets are dropped from the flow; TimerDurationEntry gains the matching domain support (hoursSegment/minutesSegment/secondsSegment, plusDoubleZero()). - An empty timer list shows the same readout/keypad/Start inline, in place of a "tap + to add" card — the keypad is still the empty state, just the redesigned one. - Timer cards are disconnected like the Alarms tab's own list, each with an instant, unconfirmed delete via a small corner button (re-creating a timer is faster than reading a confirmation dialog would be). - Hero readouts go bold app-wide to match: stopwatch, alarm rows, world clock, the ring screen, and both editors' length/time displays (ClockulaReadoutDefaults.Hero and matching call sites). Alongside: Android 16 Live Update support for the running-timer notification (POST_PROMOTED_NOTIFICATIONS, ProgressStyle), the live pill restyled to a plain tonal circle, the alarm dismiss-chooser dialog moved onto Floret's OptionCard, and small fixes to addTime's zero/negative-result handling, repeat-day selection and alarm routes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -122,7 +122,7 @@ class AlarmsInstrumentedTest {
|
||||
fun theFabCreatesAnAlarmAndOpensItsEditor() {
|
||||
openEditorForNewAlarm()
|
||||
|
||||
composeTestRule.onNodeWithText(string(R.string.editor_title)).assertIsDisplayed()
|
||||
composeTestRule.onNodeWithContentDescription("hour", substring = true).assertIsDisplayed()
|
||||
assertThat(runBlocking { seams.alarms().alarms().first() }).hasSize(1)
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ class AlarmsInstrumentedTest {
|
||||
composeTestRule.onNodeWithText("Work").performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
|
||||
composeTestRule.onNodeWithText(string(R.string.editor_delete)).performClick()
|
||||
composeTestRule.onNodeWithContentDescription(string(R.string.editor_delete)).performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
composeTestRule.onNodeWithText(string(R.string.action_delete)).performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
|
||||
@@ -105,14 +105,14 @@ class StopwatchInstrumentedTest {
|
||||
|
||||
/** §5.14 #2 */
|
||||
@Test
|
||||
fun twoTapsOnLapAppendTwoRowsAndLeaveAThirdInProgress() {
|
||||
fun newLapsStayVisibleAtTheTopOfTheList() {
|
||||
openStopwatchTab()
|
||||
|
||||
startAndLap(times = 2)
|
||||
startAndLap(times = 4)
|
||||
|
||||
assertThat(storedLaps().map { it.index }).containsExactly(1, 2).inOrder()
|
||||
assertThat(storedLaps().map { it.index }).containsExactly(1, 2, 3, 4).inOrder()
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(context.getString(R.string.stopwatch_lap_number, 3), substring = true)
|
||||
.onNodeWithContentDescription(context.getString(R.string.stopwatch_lap_number, 5), substring = true)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Intent
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.v2.createEmptyComposeRule
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.lifecycle.Lifecycle
|
||||
@@ -96,10 +97,13 @@ class TimersInstrumentedTest {
|
||||
/** §5.19 #1 */
|
||||
@Test
|
||||
fun typingOnTheKeypadAndPressingStartCreatesOneRunningTimer() {
|
||||
// The list starts empty (teardown clears it), so the keypad is
|
||||
// already inline — no FAB tap needed to reach it.
|
||||
openTimersTab()
|
||||
|
||||
typeDigits('1', '3', '0')
|
||||
composeTestRule.onNodeWithText(string(R.string.timers_setup_start)).performClick()
|
||||
// The play button is icon-only; "Start" is its content description.
|
||||
composeTestRule.onNodeWithContentDescription(string(R.string.timers_setup_start)).performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
|
||||
val timers = stored()
|
||||
@@ -116,7 +120,9 @@ class TimersInstrumentedTest {
|
||||
launched.recreate()
|
||||
composeTestRule.waitForIdle()
|
||||
|
||||
composeTestRule.onNodeWithText("0:01:30").assertIsDisplayed()
|
||||
// The readout is now three segments ("00h 01m 30s"); its merged
|
||||
// semantics still carry the spoken "0:01:30" form (`TimerDurationReadout`).
|
||||
composeTestRule.onNodeWithContentDescription("0:01:30").assertIsDisplayed()
|
||||
}
|
||||
|
||||
/** §5.19 #3 */
|
||||
|
||||
Reference in New Issue
Block a user