Skip to content

Commit c4e9f81

Browse files
committed
refactor: TodoViewModel의 린트 경고 개선 및 Action 최적화, 얼럿 고정
1 parent a338c78 commit c4e9f81

4 files changed

Lines changed: 124 additions & 95 deletions

File tree

DevLog/Presentation/ViewModel/TodoViewModel.swift

Lines changed: 112 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ final class TodoViewModel: Store {
1414
let kind: TodoKind
1515
var showEditor: Bool = false
1616
var showAlert: Bool = false
17+
var alertTitle: String = ""
1718
var alertMessage: String = ""
1819
var scope: TodoScope = .title
1920
var filterOption: FilterOption = .create
20-
var isLoading = false
21+
var isLoading: Bool = false
2122
var showToast: Bool = false
2223
var toastMessage: String = ""
2324
var pendingTask: (Todo, Int)?
@@ -29,31 +30,26 @@ final class TodoViewModel: Store {
2930

3031
enum Action {
3132
// User
32-
case tapTogglePinned(Todo)
33+
case refresh
34+
case setAlert(Bool)
35+
case setShowEditor(Bool)
3336
case swipeTodo(Todo)
3437
case tapFilterOption(FilterOption)
35-
case upsertTodo(Todo)
38+
case tapTogglePinned(Todo)
3639
case undoDelete
37-
case confirmDelete
3840

3941
// View
40-
case onAppear, refresh
41-
case openEditor
42-
case closeEditor
43-
case closeAlert
42+
case confirmDelete
43+
case onAppear
4444
case setScope(TodoScope)
4545
case setSearchText(String)
46-
case setToast(isPresented: Bool, type: ToastType? = nil)
47-
case setLoading(Bool)
48-
case setTodos([Todo])
46+
case setToast(isPresented: Bool)
47+
case upsertTodo(Todo)
4948

5049
// Run
51-
case didShowAlert(String)
5250
case didTogglePinned(Todo)
53-
}
54-
55-
enum ToastType {
56-
case delete
51+
case setLoading(Bool)
52+
case setTodos([Todo])
5753
}
5854

5955
enum SideEffect {
@@ -82,59 +78,21 @@ final class TodoViewModel: Store {
8278

8379
func reduce(with action: Action) -> [SideEffect] {
8480
var state = self.state
85-
81+
var effects: [SideEffect] = []
82+
8683
switch action {
87-
case .onAppear, .refresh:
88-
return [.fetch]
89-
case .tapTogglePinned(let todo):
90-
return [.togglePinned(todo)]
91-
case .swipeTodo(let todo):
92-
guard let index = state.todos.firstIndex(where: { $0.id == todo.id }) else {
93-
return []
94-
}
95-
state.pendingTask = (todo, index)
96-
state.todos.remove(at: index)
97-
setToast(&state, isPresented: true, for: .delete)
98-
case .tapFilterOption(let option):
99-
state.filterOption = option
100-
case .upsertTodo(let todo):
101-
return [.upsert(todo)]
102-
case .undoDelete:
103-
guard let (todo, index) = state.pendingTask else { return [] }
104-
state.todos.insert(todo, at: index)
105-
state.pendingTask = nil
106-
case .confirmDelete:
107-
guard let (item, _) = state.pendingTask else {
108-
return []
109-
}
110-
return [.delete(item)]
111-
case .openEditor:
112-
state.showEditor = true
113-
case .closeEditor:
114-
state.showEditor = false
115-
case .closeAlert:
116-
state.showAlert = false
117-
case .setScope(let scope):
118-
state.scope = scope
119-
case .setSearchText(let text):
120-
state.searchText = text
121-
case .setToast(let isPresented, let type):
122-
setToast(&state, isPresented: isPresented, for: type)
123-
case .setLoading(let value):
124-
state.isLoading = value
125-
case .setTodos(let todos):
126-
state.todos = todos
127-
case .didShowAlert(let message):
128-
state.alertMessage = message
129-
state.showAlert = true
130-
case .didTogglePinned(let todo):
131-
if let index = state.todos.firstIndex(where: { $0.id == todo.id }) {
132-
state.todos[index] = todo
133-
}
84+
case .refresh, .setAlert, .setShowEditor, .swipeTodo, .tapFilterOption, .tapTogglePinned, .undoDelete:
85+
effects = reduceByUser(action, state: &state)
86+
87+
case .confirmDelete, .onAppear, .setScope, .setSearchText, .setToast, .upsertTodo:
88+
effects = reduceByView(action, state: &state)
89+
90+
case .didTogglePinned, .setLoading, .setTodos:
91+
effects = reduceByRun(action, state: &state)
13492
}
135-
93+
13694
self.state = state
137-
return []
95+
return effects
13896
}
13997

14098
func run(_ effect: SideEffect) {
@@ -147,7 +105,7 @@ final class TodoViewModel: Store {
147105
let todos = try await fetchTodosByKindUseCase.execute(state.kind)
148106
send(.setTodos(todos))
149107
} catch {
150-
send(.didShowAlert(error.localizedDescription))
108+
send(.setAlert(true))
151109
}
152110
}
153111
case .upsert(let item):
@@ -158,7 +116,7 @@ final class TodoViewModel: Store {
158116
try await upsertTodoUseCase.execute(item)
159117
send(.refresh)
160118
} catch {
161-
send(.didShowAlert(error.localizedDescription))
119+
send(.setAlert(true))
162120
}
163121
}
164122
case .togglePinned(let item):
@@ -171,32 +129,108 @@ final class TodoViewModel: Store {
171129
try await upsertTodoUseCase.execute(todo)
172130
send(.didTogglePinned(todo))
173131
} catch {
174-
send(.didShowAlert(error.localizedDescription))
132+
send(.setAlert(true))
175133
}
176134
}
177135
case .delete(let item):
178136
Task {
179137
do {
180138
try await deleteTodoUseCase.execute(item.id)
181139
} catch {
182-
send(.didShowAlert(error.localizedDescription))
140+
send(.setAlert(true))
183141
}
184142
}
185143
}
186144
}
187145
}
146+
147+
// MARK: - Reduce Methods
148+
private extension TodoViewModel {
149+
func reduceByUser(_ action: Action, state: inout State) -> [SideEffect] {
150+
switch action {
151+
case .refresh:
152+
return [.fetch]
153+
case .setAlert(let value):
154+
setAlert(&state, isPresented: value)
155+
case .setShowEditor(let value):
156+
state.showEditor = value
157+
case .swipeTodo(let todo):
158+
guard let index = state.todos.firstIndex(where: { $0.id == todo.id }) else {
159+
return []
160+
}
161+
state.pendingTask = (todo, index)
162+
state.todos.remove(at: index)
163+
setToast(&state, isPresented: true)
164+
case .tapFilterOption(let option):
165+
state.filterOption = option
166+
case .tapTogglePinned(let todo):
167+
return [.togglePinned(todo)]
168+
case .undoDelete:
169+
guard let (todo, index) = state.pendingTask else { return [] }
170+
state.todos.insert(todo, at: index)
171+
state.pendingTask = nil
172+
default:
173+
break
174+
}
175+
return []
176+
}
177+
178+
func reduceByView(_ action: Action, state: inout State) -> [SideEffect] {
179+
switch action {
180+
case .confirmDelete:
181+
guard let (item, _) = state.pendingTask else {
182+
return []
183+
}
184+
return [.delete(item)]
185+
case .onAppear:
186+
return [.fetch]
187+
case .setScope(let scope):
188+
state.scope = scope
189+
case .setSearchText(let text):
190+
state.searchText = text
191+
case .setToast(let isPresented):
192+
setToast(&state, isPresented: isPresented)
193+
case .upsertTodo(let todo):
194+
return [.upsert(todo)]
195+
default:
196+
break
197+
}
198+
return []
199+
}
200+
201+
func reduceByRun(_ action: Action, state: inout State) -> [SideEffect] {
202+
switch action {
203+
case .didTogglePinned(let todo):
204+
if let index = state.todos.firstIndex(where: { $0.id == todo.id }) {
205+
state.todos[index] = todo
206+
}
207+
case .setLoading(let value):
208+
state.isLoading = value
209+
case .setTodos(let todos):
210+
state.todos = todos
211+
default:
212+
break
213+
}
214+
return []
215+
}
216+
}
217+
218+
// MARK: - Helper Methods
188219
private extension TodoViewModel {
220+
func setAlert(
221+
_ state: inout State,
222+
isPresented: Bool
223+
) {
224+
state.alertTitle = "오류"
225+
state.alertMessage = "문제가 발생했습니다. 잠시 후 다시 시도해주세요."
226+
state.showAlert = isPresented
227+
}
228+
189229
func setToast(
190230
_ state: inout State,
191-
isPresented: Bool,
192-
for type: ToastType?
231+
isPresented: Bool
193232
) {
194-
switch type {
195-
case .delete:
196-
state.toastMessage = "실행 취소"
197-
case .none:
198-
state.toastMessage = ""
199-
}
233+
state.toastMessage = "실행 취소"
200234
state.showToast = isPresented
201235
}
202236
}

