Skip to content

Commit 58f673e

Browse files
authored
[#659] CategoryManage 시트 흐름을 parent-owned TCA presentation으로 정리한다 (#669)
* refactor: CategoryManage 완료 delegate 추가 * refactor: Home에서 CategoryManage presentation 소유 * test: CategoryManage delegate 흐름 검증 추가 * refactor: 변수명 수정 * refactor: Home URL 입력을 store binding으로 정리 * refactor: Home URL 입력 중복 action 제거
1 parent ed3f71a commit 58f673e

7 files changed

Lines changed: 83 additions & 48 deletions

File tree

Application/Presentation/Sources/Home/Category/CategoryManageFeature.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,10 @@ struct CategoryManageFeature {
8888
}
8989
}
9090

91-
enum Action {
91+
enum Action: Equatable {
9292
case alert(PresentationAction<Alert>)
9393
case categorySheet(PresentationAction<CategorySheet>)
94+
case delegate(Delegate)
9495
case tapAddUserCategory
9596
case moveItem(from: IndexSet, target: Int)
9697
case tapItem(TodoCategoryItem)
@@ -103,6 +104,10 @@ struct CategoryManageFeature {
103104
case confirmDeleteUserCategory(TodoCategoryItem)
104105
}
105106

107+
enum Delegate: Equatable {
108+
case done([TodoCategoryItem])
109+
}
110+
106111
enum CategorySheet: BindableAction, Equatable {
107112
case binding(BindingAction<CategorySheetState>)
108113
case tapCloseButton
@@ -120,6 +125,8 @@ struct CategoryManageFeature {
120125
}
121126
case .alert:
122127
break
128+
case .delegate:
129+
break
123130
case .categorySheet(.dismiss):
124131
state.categorySheet = nil
125132
case .categorySheet(.presented(.tapCloseButton)):
@@ -166,7 +173,7 @@ struct CategoryManageFeature {
166173
state.alert = Self.deleteAlertState(for: item)
167174
}
168175
case .tapDoneButton:
169-
break
176+
return .send(.delegate(.done(state.preferences)))
170177
case .setCategorySheet(let sheet):
171178
state.categorySheet = sheet
172179
}

Application/Presentation/Sources/Home/Category/CategoryManageView.swift

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,9 @@
77

88
import SwiftUI
99
import ComposableArchitecture
10-
import Domain
1110

1211
struct CategoryManageView: View {
13-
@State private var store: StoreOf<CategoryManageFeature>
14-
var onDismiss: (([TodoCategoryItem]) -> Void)?
15-
16-
init(
17-
preferences: [TodoCategoryItem],
18-
onDismiss: (([TodoCategoryItem]) -> Void)?
19-
) {
20-
self._store = State(initialValue: Store(
21-
initialState: CategoryManageFeature.State(preferences: preferences)
22-
) {
23-
CategoryManageFeature()
24-
})
25-
self.onDismiss = onDismiss
26-
}
12+
@Bindable var store: StoreOf<CategoryManageFeature>
2713

2814
var body: some View {
2915
NavigationStack {
@@ -81,8 +67,7 @@ struct CategoryManageView: View {
8167

8268
ToolbarItem(placement: .navigationBarTrailing) {
8369
Button {
84-
store.send(.tapDoneButton)
85-
onDismiss?(store.preferences)
70+
store.send(.tapDoneButton, animation: .default)
8671
} label: {
8772
Text(String(localized: "profile_done"))
8873
}

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

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,11 @@ struct HomeFeature {
5353
let urlString: String
5454
}
5555

56-
enum Action: Equatable {
56+
enum Action: BindableAction, Equatable {
5757
case alert(PresentationAction<Never>)
5858
case sheet(PresentationAction<Sheet>)
5959
case fullScreenCover(PresentationAction<FullScreenCover>)
60+
case binding(BindingAction<State>)
6061
case view(ViewAction)
6162
case store(StoreAction)
6263
case loading(LoadingFeature.Action)
@@ -67,9 +68,8 @@ struct HomeFeature {
6768
case refreshRecentTodos
6869
case refreshWebPages
6970
case finishDeleteWebPageToast(String)
71+
case tapManageTodoCategory
7072
case tapTodoCategory(TodoCategory)
71-
case orderTodoCategory([TodoCategoryItem])
72-
case updateWebPageURLInput(String)
7373
case addWebPage
7474
case deleteWebPage(WebPageItem)
7575
case undoDeleteWebPage
@@ -106,9 +106,20 @@ struct HomeFeature {
106106
@ObservableState
107107
@CasePathable
108108
enum SheetState: Equatable {
109-
case reorderTodo
109+
case reorderTodo(CategoryManageFeature.State)
110110
case contentPicker(ContentPickerState)
111111

112+
var categoryManageState: CategoryManageFeature.State? {
113+
get {
114+
guard case .reorderTodo(let state) = self else { return nil }
115+
return state
116+
}
117+
set {
118+
guard let newValue else { return }
119+
self = .reorderTodo(newValue)
120+
}
121+
}
122+
112123
var contentPickerState: ContentPickerState? {
113124
get {
114125
guard case .contentPicker(let state) = self else { return nil }
@@ -124,6 +135,7 @@ struct HomeFeature {
124135
@CasePathable
125136
enum Sheet: Equatable {
126137
case tapCloseButton
138+
case categoryManage(CategoryManageFeature.Action)
127139
case contentPicker(ContentPicker)
128140

129141
@CasePathable
@@ -201,6 +213,7 @@ struct HomeFeature {
201213
Scope(state: \.loading, action: \.loading) {
202214
LoadingFeature()
203215
}
216+
BindingReducer()
204217
Reduce { state, action in
205218
switch action {
206219
case .alert:
@@ -219,8 +232,12 @@ struct HomeFeature {
219232
break
220233
case .sheet(.dismiss), .sheet(.presented(.tapCloseButton)):
221234
state.sheet = nil
235+
case .sheet(.presented(.categoryManage(.delegate(.done(let preferences))))):
236+
return orderTodoCategory(preferences, state: &state)
222237
case .sheet:
223238
break
239+
case .binding:
240+
break
224241
case .view(let action):
225242
return reduce(action, state: &state)
226243
case .store(let action):
@@ -276,17 +293,12 @@ private extension HomeFeature {
276293
if state.deletedWebPage?.urlString == urlString {
277294
state.deletedWebPage = nil
278295
}
296+
case .tapManageTodoCategory:
297+
state.sheet = .reorderTodo(CategoryManageFeature.State(preferences: state.preferences))
279298
case .tapTodoCategory(let category):
280299
state.selectedTodoCategory = category
281300
state.sheet = nil
282301
return delayedTodoEditorEffect()
283-
case .orderTodoCategory(let preferences):
284-
state.preferences = preferences
285-
state.recentTodos = Self.syncRecentTodos(state.recentTodos, preferences: preferences)
286-
state.sheet = nil
287-
return updateTodoCategoryPreferencesEffect(preferences)
288-
case .updateWebPageURLInput(let text):
289-
state.webPageURLInput = text
290302
case .addWebPage:
291303
guard let normalizedURL = Self.normalizedWebPageURL(state.webPageURLInput) else {
292304
Self.setAlert(&state, isPresented: true, type: .invalidURL)
@@ -316,6 +328,16 @@ private extension HomeFeature {
316328
return .none
317329
}
318330

331+
func orderTodoCategory(
332+
_ preferences: [TodoCategoryItem],
333+
state: inout State
334+
) -> Effect<Action> {
335+
state.preferences = preferences
336+
state.recentTodos = Self.syncRecentTodos(state.recentTodos, preferences: preferences)
337+
state.sheet = nil
338+
return updateTodoCategoryPreferencesEffect(preferences)
339+
}
340+
319341
func reduce(
320342
_ action: Action.StoreAction,
321343
state: inout State
@@ -359,6 +381,9 @@ private struct HomeSheetFeature: Reducer {
359381

360382
var body: some ReducerOf<Self> {
361383
EmptyReducer()
384+
.ifCaseLet(\.reorderTodo, action: \.categoryManage) {
385+
CategoryManageFeature()
386+
}
362387
.ifCaseLet(\.contentPicker, action: \.contentPicker) {
363388
HomeContentPickerFeature()
364389
}

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

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ struct HomeView: View {
5959
.bold()
6060
Spacer()
6161
Button(action: {
62-
store.send(.store(.setSheet(.reorderTodo)))
62+
store.send(.view(.tapManageTodoCategory))
6363
}) {
6464
Image(systemName: "ellipsis")
6565
.font(.title2)
@@ -164,8 +164,8 @@ struct HomeView: View {
164164

165165
@ViewBuilder
166166
private func sheetContent(_ sheetStore: Store<HomeFeature.SheetState, HomeFeature.Sheet>) -> some View {
167-
if let contentPickerStore = sheetStore.scope(state: \.contentPickerState, action: \.contentPicker) {
168-
@Bindable var contentPickerStore = contentPickerStore
167+
if let pickerStore = sheetStore.scope(state: \.contentPickerState, action: \.contentPicker) {
168+
@Bindable var pickerStore = pickerStore
169169
NavigationStack {
170170
List {
171171
Section {
@@ -193,7 +193,7 @@ struct HomeView: View {
193193

194194
Section {
195195
Button {
196-
contentPickerStore.send(.tapWebPageInput)
196+
pickerStore.send(.tapWebPageInput)
197197
} label: {
198198
labelImage(
199199
text: "URL",
@@ -208,16 +208,13 @@ struct HomeView: View {
208208
}
209209
}
210210
.navigationDestination(
211-
item: $contentPickerStore.scope(state: \.webPageInput, action: \.webPageInput)
211+
item: $pickerStore.scope(state: \.webPageInput, action: \.webPageInput)
212212
) { _ in
213213
Form {
214214
Section {
215215
TextField(
216216
"https://",
217-
text: Binding(
218-
get: { store.webPageURLInput },
219-
set: { store.send(.view(.updateWebPageURLInput($0))) }
220-
)
217+
text: $store.webPageURLInput
221218
)
222219
.textInputAutocapitalization(.never)
223220
.keyboardType(.URL)
@@ -258,13 +255,8 @@ struct HomeView: View {
258255
}
259256
}
260257
}
261-
} else {
262-
CategoryManageView(
263-
preferences: store.preferences,
264-
onDismiss: { array in
265-
store.send(.view(.orderTodoCategory(array)), animation: .default)
266-
}
267-
)
258+
} else if let store = sheetStore.scope(state: \.categoryManageState, action: \.categoryManage) {
259+
CategoryManageView(store: store)
268260
}
269261
}
270262

Application/Presentation/Tests/Home/CategoryManageFeatureTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,19 @@ struct CategoryManageFeatureTests {
164164

165165
#expect(driver.categorySheet == nil)
166166
}
167+
168+
@Test("완료 버튼을 누르면 현재 preferences를 delegate로 전달한다")
169+
func 완료_버튼을_누르면_현재_preferences를_delegate로_전달한다() async {
170+
let item = TodoCategoryItem(from: .system(.issue))
171+
let store = TestStore(
172+
initialState: CategoryManageFeature.State(preferences: [item])
173+
) {
174+
CategoryManageFeature()
175+
}
176+
177+
await store.send(.tapDoneButton)
178+
await store.receive(.delegate(.done([item])))
179+
}
167180
}
168181

169182
@MainActor

Application/Presentation/Tests/Home/HomeFeatureTestAssertions.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,16 @@ func verifyHomeOrderTodoCategory(
8787
TodoCategoryItem(from: .system(.feature))
8888
]
8989

90+
await adapter.tapManageTodoCategory()
91+
92+
#expect(adapter.showCategoryManage)
93+
9094
await adapter.orderTodoCategory(items)
9195

9296
#expect(adapter.preferences == items)
9397
#expect(adapter.recentTodos.last?.category == updatedCategory.category)
9498
#expect(updatePreferencesUseCaseSpy.updates == [items.map(\.preference)])
99+
#expect(!adapter.showCategoryManage)
95100
}
96101

97102
@MainActor

Application/Presentation/Tests/Home/HomeFeatureTestSupport.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ struct HomeStoreTestAdapter {
2323
var webPages: [WebPageItem] { store.state.webPages }
2424
var isNetworkConnected: Bool { store.state.isNetworkConnected }
2525
var showContentPicker: Bool { store.state.showContentPicker }
26+
var showCategoryManage: Bool {
27+
store.state.sheet?.categoryManageState != nil
28+
}
2629
var showWebPageInputNavigation: Bool {
2730
store.state.sheet?.contentPickerState?.webPageInput != nil
2831
}
@@ -113,13 +116,18 @@ struct HomeStoreTestAdapter {
113116
await drainReceivedActions()
114117
}
115118

119+
func tapManageTodoCategory() async {
120+
await store.send(.view(.tapManageTodoCategory))
121+
await drainReceivedActions()
122+
}
123+
116124
func orderTodoCategory(_ items: [TodoCategoryItem]) async {
117-
await store.send(.view(.orderTodoCategory(items)))
125+
await store.send(.sheet(.presented(.categoryManage(.delegate(.done(items))))))
118126
await drainReceivedActions()
119127
}
120128

121129
func updateWebPageURLInput(_ input: String) async {
122-
await store.send(.view(.updateWebPageURLInput(input)))
130+
await store.send(.binding(.set(\.webPageURLInput, input)))
123131
}
124132

125133
func addWebPage() async {

0 commit comments

Comments
 (0)