Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 112 additions & 4 deletions SleepFocus/Screens/ProfileScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,9 @@ struct SmartAlarmSettingsScreen: View {
@State private var displayedSmartAlarmSchedule = ""
@State private var smartAlarmDisplayOpacity = 1.0
@State private var pendingSmartAlarmRefresh = false
@State private var showOutOfBoundsAlert = false
@State private var outOfBoundsAlertMessage = ""
@State private var suppressPreferenceOnChange = false

private enum EditorSection {
case sleepGoal
Expand Down Expand Up @@ -427,6 +430,11 @@ struct SmartAlarmSettingsScreen: View {
} message: {
Text("Live Activities are currently disabled. Open Settings to allow lock screen and Dynamic Island smart alarm updates.")
}
.alert("Out of bounds", isPresented: $showOutOfBoundsAlert) {
Button("OK", role: .cancel) { }
} message: {
Text(outOfBoundsAlertMessage)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
Expand Down Expand Up @@ -461,6 +469,7 @@ struct SmartAlarmSettingsScreen: View {
Task { await refreshGuidanceAndScheduleIfNeeded(forceRefresh: true) }
}
.onChange(of: sleepPreferences.smartAlarmWakeTimeMinutes) { _, _ in
guard !suppressPreferenceOnChange else { return }
if sleepPreferences.smartAlarmMode == .wake {
beginIdealBedtimeFetchTransition()
scheduleGuidanceRefreshAndSync()
Expand All @@ -482,6 +491,7 @@ struct SmartAlarmSettingsScreen: View {
}
}
.onChange(of: sleepPreferences.smartAlarmBedtimeMinutes) { _, _ in
guard !suppressPreferenceOnChange else { return }
if sleepPreferences.smartAlarmMode == .bedtime {
beginSmartAlarmFetchTransition()
scheduleGuidanceRefreshAndSync()
Expand All @@ -496,6 +506,7 @@ struct SmartAlarmSettingsScreen: View {
scheduleGuidanceRefreshAndSync()
}
.onChange(of: sleepPreferences.earliestWakeupMinutes) { _, _ in
guard !suppressPreferenceOnChange else { return }
if sleepPreferences.smartAlarmMode == .wake {
beginIdealBedtimeFetchTransition()
} else {
Expand All @@ -504,6 +515,7 @@ struct SmartAlarmSettingsScreen: View {
scheduleGuidanceRefreshAndSync()
}
.onChange(of: sleepPreferences.finalWakeupMinutes) { _, _ in
guard !suppressPreferenceOnChange else { return }
if sleepPreferences.smartAlarmMode == .wake {
beginIdealBedtimeFetchTransition()
} else {
Expand All @@ -513,6 +525,23 @@ struct SmartAlarmSettingsScreen: View {
}
}

private var allowedWakeWindowMinutes: (earliest: Int, latest: Int) {
(earliest: sleepPreferences.earliestWakeupMinutes, latest: sleepPreferences.finalWakeupMinutes)
}

private func normalizedMinutes(_ minutes: Int) -> Int {
((minutes % 1440) + 1440) % 1440
}

private func isWithinAllowedWakeWindow(_ minutes: Int, window: (earliest: Int, latest: Int)) -> Bool {
minutes >= window.earliest && minutes <= window.latest
}

private func presentOutOfBoundsAlert(_ message: String) {
outOfBoundsAlertMessage = message
showOutOfBoundsAlert = true
}

private var inputTimeBinding: Binding<Date> {
Binding(
get: {
Expand All @@ -526,7 +555,21 @@ struct SmartAlarmSettingsScreen: View {
set: { updatedDate in
switch sleepPreferences.smartAlarmMode {
case .wake:
let previousMinutes = sleepPreferences.smartAlarmWakeTimeMinutes
let attemptedMinutes = SleepPreferencesStore.minutesSinceMidnight(from: updatedDate)
let window = allowedWakeWindowMinutes
guard isWithinAllowedWakeWindow(attemptedMinutes, window: window) else {
presentOutOfBoundsAlert("That alarm time (\(sleepPreferences.timeDisplay(for: attemptedMinutes))) is outside your allowed wake window (\(sleepPreferences.timeDisplay(for: window.earliest))–\(sleepPreferences.timeDisplay(for: window.latest))). Resetting to the previous alarm time.")
suppressPreferenceOnChange = true
sleepPreferences.updateSmartAlarmWakeTime(sleepPreferences.date(for: previousMinutes))
suppressPreferenceOnChange = false
return
}
suppressPreferenceOnChange = true
sleepPreferences.updateSmartAlarmWakeTime(updatedDate)
suppressPreferenceOnChange = false
beginIdealBedtimeFetchTransition()
scheduleGuidanceRefreshAndSync()
case .bedtime:
beginSmartAlarmFetchTransition()
sleepPreferences.updateSmartAlarmBedtime(updatedDate)
Expand All @@ -545,24 +588,88 @@ struct SmartAlarmSettingsScreen: View {
private var bedtimeBinding: Binding<Date> {
Binding(
get: { sleepPreferences.date(for: sleepPreferences.smartAlarmBedtimeMinutes) },
set: {
set: { updatedDate in
let previousBedtimeMinutes = sleepPreferences.smartAlarmBedtimeMinutes

suppressPreferenceOnChange = true
sleepPreferences.updateSmartAlarmBedtime(updatedDate)
suppressPreferenceOnChange = false

let recommendedWakeMinutes = normalizedMinutes(sleepPreferences.smartAlarmWakeTimeMinutes)
let window = allowedWakeWindowMinutes
guard isWithinAllowedWakeWindow(recommendedWakeMinutes, window: window) else {
presentOutOfBoundsAlert("That bedtime would recommend a wake time (\(sleepPreferences.timeDisplay(for: recommendedWakeMinutes))) outside your allowed wake window (\(sleepPreferences.timeDisplay(for: window.earliest))–\(sleepPreferences.timeDisplay(for: window.latest))). Resetting to the previous bedtime.")
suppressPreferenceOnChange = true
sleepPreferences.updateSmartAlarmBedtime(sleepPreferences.date(for: previousBedtimeMinutes))
suppressPreferenceOnChange = false
return
}

beginSmartAlarmFetchTransition()
sleepPreferences.updateSmartAlarmBedtime($0)
scheduleGuidanceRefreshAndSync()
}
)
}

private var earliestWakeBinding: Binding<Date> {
Binding(
get: { sleepPreferences.date(for: sleepPreferences.earliestWakeupMinutes) },
set: { sleepPreferences.updateEarliestWakeup($0) }
set: { updatedDate in
let previousMinutes = sleepPreferences.earliestWakeupMinutes
suppressPreferenceOnChange = true
sleepPreferences.updateEarliestWakeup(updatedDate)
suppressPreferenceOnChange = false

let window = allowedWakeWindowMinutes
if sleepPreferences.smartAlarmEnabled {
let currentWakeMinutes = normalizedMinutes(sleepPreferences.smartAlarmWakeTimeMinutes)
if !isWithinAllowedWakeWindow(currentWakeMinutes, window: window) {
presentOutOfBoundsAlert("That wake-window change would put your next alarm (\(sleepPreferences.timeDisplay(for: currentWakeMinutes))) outside the allowed wake window (\(sleepPreferences.timeDisplay(for: window.earliest))–\(sleepPreferences.timeDisplay(for: window.latest))). Resetting to the previous window.")
suppressPreferenceOnChange = true
sleepPreferences.updateEarliestWakeup(sleepPreferences.date(for: previousMinutes))
suppressPreferenceOnChange = false
return
}
}

if sleepPreferences.smartAlarmMode == .wake {
beginIdealBedtimeFetchTransition()
} else {
beginSmartAlarmFetchTransition()
}
scheduleGuidanceRefreshAndSync()
}
)
}

private var finalWakeBinding: Binding<Date> {
Binding(
get: { sleepPreferences.date(for: sleepPreferences.finalWakeupMinutes) },
set: { sleepPreferences.updateFinalWakeup($0) }
set: { updatedDate in
let previousMinutes = sleepPreferences.finalWakeupMinutes
suppressPreferenceOnChange = true
sleepPreferences.updateFinalWakeup(updatedDate)
suppressPreferenceOnChange = false

let window = allowedWakeWindowMinutes
if sleepPreferences.smartAlarmEnabled {
let currentWakeMinutes = normalizedMinutes(sleepPreferences.smartAlarmWakeTimeMinutes)
if !isWithinAllowedWakeWindow(currentWakeMinutes, window: window) {
presentOutOfBoundsAlert("That wake-window change would put your next alarm (\(sleepPreferences.timeDisplay(for: currentWakeMinutes))) outside the allowed wake window (\(sleepPreferences.timeDisplay(for: window.earliest))–\(sleepPreferences.timeDisplay(for: window.latest))). Resetting to the previous window.")
suppressPreferenceOnChange = true
sleepPreferences.updateFinalWakeup(sleepPreferences.date(for: previousMinutes))
suppressPreferenceOnChange = false
return
}
}

if sleepPreferences.smartAlarmMode == .wake {
beginIdealBedtimeFetchTransition()
} else {
beginSmartAlarmFetchTransition()
}
scheduleGuidanceRefreshAndSync()
}
)
}

Expand Down Expand Up @@ -770,6 +877,7 @@ struct SmartAlarmSettingsScreen: View {
.font(.system(size: 13, weight: .semibold))
.foregroundColor(.appSecondaryText)


// if let compensationText = sleepPreferences.smartAlarmSleepGoalCompensationText {
// Text(compensationText)
// .font(.system(size: 12))
Expand Down
Loading