DevLog/Resource/Localizable.xcstrings

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,6 @@
286286
},
287287
"베타 테스트 참여" : {
288288

289-
},
290-
"불러오기 실패" : {
291-
292289
},
293290
"사용자 설정" : {
294291

DevLog/UI/Home/TodoView.swift

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,13 @@ struct TodoView: View {
8080
}
8181
}
8282
}
83-
.alert("불러오기 실패", isPresented: Binding(
84-
get: { viewModel.state.showAlert },
85-
set: { _, _ in }
83+
.alert(
84+
viewModel.state.alertTitle,
85+
isPresented: Binding(
86+
get: { viewModel.state.showAlert },
87+
set: { viewModel.send(.setAlert($0)) }
8688
)) {
87-
Button(role: .cancel, action: {
88-
viewModel.send(.closeAlert)
89-
}) {
90-
Text("확인")
91-
}
89+
Button("확인", role: .cancel) { }
9290
} message: {
9391
Text(viewModel.state.alertMessage)
9492
}
@@ -107,8 +105,8 @@ struct TodoView: View {
107105
.navigationBarTitleDisplayMode(.large)
108106
.fullScreenCover(isPresented: Binding(
109107
get: { viewModel.state.showEditor },
110-
set: { _, _ in viewModel.send(.closeEditor) })
111-
) {
108+
set: { viewModel.send(.setShowEditor($0)) }
109+
)) {
112110
TodoEditorView(
113111
viewModel: TodoEditorViewModel(kind: viewModel.state.kind),
114112
onSubmit: { viewModel.send(.upsertTodo($0)) }
@@ -183,9 +181,9 @@ struct TodoView: View {
183181
}, label: {
184182
Image(systemName: "ellipsis")
185183
})
186-
Button(action: {
187-
viewModel.send(.openEditor)
188-
}) {
184+
Button {
185+
viewModel.send(.setShowEditor(true))
186+
} label: {
189187
Image(systemName: "plus")
190188
}
191189
}

DevLog/UI/Search/SearchView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ struct SearchView: View {
114114
}
115115
}
116116
case .error:
117-
Button("확인", role: .cancel) {}
117+
Button("확인", role: .cancel) { }
118118
}
119119
}
120120

0 commit comments

Comments
 (0)