diff --git a/DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift b/DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift index a2942b8..9979086 100644 --- a/DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift +++ b/DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift @@ -10,7 +10,8 @@ import Foundation final class PushNotificationSettingsViewModel: Store { struct State { var pushNotificationEnable: Bool = false - var pushNotificationTime: Date = .init() + var viewPushNotificationTime: Date = .init() + var sheetPushNotificationTime: Date = .init() var showTimePicker: Bool = false var isLoading: Bool = false var sheetHeight: CGFloat = .pi @@ -19,22 +20,24 @@ final class PushNotificationSettingsViewModel: Store { var alertTitle: String = "" var alertMessage: String = "" var pushNotificationHour: Int { - Calendar.current.component(.hour, from: pushNotificationTime) + Calendar.current.component(.hour, from: viewPushNotificationTime) } var pushNotificationMinute: Int { - Calendar.current.component(.minute, from: pushNotificationTime) + Calendar.current.component(.minute, from: viewPushNotificationTime) } } enum Action { - case onAppear + case fetchSettings case setAlert(Bool) case setLoading(Bool) case setPushNotificationEnable(Bool) - case setPushNotificationHour(Int) - case setPushNotificationTime(Date) + case setPushNotificationTime(view: Date? = nil, sheet: Date? = nil) case setShowTimePicker(Bool) case setSheetHeight(CGFloat) + case selectPresetTime(Date) + case confirmUpdate + case rollbackUpdate } enum SideEffect { @@ -42,8 +45,8 @@ final class PushNotificationSettingsViewModel: Store { case updatePushNotificationSettings } - private let calendar = Calendar.current @Published private(set) var state: State = .init() + private let calendar = Calendar.current private let fetchPushSettingsUseCase: FetchPushSettingsUseCase private let updatePushSettingsUseCase: UpdatePushSettingsUseCase @@ -57,36 +60,45 @@ final class PushNotificationSettingsViewModel: Store { func reduce(with action: Action) -> [SideEffect] { var state = self.state + var effects: [SideEffect] = [] switch action { - case .onAppear: - return [.fetchPushNotificationSettings] + case .fetchSettings: + effects = [.fetchPushNotificationSettings] case .setAlert(let isPresented): setAlert(&state, isPresented: isPresented) case .setLoading(let value): state.isLoading = value case .setPushNotificationEnable(let value): - self.state.pushNotificationEnable = value - return [.updatePushNotificationSettings] - case .setPushNotificationHour(let value): - // 시간만 변경 - if let newDate = calendar.date( - bySettingHour: value, - minute: 0, second: 0, - of: state.pushNotificationTime - ) { - self.state.pushNotificationTime = newDate - return [.updatePushNotificationSettings] + state.pushNotificationEnable = value + effects = [.updatePushNotificationSettings] + case .setPushNotificationTime(let view, let sheet): + if let value = view { + state.viewPushNotificationTime = value + } + if let value = sheet { + state.sheetPushNotificationTime = value } - case .setPushNotificationTime(let value): - self.state.pushNotificationTime = value - return [.updatePushNotificationSettings] case .setShowTimePicker(let value): state.showTimePicker = value + if !value { + state.sheetPushNotificationTime = state.viewPushNotificationTime + } case .setSheetHeight(let value): state.sheetHeight = value + case .selectPresetTime(let date): + state.viewPushNotificationTime = date + state.sheetPushNotificationTime = date + effects = [.updatePushNotificationSettings] + case .confirmUpdate: + state.showTimePicker = false + state.viewPushNotificationTime = state.sheetPushNotificationTime + effects = [.updatePushNotificationSettings] + case .rollbackUpdate: + state.showTimePicker = false + state.sheetPushNotificationTime = state.viewPushNotificationTime } self.state = state - return [] + return effects } func run(_ effect: SideEffect) { @@ -101,7 +113,7 @@ final class PushNotificationSettingsViewModel: Store { if let hour = settings.scheduledTime.hour, let minute = settings.scheduledTime.minute, let date = calendar.date(bySettingHour: hour, minute: minute, second: 0, of: Date()) { - self.send(.setPushNotificationTime(date)) + self.send(.setPushNotificationTime(view: date, sheet: date)) } } catch { send(.setAlert(true)) @@ -112,7 +124,10 @@ final class PushNotificationSettingsViewModel: Store { do { defer { send(.setLoading(false)) } send(.setLoading(true)) - let dateComponents = calendar.dateComponents([.hour, .minute], from: state.pushNotificationTime) + let dateComponents = calendar.dateComponents( + [.hour, .minute], + from: state.sheetPushNotificationTime + ) let settings = PushNotificationSettings( isEnabled: state.pushNotificationEnable, scheduledTime: dateComponents @@ -120,6 +135,7 @@ final class PushNotificationSettingsViewModel: Store { try await updatePushSettingsUseCase.execute(settings) } catch { send(.setAlert(true)) + send(.fetchSettings) } } } diff --git a/DevLog/UI/Setting/PushNotificationSettingsView.swift b/DevLog/UI/Setting/PushNotificationSettingsView.swift index 9231a64..181ed29 100644 --- a/DevLog/UI/Setting/PushNotificationSettingsView.swift +++ b/DevLog/UI/Setting/PushNotificationSettingsView.swift @@ -37,14 +37,14 @@ struct PushNotificationSettingsView: View { } .contentShape(Rectangle()) .onTapGesture { - viewModel.send(.setPushNotificationHour(hour)) + viewModel.send(.selectPresetTime(date)) } } } HStack { Text("사용자 설정") Spacer() - Text(formattedTimeString(viewModel.state.pushNotificationTime)) + Text(formattedTimeString(viewModel.state.viewPushNotificationTime)) .foregroundStyle(.secondary) if viewModel.state.pushNotificationMinute != 0 { Image(systemName: "checkmark") @@ -67,37 +67,70 @@ struct PushNotificationSettingsView: View { } } .onAppear { - viewModel.send(.onAppear) + viewModel.send(.fetchSettings) } .sheet(isPresented: Binding( get: { viewModel.state.showTimePicker }, - set: { _ in viewModel.send(.setShowTimePicker(false)) } + set: { viewModel.send(.setShowTimePicker($0)) } )) { - DatePicker( - "", - selection: Binding( - get: { viewModel.state.pushNotificationTime }, - set: { viewModel.send(.setPushNotificationTime($0)) } - ), - displayedComponents: .hourAndMinute - ) - .datePickerStyle(.wheel) - .labelsHidden() - .presentationDragIndicator(.hidden) - .presentationDetents([.height(viewModel.state.sheetHeight)]) - .onAppear { - UIDatePicker.appearance().minuteInterval = 5 + NavigationStack { + DatePicker( + "", + selection: Binding( + get: { viewModel.state.sheetPushNotificationTime }, + set: { viewModel.send(.setPushNotificationTime(sheet: $0)) } + ), + displayedComponents: .hourAndMinute + ) + .datePickerStyle(.wheel) + .labelsHidden() + .presentationDragIndicator(.hidden) + .presentationDetents([.height(viewModel.state.sheetHeight)]) + .onAppear { UIDatePicker.appearance().minuteInterval = 5 } + .onDisappear { UIDatePicker.appearance().minuteInterval = 1 /* 기본값으로 복원 */ } + .toolbar { toolbar } + .background( + GeometryReader { geometry in + Color.clear.onAppear { + viewModel.send(.setSheetHeight(geometry.size.height)) + } + } + ) } - .onDisappear { - UIDatePicker.appearance().minuteInterval = 1 // 기본값으로 복원 + } + } + + @ToolbarContentBuilder + private var toolbar: some ToolbarContent { + if #available(iOS 26.0, *) { + ToolbarItem(placement: .topBarLeading) { + Button(role: .cancel) { + viewModel.send(.rollbackUpdate) + } } - .background( - GeometryReader { geometry in - Color.clear.onAppear { - viewModel.send(.setSheetHeight(geometry.size.height)) - } + + ToolbarItem(placement: .topBarTrailing) { + Button(role: .confirm) { + viewModel.send(.confirmUpdate) + } + } + } else { + ToolbarItem(placement: .topBarLeading) { + Button { + viewModel.send(.rollbackUpdate) + } label: { + Text("취소") } - ) + } + + ToolbarItem(placement: .topBarTrailing) { + Button { + viewModel.send(.confirmUpdate) + } label: { + Text("확인") + .bold() + } + } } }