diff --git a/iosApp/iosApp/Profile/NotificationPreferencesView.swift b/iosApp/iosApp/Profile/NotificationPreferencesView.swift index 95c5a73..698a0d1 100644 --- a/iosApp/iosApp/Profile/NotificationPreferencesView.swift +++ b/iosApp/iosApp/Profile/NotificationPreferencesView.swift @@ -552,8 +552,7 @@ struct NotificationTimePickerRow: View { }, onCancel: { showingTimePicker = false - }, - formatHour: formatHour + } ) } } @@ -563,18 +562,37 @@ struct NotificationTimePickerRow: View { struct TimePickerSheet: View { @State private var selectedHour: Int + @State private var isPresented: Bool = true let onSave: (Int) -> Void let onCancel: () -> Void - let formatHour: (Int) -> String - // Pre-computed hours array to avoid range issues with wheel picker - private let hours: [Int] = Array(0..<24) + // Pre-computed hour labels as a simple struct for stable identity + private struct HourOption: Identifiable { + let id: Int + let label: String - init(selectedHour: Int, onSave: @escaping (Int) -> Void, onCancel: @escaping () -> Void, formatHour: @escaping (Int) -> String) { + var hour: Int { id } + } + + private static let hourOptions: [HourOption] = (0..<24).map { hour in + let label: String + switch hour { + case 0: label = "12:00 AM" + case 1...11: label = "\(hour):00 AM" + case 12: label = "12:00 PM" + default: label = "\(hour - 12):00 PM" + } + return HourOption(id: hour, label: label) + } + + init(selectedHour: Int, onSave: @escaping (Int) -> Void, onCancel: @escaping () -> Void) { _selectedHour = State(initialValue: selectedHour) self.onSave = onSave self.onCancel = onCancel - self.formatHour = formatHour + } + + private func formatHour(_ hour: Int) -> String { + Self.hourOptions.first { $0.hour == hour }?.label ?? "\(hour):00" } var body: some View { @@ -585,14 +603,17 @@ struct TimePickerSheet: View { .foregroundColor(Color.appTextPrimary) .padding(.top) - Picker("Hour", selection: $selectedHour) { - ForEach(hours, id: \.self) { hour in - Text(formatHour(hour)) - .tag(hour) + if isPresented { + Picker("Hour", selection: $selectedHour) { + ForEach(Self.hourOptions) { option in + Text(option.label) + .tag(option.hour) + } } + .pickerStyle(.wheel) + .frame(height: 150) + .id("hourPicker") // Stable identity to prevent view recycling issues } - .pickerStyle(.wheel) - .frame(height: 150) Text("Notifications will be sent at \(formatHour(selectedHour)) in your local timezone") .font(.caption) @@ -609,13 +630,21 @@ struct TimePickerSheet: View { .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { - onCancel() + // Hide picker before dismissing to prevent race condition + isPresented = false + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + onCancel() + } } .foregroundColor(Color.appTextSecondary) } ToolbarItem(placement: .confirmationAction) { Button("Save") { - onSave(selectedHour) + // Hide picker before dismissing to prevent race condition + isPresented = false + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + onSave(selectedHour) + } } .foregroundColor(Color.appPrimary) .fontWeight(.semibold) @@ -623,6 +652,7 @@ struct TimePickerSheet: View { } } .presentationDetents([.medium]) + .interactiveDismissDisabled() // Prevent swipe-to-dismiss which can cause race condition } }