Skip to content

Commit 46e98de

Browse files
authored
Fix alignment on Snooze button when using iOS Display Zoom (#482)
* Fix alignment on Snooze button when using iOS Display Zoom * Auto-hide Snooze all button after alarm
1 parent 45dd956 commit 46e98de

File tree

1 file changed

+71
-50
lines changed

1 file changed

+71
-50
lines changed

LoopFollow/Snoozer/SnoozerView.swift

Lines changed: 71 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)