Skip to content

Commit 0a9194c

Browse files
authored
[#134] 사용자 설정으로 시간을 바꿀 때 확인 버튼을 탭하면 서버에 변동사항이 저장되도록 개선한다 (#139)
* ui: 시트 상단에 툴바로 취소, 확인 버튼 추가 * feat: 시트 내 변동 값으로 서버에 업데이트 하고, 시트가 닫힐 때 적절한 값으로 부모 뷰 값 업데이트 * fix: 시트 외부를 탭 해서 내려갔을 때 원복되지 않는 현상 해결 * fix: 서버에 업데이트하지 않는 현상 해결 * feat: 업데이트가 실패했을 경우 서버값으로 오버라이드 * refactor: 불필요 액션 제거 * refactor: 뷰가 뷰모델 액션을 덜 알도록 개선
1 parent aef3d86 commit 0a9194c

2 files changed

Lines changed: 101 additions & 52 deletions

File tree

DevLog/Presentation/ViewModel/PushNotificationSettingsViewModel.swift

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import Foundation
1010
final class PushNotificationSettingsViewModel: Store {
1111
struct State {
1212
var pushNotificationEnable: Bool = false
13-
var pushNotificationTime: Date = .init()
13+
var viewPushNotificationTime: Date = .init()
14+
var sheetPushNotificationTime: Date = .init()
1415
var showTimePicker: Bool = false
1516
var isLoading: Bool = false
1617
var sheetHeight: CGFloat = .pi
@@ -19,31 +20,33 @@ final class PushNotificationSettingsViewModel: Store {
1920
var alertTitle: String = ""
2021
var alertMessage: String = ""
2122
var pushNotificationHour: Int {
22-
Calendar.current.component(.hour, from: pushNotificationTime)
23+
Calendar.current.component(.hour, from: viewPushNotificationTime)
2324
}
2425
var pushNotificationMinute: Int {
25-
Calendar.current.component(.minute, from: pushNotificationTime)
26+
Calendar.current.component(.minute, from: viewPushNotificationTime)
2627
}
2728
}
2829

2930
enum Action {
30-
case onAppear
31+
case fetchSettings
3132
case setAlert(Bool)
3233
case setLoading(Bool)
3334
case setPushNotificationEnable(Bool)
34-
case setPushNotificationHour(Int)
35-
case setPushNotificationTime(Date)
35+
case setPushNotificationTime(view: Date? = nil, sheet: Date? = nil)
3636
case setShowTimePicker(Bool)
3737
case setSheetHeight(CGFloat)
38+
case selectPresetTime(Date)
39+
case confirmUpdate
40+
case rollbackUpdate
3841
}
3942

4043
enum SideEffect {
4144
case fetchPushNotificationSettings
4245
case updatePushNotificationSettings
4346
}
4447

45-
private let calendar = Calendar.current
4648
@Published private(set) var state: State = .init()
49+
private let calendar = Calendar.current
4750
private let fetchPushSettingsUseCase: FetchPushSettingsUseCase
4851
private let updatePushSettingsUseCase: UpdatePushSettingsUseCase
4952

@@ -57,36 +60,45 @@ final class PushNotificationSettingsViewModel: Store {
5760

5861
func reduce(with action: Action) -> [SideEffect] {
5962
var state = self.state
63+
var effects: [SideEffect] = []
6064
switch action {
61-
case .onAppear:
62-
return [.fetchPushNotificationSettings]
65+
case .fetchSettings:
66+
effects = [.fetchPushNotificationSettings]
6367
case .setAlert(let isPresented):
6468
setAlert(&state, isPresented: isPresented)
6569
case .setLoading(let value):
6670
state.isLoading = value
6771
case .setPushNotificationEnable(let value):
68-
self.state.pushNotificationEnable = value
69-
return [.updatePushNotificationSettings]
70-
case .setPushNotificationHour(let value):
71-
// 시간만 변경
72-
if let newDate = calendar.date(
73-
bySettingHour: value,
74-
minute: 0, second: 0,
75-
of: state.pushNotificationTime
76-
) {
77-
self.state.pushNotificationTime = newDate
78-
return [.updatePushNotificationSettings]
72+
state.pushNotificationEnable = value
73+
effects = [.updatePushNotificationSettings]
74+
case .setPushNotificationTime(let view, let sheet):
75+
if let value = view {
76+
state.viewPushNotificationTime = value
77+
}
78+
if let value = sheet {
79+
state.sheetPushNotificationTime = value
7980
}
80-
case .setPushNotificationTime(let value):
81-
self.state.pushNotificationTime = value
82-
return [.updatePushNotificationSettings]
8381
case .setShowTimePicker(let value):
8482
state.showTimePicker = value
83+
if !value {
84+
state.sheetPushNotificationTime = state.viewPushNotificationTime
85+
}
8586
case .setSheetHeight(let value):
8687
state.sheetHeight = value
88+
case .selectPresetTime(let date):
89+
state.viewPushNotificationTime = date
90+
state.sheetPushNotificationTime = date
91+
effects = [.updatePushNotificationSettings]
92+
case .confirmUpdate:
93+
state.showTimePicker = false
94+
state.viewPushNotificationTime = state.sheetPushNotificationTime
95+
effects = [.updatePushNotificationSettings]
96+
case .rollbackUpdate:
97+
state.showTimePicker = false
98+
state.sheetPushNotificationTime = state.viewPushNotificationTime
8799
}
88100
self.state = state
89-
return []
101+
return effects
90102
}
91103

92104
func run(_ effect: SideEffect) {
@@ -101,7 +113,7 @@ final class PushNotificationSettingsViewModel: Store {
101113
if let hour = settings.scheduledTime.hour,
102114
let minute = settings.scheduledTime.minute,
103115
let date = calendar.date(bySettingHour: hour, minute: minute, second: 0, of: Date()) {
104-
self.send(.setPushNotificationTime(date))
116+
self.send(.setPushNotificationTime(view: date, sheet: date))
105117
}
106118
} catch {
107119
send(.setAlert(true))
@@ -112,14 +124,18 @@ final class PushNotificationSettingsViewModel: Store {
112124
do {
113125
defer { send(.setLoading(false)) }
114126
send(.setLoading(true))
115-
let dateComponents = calendar.dateComponents([.hour, .minute], from: state.pushNotificationTime)
127+
let dateComponents = calendar.dateComponents(
128+
[.hour, .minute],
129+
from: state.sheetPushNotificationTime
130+
)
116131
let settings = PushNotificationSettings(
117132
isEnabled: state.pushNotificationEnable,
118133
scheduledTime: dateComponents
119134
)
120135
try await updatePushSettingsUseCase.execute(settings)
121136
} catch {
122137
send(.setAlert(true))
138+
send(.fetchSettings)
123139
}
124140
}
125141
}

DevLog/UI/Setting/PushNotificationSettingsView.swift

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ struct PushNotificationSettingsView: View {
3737
}
3838
.contentShape(Rectangle())
3939
.onTapGesture {
40-
viewModel.send(.setPushNotificationHour(hour))
40+
viewModel.send(.selectPresetTime(date))
4141
}
4242
}
4343
}
4444
HStack {
4545
Text("사용자 설정")
4646
Spacer()
47-
Text(formattedTimeString(viewModel.state.pushNotificationTime))
47+
Text(formattedTimeString(viewModel.state.viewPushNotificationTime))
4848
.foregroundStyle(.secondary)
4949
if viewModel.state.pushNotificationMinute != 0 {
5050
Image(systemName: "checkmark")
@@ -67,37 +67,70 @@ struct PushNotificationSettingsView: View {
6767
}
6868
}
6969
.onAppear {
70-
viewModel.send(.onAppear)
70+
viewModel.send(.fetchSettings)
7171
}
7272
.sheet(isPresented: Binding(
7373
get: { viewModel.state.showTimePicker },
74-
set: { _ in viewModel.send(.setShowTimePicker(false)) }
74+
set: { viewModel.send(.setShowTimePicker($0)) }
7575
)) {
76-
DatePicker(
77-
"",
78-
selection: Binding(
79-
get: { viewModel.state.pushNotificationTime },
80-
set: { viewModel.send(.setPushNotificationTime($0)) }
81-
),
82-
displayedComponents: .hourAndMinute
83-
)
84-
.datePickerStyle(.wheel)
85-
.labelsHidden()
86-
.presentationDragIndicator(.hidden)
87-
.presentationDetents([.height(viewModel.state.sheetHeight)])
88-
.onAppear {
89-
UIDatePicker.appearance().minuteInterval = 5
76+
NavigationStack {
77+
DatePicker(
78+
"",
79+
selection: Binding(
80+
get: { viewModel.state.sheetPushNotificationTime },
81+
set: { viewModel.send(.setPushNotificationTime(sheet: $0)) }
82+
),
83+
displayedComponents: .hourAndMinute
84+
)
85+
.datePickerStyle(.wheel)
86+
.labelsHidden()
87+
.presentationDragIndicator(.hidden)
88+
.presentationDetents([.height(viewModel.state.sheetHeight)])
89+
.onAppear { UIDatePicker.appearance().minuteInterval = 5 }
90+
.onDisappear { UIDatePicker.appearance().minuteInterval = 1 /* 기본값으로 복원 */ }
91+
.toolbar { toolbar }
92+
.background(
93+
GeometryReader { geometry in
94+
Color.clear.onAppear {
95+
viewModel.send(.setSheetHeight(geometry.size.height))
96+
}
97+
}
98+
)
9099
}
91-
.onDisappear {
92-
UIDatePicker.appearance().minuteInterval = 1 // 기본값으로 복원
100+
}
101+
}
102+
103+
@ToolbarContentBuilder
104+
private var toolbar: some ToolbarContent {
105+
if #available(iOS 26.0, *) {
106+
ToolbarItem(placement: .topBarLeading) {
107+
Button(role: .cancel) {
108+
viewModel.send(.rollbackUpdate)
109+
}
93110
}
94-
.background(
95-
GeometryReader { geometry in
96-
Color.clear.onAppear {
97-
viewModel.send(.setSheetHeight(geometry.size.height))
98-
}
111+
112+
ToolbarItem(placement: .topBarTrailing) {
113+
Button(role: .confirm) {
114+
viewModel.send(.confirmUpdate)
115+
}
116+
}
117+
} else {
118+
ToolbarItem(placement: .topBarLeading) {
119+
Button {
120+
viewModel.send(.rollbackUpdate)
121+
} label: {
122+
Text("취소")
99123
}
100-
)
124+
}
125+
126+
ToolbarItem(placement: .topBarTrailing) {
127+
Button {
128+
viewModel.send(.confirmUpdate)
129+
} label: {
130+
Text("확인")
131+
.bold()
132+
}
133+
}
101134
}
102135
}
103136

0 commit comments

Comments
 (0)