Skip to content

Show the custom sleep timer as a popover - #1638

Merged
GianniCarlo merged 2 commits into
developfrom
feat/sleep-timer-popover
Oct 11, 2026
Merged

GianniCarlo merged 2 commits into
developfrom
feat/sleep-timer-popover

Conversation

@GianniCarlo

Copy link
Copy Markdown
Collaborator

Problem

The custom sleep timer was a bottom sheet fixed at 400pt (.presentationDetents([.height(400)])). With the largest text sizes its content outgrew that: in portrait the title ran past the sheet's top edge, and in landscape (where the sheet goes full screen, 402pt tall) the content needed about 474pt, so the title and Cancel were cut off with nothing to scroll. On iPad, and the open iPhone Duo, it showed as a card in the middle of the screen.

Changes

  • DurationPickerPopover (was DurationPickerSheet): a popover from the sleep timer button. A popover sizes itself to its content, so it grows with the text instead of clipping; on iPad and iPhone Duo it opens next to the button. presentationCompactAdaptation(.popover) keeps it a popover on iPhone.
    • Same title and the same wheels (NativeDurationPicker). The native countdown picker (UIDatePicker) isn't used: it was replaced on purpose in bcf215f for themes and the Mac.
    • OK is a tinted capsule (.glassProminent, the theme's link color). Tapping outside cancels, so Cancel goes.
    • OK is disabled at 0 hours 0 minutes, which used to start a timer with nothing to count.
    • The wheels' height is a maximum (200pt), not fixed: they're shorter in landscape, and a fixed frame left a gap that pushed OK against the popover's edge.
  • MediaActionRow: shows a popover from the sleep timer button (showsTimerPopover + a timerPopover builder).
  • PlayerViewModel / PlayerView: the Custom option sets showsCustomSleepTimer; the .sleep sheet style is gone.

Testing

  • Simulators, iPhone 17 Pro and iPad Air 11", iOS 27, portrait and landscape: Custom opens the popover from the sleep menu, OK starts the timer (the moon shows the countdown), tapping outside leaves the timer alone, the last duration is remembered, and the popover's elements are in the accessibility hierarchy.
  • Largest accessibility text size (iPhone): everything fits in both orientations; the title wraps to two lines in portrait, and there's 16pt of padding around OK.
  • Device: iPhone 17.

The custom sleep timer was a bottom sheet fixed at 400pt. With the largest
text sizes its content outgrew that: the title ran past the top edge, and in
landscape, where the sheet goes full screen, the title and Cancel were cut
off with nothing to scroll.

It's now a popover from the sleep timer button. A popover sizes itself to
its content, so it grows with the text instead of clipping, and on iPad and
iPhone Duo it opens next to the button instead of as a card in the middle
of the screen. It keeps the title and the same wheels (the native countdown
picker was replaced on purpose for themes and the Mac), with a tinted
capsule OK; tapping outside cancels, so Cancel goes. OK is disabled at
0 hours 0 minutes, which used to start a timer with nothing to count.
@github-actions

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

✅ Claude PR Review — PASS

Replaces the fixed-height custom sleep timer sheet with DurationPickerPopover, a popover anchored to the sleep timer button (MediaActionRow gains a showsTimerPopover binding + timerPopover builder; PlayerViewModel drops the .sleep sheet style for showsCustomSleepTimer). This is view-layer only: no concurrency, CoreData/SwiftData, player/AVAudioSession, or auth/entitlement code changes. The deployment target is iOS 26, so .glassProminent and presentationCompactAdaptation need no availability guards. Spacing comes from BookPlayerKit, so the new import respects the framework boundary, and the per-item popover binding uses a ternary rather than an if inside the ForEach. The custom duration is still stored in UserDefaults.standard, but that code was only moved and only the app reads it. No BookPlayerUITests identifiers or strings are affected.

Findings: 1 info

Findings not visible inline (no line in this diff, beyond the 25-comment cap, a comment the API refused, on a thread that could not be reopened, or on one a maintainer had the last word on)
  • 🔵 BookPlayer/Player/Views/DurationPickerPopover.swift:171 — NativeDurationPicker shows whole hours and minutes, but selectedDuration is a raw TimeInterval. A saved value between 1 and 59 seconds would show as 0 h 0 m while OK stays enabled, and it would start a timer of under a minute. Values written by this UI are always multiples of 60, so this is only an edge case. Comparing against what the wheels can show is more robust:
.disabled(selectedDuration < 60)

(and use the same check in init: initialDuration >= 60 ? initialDuration : 3600).

Model claude-opus-5-5 · run log · 0 new · 0 carried over · 0 resolved · advisory (a human should still review). Findings are de-duplicated across pushes; an earlier finding closes only when the verification pass judges it against the current code — fixed, no longer applicable, accepted by a maintainer, or a duplicate of a finding reported on this push.

Nothing is stored until a custom timer is set once, so the popover opened
at 0 hours 0 minutes, where OK is now disabled: you had to scroll a wheel
before anything worked. It now starts at an hour in that case, and also when
the old sheet stored 0.
@GianniCarlo
GianniCarlo merged commit 68296a2 into develop Oct 11, 2026
3 checks passed
@GianniCarlo
GianniCarlo deleted the feat/sleep-timer-popover branch October 11, 2026 22:55

This branch was successfully deployed

1 active deployment
reviewer — b8ac5308 Deployed Oct 11, 2026 by GianniCarlo via review #253
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant