@@ -74,8 +74,11 @@ struct SnoozerView: View {
7474 if alarm != nil {
7575 showSnoozerBar = true
7676 cancelAutoHide ( )
77- } else if !isGlobalSnoozeActive {
78- scheduleAutoHide ( )
77+ } else {
78+ // When alarm is dismissed, schedule auto-hide if no global snooze is active
79+ if !isGlobalSnoozeActive {
80+ scheduleAutoHide ( )
81+ }
7982 }
8083 }
8184 . onChange ( of: isGlobalSnoozeActive) { active in
@@ -95,6 +98,56 @@ struct SnoozerView: View {
9598 . transition ( . move( edge: . top) . combined ( with: . opacity) )
9699 }
97100 }
101+ . overlay ( alignment: . bottom) {
102+ if let alarm = vm. activeAlarm {
103+ VStack ( spacing: 16 ) {
104+ // Alarm name at the top
105+ Text ( alarm. name)
106+ . font ( . system( size: 30 , weight: . semibold) )
107+ . foregroundColor ( . white)
108+ . lineLimit ( 1 )
109+ . minimumScaleFactor ( 0.5 )
110+ . padding ( . top, 20 )
111+
112+ Divider ( )
113+
114+ // Snooze duration controls
115+ if alarm. type. snoozeTimeUnit != . none {
116+ HStack {
117+ VStack ( alignment: . leading, spacing: 4 ) {
118+ Text ( " Snooze for " )
119+ . font ( . headline)
120+ Text ( " \( vm. snoozeUnits) \( vm. timeUnitLabel) " )
121+ . font ( . title3) . bold ( )
122+ }
123+ Spacer ( )
124+ Stepper ( " " , value: $vm. snoozeUnits,
125+ in: alarm. type. snoozeRange,
126+ step: alarm. type. snoozeStep)
127+ . labelsHidden ( )
128+ }
129+ . padding ( . horizontal, 24 )
130+ }
131+
132+ // Snooze button anchored to tab bar edge (bottom of VStack)
133+ Button ( action: vm. snoozeTapped) {
134+ Text ( vm. snoozeUnits == 0 ? " Acknowledge " : " Snooze " )
135+ . font ( . system( size: 30 , weight: . bold) )
136+ . frame ( maxWidth: . infinity, minHeight: 60 )
137+ . background ( Color . orange)
138+ . foregroundColor ( . white)
139+ . clipShape ( Capsule ( ) )
140+ }
141+ . padding ( . horizontal, 24 )
142+ . padding ( . bottom, 20 )
143+ }
144+ . background ( . ultraThinMaterial)
145+ . cornerRadius ( 20 , corners: [ . topLeft, . topRight] )
146+ . transition ( . move( edge: . bottom) . combined ( with: . opacity) )
147+ . animation ( . spring( ) , value: vm. activeAlarm != nil )
148+ . padding ( . bottom, 0 ) // Anchor directly to bottom edge
149+ }
150+ }
98151 . sheet ( isPresented: $showDatePickerDate) { datePickerSheetDate ( ) }
99152 . sheet ( isPresented: $showDatePickerTime) { datePickerSheetTime ( ) }
100153 }
@@ -171,66 +224,32 @@ struct SnoozerView: View {
171224 . padding ( . bottom, 8 )
172225 }
173226
174- if let alarm = vm. activeAlarm {
175- VStack ( spacing: 16 ) {
176- Text ( alarm. name)
177- . font ( . system( size: 30 , weight: . semibold) )
178- . foregroundColor ( . white)
179- . lineLimit ( 1 )
180- . minimumScaleFactor ( 0.5 )
181- . padding ( . top, 20 )
182- Divider ( )
183-
184- if alarm. type. snoozeTimeUnit != . none {
185- HStack {
186- VStack ( alignment: . leading, spacing: 4 ) {
187- Text ( " Snooze for " )
188- . font ( . headline)
189- Text ( " \( vm. snoozeUnits) \( vm. timeUnitLabel) " )
190- . font ( . title3) . bold ( )
191- }
192- Spacer ( )
193- Stepper ( " " , value: $vm. snoozeUnits,
194- in: alarm. type. snoozeRange,
195- step: alarm. type. snoozeStep)
196- . labelsHidden ( )
197- }
198- . padding ( . horizontal, 24 )
199- }
227+ if snoozerEmoji. value {
228+ TimelineView ( . periodic( from: . now, by: 1 ) ) { context in
229+ VStack ( spacing: 4 ) {
230+ Text ( bgEmoji)
231+ . font ( . system( size: 128 ) )
232+ . minimumScaleFactor ( 0.5 )
200233
201- Button ( action: vm. snoozeTapped) {
202- Text ( vm. snoozeUnits == 0 ? " Acknowledge " : " Snooze " )
203- . font ( . system( size: 30 , weight: . bold) )
204- . frame ( maxWidth: . infinity, minHeight: 60 )
205- . background ( Color . orange)
234+ Text ( context. date, format: Date . FormatStyle ( date: . omitted, time: . shortened) )
235+ . font ( . system( size: 70 ) )
236+ . minimumScaleFactor ( 0.5 )
206237 . foregroundColor ( . white)
207- . clipShape ( Capsule ( ) )
238+ . frame ( height : 78 )
208239 }
209- . padding ( . horizontal, 24 )
210- . padding ( . bottom, 20 )
211240 }
212- . background ( . ultraThinMaterial)
213- . cornerRadius ( 20 , corners: [ . topLeft, . topRight] )
214- . transition ( . move( edge: . bottom) . combined ( with: . opacity) )
215- . animation ( . spring( ) , value: vm. activeAlarm != nil )
216241 } else {
217242 TimelineView ( . periodic( from: . now, by: 1 ) ) { context in
218243 VStack ( spacing: 4 ) {
219- if snoozerEmoji. value {
220- Text ( bgEmoji)
221- . font ( . system( size: 128 ) )
222- . minimumScaleFactor ( 0.5 )
223- }
224-
225244 Text ( context. date, format: Date . FormatStyle ( date: . omitted, time: . shortened) )
226245 . font ( . system( size: 70 ) )
227246 . minimumScaleFactor ( 0.5 )
228247 . foregroundColor ( . white)
229248 . frame ( height: 78 )
230249 }
231250 }
232- Spacer ( )
233251 }
252+ Spacer ( )
234253 }
235254 }
236255
@@ -471,10 +490,12 @@ struct SnoozerView: View {
471490
472491 private func scheduleAutoHide( ) {
473492 cancelAutoHide ( )
474- if isGlobalSnoozeActive || vm. activeAlarm != nil { return }
493+ // Always schedule the task - it will check conditions when it executes
494+ // This ensures auto-hide works even if conditions change between scheduling and execution
475495 let task = DispatchWorkItem {
476- if !isGlobalSnoozeActive && vm. activeAlarm == nil {
477- withAnimation { showSnoozerBar = false }
496+ // Only hide if neither global snooze nor active alarm exists
497+ if !self . isGlobalSnoozeActive && self . vm. activeAlarm == nil {
498+ withAnimation { self . showSnoozerBar = false }
478499 }
479500 }
480501 autoHideTask = task
0 commit comments