Skip to content

Commit aad35e4

Browse files
committed
refactor: 이미 분리되어 있던 Store들의 reduce() 패턴 싱크
1 parent f4f54dd commit aad35e4

8 files changed

Lines changed: 237 additions & 192 deletions

File tree

Application/DevLogPresentation/Sources/Home/List/TodoListFeature.swift

Lines changed: 113 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -63,20 +63,24 @@ struct TodoListFeature {
6363
case alert(PresentationAction<Never>)
6464
case fullScreenCover(PresentationAction<Never>)
6565
case binding(BindingAction<State>)
66-
case refresh
67-
case setFullScreenCover(FullScreenCoverState?)
68-
case swipeTodo(TodoListItem)
69-
case resetFilters
70-
case finishDeleteToast(String)
71-
case tapToggleCompleted(TodoListItem)
72-
case tapTogglePinned(TodoListItem)
73-
case undoDelete
74-
case onAppear
75-
case loadNextPage
66+
case view(ViewAction)
7667
case store(StoreAction)
7768
case loading(LoadingFeature.Action)
7869

70+
enum ViewAction: Equatable {
71+
case refresh
72+
case swipeTodo(TodoListItem)
73+
case resetFilters
74+
case finishDeleteToast(String)
75+
case tapToggleCompleted(TodoListItem)
76+
case tapTogglePinned(TodoListItem)
77+
case undoDelete
78+
case onAppear
79+
case loadNextPage
80+
}
81+
7982
enum StoreAction: Equatable {
83+
case setFullScreenCover(FullScreenCoverState?)
8084
case setAlert(Bool)
8185
case applySearchQuery(String)
8286
case fetchSearchResults([TodoListItem])
@@ -111,7 +115,36 @@ struct TodoListFeature {
111115
}
112116
BindingReducer()
113117
Reduce { state, action in
114-
reduce(action, state: &state)
118+
switch action {
119+
case .alert:
120+
break
121+
case .fullScreenCover(.dismiss):
122+
state.fullScreenCover = nil
123+
case .fullScreenCover:
124+
break
125+
case .binding(\.searchText):
126+
return setSearchTextEffect(state: &state)
127+
case .binding(\.isSearching):
128+
guard !state.isSearching else { break }
129+
state.searchText = ""
130+
state.searchResults = []
131+
state.showAllSearchResults = false
132+
return cancelSearchEffect()
133+
case .binding(\.query.sortTarget), .binding(\.query.sortOrder), .binding(\.query.isPinned),
134+
.binding(\.query.completionFilter):
135+
state.nextCursor = nil
136+
return fetchEffect(query: state.query, cursor: nil)
137+
case .binding:
138+
break
139+
case .view(let action):
140+
return reduce(action, state: &state)
141+
case .store(let action):
142+
return reduce(action, state: &state)
143+
case .loading:
144+
break
145+
}
146+
147+
return .none
115148
}
116149
.ifLet(\.$alert, action: \.alert)
117150
}
@@ -165,84 +198,6 @@ private enum TodoListUndoDeleteTodoUseCaseKey: DependencyKey {
165198
}
166199

167200
private extension TodoListFeature {
168-
func reduce(_ action: Action, state: inout State) -> Effect<Action> {
169-
switch action {
170-
case .alert:
171-
break
172-
case .fullScreenCover(.dismiss):
173-
state.fullScreenCover = nil
174-
case .fullScreenCover:
175-
break
176-
case .binding(\.searchText):
177-
return setSearchTextEffect(state: &state)
178-
case .binding(\.isSearching):
179-
guard !state.isSearching else { break }
180-
state.searchText = ""
181-
state.searchResults = []
182-
state.showAllSearchResults = false
183-
return cancelSearchEffect()
184-
case .binding(\.query.sortTarget), .binding(\.query.sortOrder), .binding(\.query.isPinned),
185-
.binding(\.query.completionFilter):
186-
state.nextCursor = nil
187-
return fetchEffect(query: state.query, cursor: nil)
188-
case .binding:
189-
break
190-
case .refresh, .onAppear:
191-
return fetchEffect(query: state.query, cursor: nil)
192-
case .store(.setAlert(let value)):
193-
Self.setAlert(&state, isPresented: value)
194-
case .setFullScreenCover(let cover):
195-
state.fullScreenCover = cover
196-
case .swipeTodo(let todo):
197-
return swipeTodoEffect(todo, state: &state)
198-
case .resetFilters:
199-
state.query = TodoQuery(categoryId: state.category.storageValue)
200-
state.nextCursor = nil
201-
return fetchEffect(query: state.query, cursor: nil)
202-
case .finishDeleteToast(let todoId):
203-
state.todos.removeAll { $0.id == todoId && $0.isHidden }
204-
state.searchResults.removeAll { $0.id == todoId && $0.isHidden }
205-
if state.undoTodoId == todoId {
206-
state.undoTodoId = nil
207-
}
208-
case .tapToggleCompleted(let todo):
209-
return toggleCompletedEffect(todo)
210-
case .tapTogglePinned(let todo):
211-
return togglePinnedEffect(todo)
212-
case .undoDelete:
213-
guard let undoTodoId = state.undoTodoId else { return .none }
214-
Self.setTodoHidden(&state, todoId: undoTodoId, isHidden: false)
215-
state.undoTodoId = nil
216-
return undoDeleteEffect(undoTodoId)
217-
case .loadNextPage:
218-
guard state.hasMore, !state.isLoading else { return .none }
219-
return fetchEffect(query: state.query, cursor: state.nextCursor, resetsPagination: false)
220-
case .store(.applySearchQuery(let query)):
221-
return applySearchQueryEffect(query, state: &state)
222-
case .store(.fetchSearchResults(let items)):
223-
state.searchResults = items
224-
case .store(.didToggleCompleted(let todo)), .store(.didTogglePinned(let todo)):
225-
if let index = state.todos.firstIndex(where: { $0.id == todo.id }) {
226-
state.todos[index] = todo
227-
}
228-
case .store(.setTodoHidden(let todoId, let isHidden)):
229-
Self.setTodoHidden(&state, todoId: todoId, isHidden: isHidden)
230-
case .store(.appendTodos(let todos, let nextCursor)):
231-
state.todos.append(contentsOf: todos)
232-
state.nextCursor = nextCursor
233-
case .store(.resetPagination):
234-
state.todos = []
235-
state.nextCursor = nil
236-
state.hasMore = false
237-
case .store(.setHasMore(let value)):
238-
state.hasMore = value
239-
case .loading:
240-
break
241-
}
242-
243-
return .none
244-
}
245-
246201
func fetchEffect(
247202
query: TodoQuery,
248203
cursor: TodoCursor?,
@@ -316,4 +271,73 @@ private extension TodoListFeature {
316271
.cancellable(id: CancelID.debounce, cancelInFlight: true)
317272
)
318273
}
274+
275+
func reduce(
276+
_ action: Action.ViewAction,
277+
state: inout State
278+
) -> Effect<Action> {
279+
switch action {
280+
case .refresh, .onAppear:
281+
return fetchEffect(query: state.query, cursor: nil)
282+
case .swipeTodo(let todo):
283+
return swipeTodoEffect(todo, state: &state)
284+
case .resetFilters:
285+
state.query = TodoQuery(categoryId: state.category.storageValue)
286+
state.nextCursor = nil
287+
return fetchEffect(query: state.query, cursor: nil)
288+
case .finishDeleteToast(let todoId):
289+
state.todos.removeAll { $0.id == todoId && $0.isHidden }
290+
state.searchResults.removeAll { $0.id == todoId && $0.isHidden }
291+
if state.undoTodoId == todoId {
292+
state.undoTodoId = nil
293+
}
294+
case .tapToggleCompleted(let todo):
295+
return toggleCompletedEffect(todo)
296+
case .tapTogglePinned(let todo):
297+
return togglePinnedEffect(todo)
298+
case .undoDelete:
299+
guard let undoTodoId = state.undoTodoId else { return .none }
300+
Self.setTodoHidden(&state, todoId: undoTodoId, isHidden: false)
301+
state.undoTodoId = nil
302+
return undoDeleteEffect(undoTodoId)
303+
case .loadNextPage:
304+
guard state.hasMore, !state.isLoading else { return .none }
305+
return fetchEffect(query: state.query, cursor: state.nextCursor, resetsPagination: false)
306+
}
307+
308+
return .none
309+
}
310+
311+
func reduce(
312+
_ action: Action.StoreAction,
313+
state: inout State
314+
) -> Effect<Action> {
315+
switch action {
316+
case .setFullScreenCover(let cover):
317+
state.fullScreenCover = cover
318+
case .setAlert(let value):
319+
Self.setAlert(&state, isPresented: value)
320+
case .applySearchQuery(let query):
321+
return applySearchQueryEffect(query, state: &state)
322+
case .fetchSearchResults(let items):
323+
state.searchResults = items
324+
case .didToggleCompleted(let todo), .didTogglePinned(let todo):
325+
if let index = state.todos.firstIndex(where: { $0.id == todo.id }) {
326+
state.todos[index] = todo
327+
}
328+
case .setTodoHidden(let todoId, let isHidden):
329+
Self.setTodoHidden(&state, todoId: todoId, isHidden: isHidden)
330+
case .appendTodos(let todos, let nextCursor):
331+
state.todos.append(contentsOf: todos)
332+
state.nextCursor = nextCursor
333+
case .resetPagination:
334+
state.todos = []
335+
state.nextCursor = nil
336+
state.hasMore = false
337+
case .setHasMore(let value):
338+
state.hasMore = value
339+
}
340+
341+
return .none
342+
}
319343
}

Application/DevLogPresentation/Sources/Home/List/TodoListView.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ struct TodoListView: View {
8080
}
8181
.background(NavigationBarConfigurator())
8282
.background(Color(.systemGroupedBackground))
83-
.task { store.send(.onAppear) }
83+
.task { store.send(.view(.onAppear)) }
8484
}
8585

