Skip to content

Commit 92510e0

Browse files
authored
[#658] TodoEditor 저장 결과 전달을 TCA delegate action으로 전환한다 (#667)
* refactor: TodoEditor 저장 성공 delegate action 추가 * refactor: Home TodoEditor 생성 흐름 reducer 이동 * refactor: TodoList TodoEditor 생성 흐름 reducer 이동 * refactor: TodoDetail TodoEditor 수정 흐름 reducer 이동 * refactor: Home TodoEditor 표시 조건 단순화 * refactor: TodoEditor 생성 analytics 의존성 제거 * refactor: Home TodoEditor 생성 analytics 처리 이동 * refactor: TodoList 생성 analytics 처리 이동 * test: TodoEditor 생성 analytics 책임 이동 검증
1 parent 21cc551 commit 92510e0

20 files changed

Lines changed: 308 additions & 92 deletions

Application/Presentation/Sources/Home/Detail/TodoDetailFeature.swift

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,26 @@ struct TodoDetailFeature {
5757
@ObservableState
5858
struct FullScreenCoverState: Equatable {
5959
var destination: Destination
60+
var todoEditor: TodoEditorFeature.State?
6061

6162
enum Destination: Equatable {
6263
case editor
6364
}
6465

6566
static let editor = Self(destination: .editor)
67+
68+
static func editor(_ todo: Todo) -> Self {
69+
Self(
70+
destination: .editor,
71+
todoEditor: TodoEditorFeature.State(todo: todo)
72+
)
73+
}
6674
}
6775

6876
enum Action {
6977
case alert(PresentationAction<Never>)
7078
case sheet(PresentationAction<Sheet>)
71-
case fullScreenCover(PresentationAction<Never>)
79+
case fullScreenCover(PresentationAction<FullScreenCover>)
7280
case onAppear
7381
case fetchFailed
7482
case setSheet(SheetState?)
@@ -82,6 +90,11 @@ struct TodoDetailFeature {
8290
case tapCloseButton
8391
case todo(TodoDetailFeature.Action)
8492
}
93+
94+
@CasePathable
95+
enum FullScreenCover {
96+
case todoEditor(TodoEditorFeature.Action)
97+
}
8598
}
8699

87100
@Dependency(\.fetchTodoByIdUseCase) var fetchTodoUseCase
@@ -101,6 +114,11 @@ struct TodoDetailFeature {
101114
state.sheet = nil
102115
case .sheet:
103116
break
117+
case .fullScreenCover(.presented(.todoEditor(.delegate(.updated(let todo))))):
118+
state.fullScreenCover = nil
119+
state.todo = todo
120+
state.referenceItems = [:]
121+
return resolveMarkdownEffect(content: todo.content)
104122
case .fullScreenCover(.dismiss):
105123
state.fullScreenCover = nil
106124
case .fullScreenCover:
@@ -112,7 +130,12 @@ struct TodoDetailFeature {
112130
case .setSheet(let sheet):
113131
state.sheet = sheet
114132
case .setFullScreenCover(let cover):
115-
state.fullScreenCover = cover
133+
if cover?.destination == .editor,
134+
let todo = state.todo {
135+
state.fullScreenCover = .editor(todo)
136+
} else {
137+
state.fullScreenCover = nil
138+
}
116139
case .setTodo(let todo):
117140
state.todo = todo
118141
state.referenceItems = [:]
@@ -129,6 +152,21 @@ struct TodoDetailFeature {
129152
.ifLet(\.$sheet, action: \.sheet) {
130153
TodoDetailSheetFeature()
131154
}
155+
.ifLet(\.$fullScreenCover, action: \.fullScreenCover) {
156+
TodoDetailFullScreenCoverFeature()
157+
}
158+
}
159+
}
160+
161+
private struct TodoDetailFullScreenCoverFeature: Reducer {
162+
typealias State = TodoDetailFeature.FullScreenCoverState
163+
typealias Action = TodoDetailFeature.Action.FullScreenCover
164+
165+
var body: some ReducerOf<Self> {
166+
EmptyReducer()
167+
.ifLet(\.todoEditor, action: \.todoEditor) {
168+
TodoEditorFeature()
169+
}
132170
}
133171
}
134172

Application/Presentation/Sources/Home/Detail/TodoDetailView.swift

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -82,26 +82,12 @@ struct TodoDetailView: View {
8282

8383
@ViewBuilder
8484
private func fullScreenCoverContent(
85-
_ coverStore: Store<TodoDetailFeature.FullScreenCoverState, Never>
85+
_ coverStore: Store<TodoDetailFeature.FullScreenCoverState, TodoDetailFeature.Action.FullScreenCover>
8686
) -> some View {
8787
switch coverStore.destination {
8888
case .editor:
89-
if let todo = store.todo {
90-
TodoEditorView(
91-
store: Store(initialState: TodoEditorFeature.State(todo: todo)) {
92-
TodoEditorFeature()
93-
} withDependencies: {
94-
$0.fetchTodoCategoryPreferencesUseCase = container.resolve(
95-
FetchTodoCategoryPreferencesUseCase.self
96-
)
97-
$0.fetchReferenceItemsUseCase = container.resolve(FetchReferenceItemsUseCase.self)
98-
$0.upsertTodoUseCase = container.resolve(UpsertTodoUseCase.self)
99-
},
100-
onUpdateSuccess: { todo in
101-
store.send(.setFullScreenCover(nil))
102-
store.send(.setTodo(todo))
103-
}
104-
)
89+
if let todoEditorStore = coverStore.scope(state: \.todoEditor, action: \.todoEditor) {
90+
TodoEditorView(store: todoEditorStore)
10591
}
10692
}
10793
}

Application/Presentation/Sources/Home/Editor/TodoEditorFeature.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ struct TodoEditorFeature {
111111
case alert(PresentationAction<Never>)
112112
case sheet(PresentationAction<Sheet>)
113113
case binding(BindingAction<State>)
114+
case delegate(Delegate)
114115
case onAppear
115116
case addTag(String)
116117
case removeTag(String)
@@ -125,6 +126,11 @@ struct TodoEditorFeature {
125126
enum Sheet: Equatable {
126127
case tapCloseButton
127128
}
129+
130+
enum Delegate: Equatable {
131+
case created
132+
case updated(Todo)
133+
}
128134
}
129135

130136
private enum CancelID: Hashable {
@@ -135,7 +141,6 @@ struct TodoEditorFeature {
135141
@Dependency(\.fetchTodoCategoryPreferencesUseCase) var fetchPreferencesUseCase
136142
@Dependency(\.fetchReferenceItemsUseCase) var fetchReferenceItemsUseCase
137143
@Dependency(\.upsertTodoUseCase) var upsertTodoUseCase
138-
@Dependency(\.trackAnalyticsEventUseCase) var trackAnalyticsEventUseCase
139144

140145
var body: some ReducerOf<Self> {
141146
Scope(state: \.loading, action: \.loading) {
@@ -169,6 +174,8 @@ struct TodoEditorFeature {
169174
}
170175
case .binding:
171176
break
177+
case .delegate:
178+
break
172179
case .onAppear:
173180
return fetchCategoriesEffect()
174181
case .addTag(let tag):
@@ -300,12 +307,12 @@ private extension TodoEditorFeature {
300307
}
301308

302309
func createTodoEffect(_ draft: TodoDraft) -> Effect<Action> {
303-
.run { [trackAnalyticsEventUseCase, upsertTodoUseCase] send in
310+
.run { [upsertTodoUseCase] send in
304311
await send(.loading(.begin(target: .default, mode: .immediate)))
305312
do {
306313
try await upsertTodoUseCase.execute(draft)
307-
trackAnalyticsEventUseCase.execute(.todoCreate)
308314
await send(.createSucceeded)
315+
await send(.delegate(.created))
309316
} catch {
310317
await send(.saveFailed)
311318
}
@@ -319,6 +326,7 @@ private extension TodoEditorFeature {
319326
do {
320327
try await upsertTodoUseCase.execute(todo)
321328
await send(.updateSucceeded(todo))
329+
await send(.delegate(.updated(todo)))
322330
} catch {
323331
await send(.saveFailed)
324332
}

Application/Presentation/Sources/Home/Home/HomeFeature+Effects.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ extension HomeFeature {
8484
}
8585
}
8686

87+
func trackTodoCreateEffect() -> Effect<Action> {
88+
.run { [trackAnalyticsEventUseCase] _ in
89+
trackAnalyticsEventUseCase.execute(.todoCreate)
90+
}
91+
}
92+
8793
func deleteWebPageEffect(_ page: WebPageItem) -> Effect<Action> {
8894
.run { [deleteWebPageUseCase] send in
8995
do {

Application/Presentation/Sources/Home/Home/HomeFeature.swift

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ struct HomeFeature {
2727
var loading = LoadingFeature.State()
2828

2929
var showContentPicker: Bool { sheet?.contentPickerState != nil }
30-
var showTodoEditor: Bool { fullScreenCover?.destination == .todoEditor }
30+
var showTodoEditor: Bool {
31+
fullScreenCover?.destination == .todoEditor
32+
}
3133

3234
var isPreferencesLoading: Bool {
3335
loading.visibleTargets.contains(LoadingTarget.preferences.target)
@@ -54,7 +56,7 @@ struct HomeFeature {
5456
enum Action: Equatable {
5557
case alert(PresentationAction<Never>)
5658
case sheet(PresentationAction<Sheet>)
57-
case fullScreenCover(PresentationAction<Never>)
59+
case fullScreenCover(PresentationAction<FullScreenCover>)
5860
case view(ViewAction)
5961
case store(StoreAction)
6062
case loading(LoadingFeature.Action)
@@ -135,19 +137,29 @@ struct HomeFeature {
135137
struct FullScreenCoverState: Equatable {
136138
var destination: Destination
137139
var selectedTodoCategory: TodoCategory?
140+
var todoEditor: TodoEditorFeature.State?
138141

139142
enum Destination: Equatable {
140143
case todoEditor
141144
case search
142145
}
143146

144147
static func todoEditor(_ category: TodoCategory) -> Self {
145-
Self(destination: .todoEditor, selectedTodoCategory: category)
148+
Self(
149+
destination: .todoEditor,
150+
selectedTodoCategory: category,
151+
todoEditor: TodoEditorFeature.State(category: category)
152+
)
146153
}
147154

148155
static let search = Self(destination: .search)
149156
}
150157

158+
@CasePathable
159+
enum FullScreenCover: Equatable {
160+
case todoEditor(TodoEditorFeature.Action)
161+
}
162+
151163
enum Presentation: Equatable {
152164
case todoEditor
153165
case contentPicker
@@ -193,6 +205,13 @@ struct HomeFeature {
193205
switch action {
194206
case .alert:
195207
break
208+
case .fullScreenCover(.presented(.todoEditor(.delegate(.created)))):
209+
state.fullScreenCover = nil
210+
state.selectedTodoCategory = nil
211+
return .merge(
212+
trackTodoCreateEffect(),
213+
.send(.view(.fetchData))
214+
)
196215
case .fullScreenCover(.dismiss):
197216
state.fullScreenCover = nil
198217
state.selectedTodoCategory = nil
@@ -216,6 +235,21 @@ struct HomeFeature {
216235
.ifLet(\.$sheet, action: \.sheet) {
217236
HomeSheetFeature()
218237
}
238+
.ifLet(\.$fullScreenCover, action: \.fullScreenCover) {
239+
HomeFullScreenCoverFeature()
240+
}
241+
}
242+
}
243+
244+
private struct HomeFullScreenCoverFeature: Reducer {
245+
typealias State = HomeFeature.FullScreenCoverState
246+
typealias Action = HomeFeature.FullScreenCover
247+
248+
var body: some ReducerOf<Self> {
249+
EmptyReducer()
250+
.ifLet(\.todoEditor, action: \.todoEditor) {
251+
TodoEditorFeature()
252+
}
219253
}
220254
}
221255

Application/Presentation/Sources/Home/Home/HomeView.swift

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -269,17 +269,13 @@ struct HomeView: View {
269269
}
270270

271271
@ViewBuilder
272-
private func coverContent(_ coverStore: Store<HomeFeature.FullScreenCoverState, Never>) -> some View {
272+
private func coverContent(
273+
_ coverStore: Store<HomeFeature.FullScreenCoverState, HomeFeature.FullScreenCover>
274+
) -> some View {
273275
switch coverStore.destination {
274276
case .todoEditor:
275-
if let selectedCategory = coverStore.selectedTodoCategory {
276-
TodoEditorView(
277-
store: coordinator.makeTodoEditorStore(category: selectedCategory),
278-
onCreateSuccess: {
279-
store.send(.store(.setPresentation(.todoEditor, false)))
280-
store.send(.view(.fetchData))
281-
}
282-
)
277+
if let todoEditorStore = coverStore.scope(state: \.todoEditor, action: \.todoEditor) {
278+
TodoEditorView(store: todoEditorStore)
283279
}
284280
case .search:
285281
SearchView(store: coordinator.makeSearchStore())

Application/Presentation/Sources/Home/Home/HomeViewCoordinator.swift

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ final class HomeViewCoordinator {
3939
$0.homeFetchTodosUseCase = container.resolve(FetchTodosUseCase.self)
4040
$0.homeFetchWebPagesUseCase = container.resolve(FetchWebPagesUseCase.self)
4141
$0.networkConnectivityUseCase = container.resolve(ObserveNetworkConnectivityUseCase.self)
42+
$0.fetchReferenceItemsUseCase = container.resolve(FetchReferenceItemsUseCase.self)
43+
$0.upsertTodoUseCase = container.resolve(UpsertTodoUseCase.self)
4244
$0.trackAnalyticsEventUseCase = container.resolve(TrackAnalyticsEventUseCase.self)
4345
}
4446
self.store.send(.view(.startObserving))
@@ -82,17 +84,6 @@ final class HomeViewCoordinator {
8284
.store(in: &cancellables)
8385
}
8486

85-
func makeTodoEditorStore(category: TodoCategory) -> StoreOf<TodoEditorFeature> {
86-
Store(initialState: TodoEditorFeature.State(category: category)) {
87-
TodoEditorFeature()
88-
} withDependencies: {
89-
$0.fetchTodoCategoryPreferencesUseCase = self.container.resolve(FetchTodoCategoryPreferencesUseCase.self)
90-
$0.fetchReferenceItemsUseCase = self.container.resolve(FetchReferenceItemsUseCase.self)
91-
$0.upsertTodoUseCase = self.container.resolve(UpsertTodoUseCase.self)
92-
$0.trackAnalyticsEventUseCase = self.container.resolve(TrackAnalyticsEventUseCase.self)
93-
}
94-
}
95-
9687
func makeSearchStore() -> StoreOf<SearchFeature> {
9788
Store(
9889
initialState: SearchFeature.State(

Application/Presentation/Sources/Home/List/TodoListFeature+Effects.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ extension TodoListFeature {
6161
)
6262
}
6363

64+
func trackTodoCreateEffect() -> Effect<Action> {
65+
.run { [trackAnalyticsEventUseCase] _ in
66+
trackAnalyticsEventUseCase.execute(.todoCreate)
67+
}
68+
}
69+
6470
func togglePinnedEffect(_ item: TodoListItem) -> Effect<Action> {
6571
.concatenate(
6672
.send(.loading(.begin(target: .default, mode: .delayed))),

0 commit comments

Comments
 (0)