8686
@ViewBuilder
@@ -118,26 +118,26 @@ struct TodoListView: View {
118118
.onAppear {
119119
let lastID = visibleTodos.last?.id
120120
if todo.id == lastID, store.state.hasMore {
121-
store.send(.loadNextPage)
121+
store.send(.view(.loadNextPage))
122122
}
123123
}
124124
.swipeActions(edge: .leading) {
125125
Button(action: {
126-
store.send(.tapTogglePinned(todo))
126+
store.send(.view(.tapTogglePinned(todo)))
127127
}) {
128128
Image(systemName: "star\(todo.isPinned ? ".slash" : ".fill")")
129129
}
130130
.tint(Color.orange)
131131
Button {
132-
store.send(.tapToggleCompleted(todo))
132+
store.send(.view(.tapToggleCompleted(todo)))
133133
} label: {
134134
Image(systemName: todo.isCompleted ? "arrow.uturn.backward" : "checkmark")
135135
}
136136
.tint(Color.green)
137137
}
138138
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
139139
Button(role: .destructive, action: {
140-
store.send(.swipeTodo(todo))
140+
store.send(.view(.swipeTodo(todo)))
141141
presentDeleteTodoToast(todo.id)
142142
}) {
143143
Image(systemName: "trash")
@@ -171,7 +171,7 @@ struct TodoListView: View {
171171
}
172172
.offset(y: headerOffset)
173173
}
174-
.refreshable { store.send(.refresh) }
174+
.refreshable { store.send(.view(.refresh)) }
175175
.scrollDisabled(visibleTodos.isEmpty || store.state.isLoading)
176176

177177
if store.state.isLoading {
@@ -212,8 +212,8 @@ struct TodoListView: View {
212212
$0.trackAnalyticsEventUseCase = container.resolve(TrackAnalyticsEventUseCase.self)
213213
},
214214
onCreateSuccess: {
215-
store.send(.setFullScreenCover(nil))
216-
store.send(.refresh)
215+
store.send(.store(.setFullScreenCover(nil)))
216+
store.send(.view(.refresh))
217217
}
218218
)
219219
}
@@ -226,7 +226,7 @@ struct TodoListView: View {
226226
value: TodoEditorWindowValue(todoCategory: store.category, source: .list)
227227
)
228228
} else {
229-
store.send(.setFullScreenCover(.editor))
229+
store.send(.store(.setFullScreenCover(.editor)))
230230
}
231231
}
232232

@@ -236,10 +236,10 @@ struct TodoListView: View {
236236
systemImage: "arrow.uturn.left",
237237
duration: 5,
238238
action: {
239-
store.send(.undoDelete)
239+
store.send(.view(.undoDelete))
240240
},
241241
onDismiss: {
242-
store.send(.finishDeleteToast(todoId))
242+
store.send(.view(.finishDeleteToast(todoId)))
243243
}
244244
)
245245
}
@@ -310,7 +310,7 @@ struct TodoListView: View {
310310
)
311311
)
312312
Button(role: .destructive) {
313-
store.send(.resetFilters)
313+
store.send(.view(.resetFilters))
314314
} label: {
315315
Text(String(localized: "todo_list_clear_filters"))
316316
}

0 commit comments

Comments
 (0)