Skip to content

Commit 04c7b24

Browse files
authored
[#574] SearchView에 TCA를 적용한다 (#592)
* refactor: SearchView TCA 적용 * test: SearchFeature 테스트 추가 * refactor: BindingAction 적용 * refactor: LoadingFeature 적용 * refactor: LoginFeature 적용 * refactor: TCA helper 스타일 통일 * style: 주석 추가
1 parent 5889f7b commit 04c7b24

13 files changed

Lines changed: 875 additions & 376 deletions

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ struct CategoryManageFeature {
163163
}
164164
case .tapDeleteUserCategory(let item):
165165
if item.isUserCategory {
166-
state.alert = deleteAlertState(for: item)
166+
state.alert = Self.deleteAlertState(for: item)
167167
}
168168
case .tapDoneButton:
169169
break
@@ -204,7 +204,7 @@ private struct CategoryManageSheetFeature: Reducer {
204204
}
205205

206206
private extension CategoryManageFeature {
207-
func deleteAlertState(for item: TodoCategoryItem) -> AlertState<Action.Alert> {
207+
static func deleteAlertState(for item: TodoCategoryItem) -> AlertState<Action.Alert> {
208208
AlertState {
209209
TextState(String(localized: "todo_manage_delete_category_title"))
210210
} actions: {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ struct TodoDetailFeature {
108108
case .onAppear:
109109
return fetchTodoEffect(todoId: state.todoId)
110110
case .fetchFailed:
111-
state.alert = alertState()
111+
state.alert = Self.alertState()
112112
case .setSheet(let sheet):
113113
state.sheet = sheet
114114
case .setFullScreenCover(let cover):
@@ -209,7 +209,7 @@ private extension TodoDetailFeature {
209209
}
210210
}
211211

212-
func alertState() -> AlertState<Never> {
212+
static func alertState() -> AlertState<Never> {
213213
AlertState {
214214
TextState(String(localized: "common_error_title"))
215215
} actions: {

Application/DevLogPresentation/Sources/Home/Home/HomeView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ struct HomeView: View {
5858
get: { coordinator.viewModel.state.showSearchView },
5959
set: { coordinator.viewModel.send(.setPresentation(.searchView, $0)) }
6060
)) {
61-
SearchView(viewModel: coordinator.makeSearchViewModel())
61+
SearchView(store: coordinator.makeSearchStore())
6262
}
6363
.alert(
6464
coordinator.viewModel.state.alertTitle,

Application/DevLogPresentation/Sources/Home/Home/HomeViewCoordinator.swift

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import Combine
9+
import ComposableArchitecture
910
import Foundation
1011
import DevLogCore
1112
import DevLogDomain
@@ -90,12 +91,17 @@ final class HomeViewCoordinator {
9091
)
9192
}
9293

93-
func makeSearchViewModel() -> SearchViewModel {
94-
SearchViewModel(
95-
fetchWebPagesUseCase: container.resolve(FetchWebPagesUseCase.self),
96-
fetchTodosUseCase: container.resolve(FetchTodosUseCase.self),
97-
fetchRecentSearchQueriesUseCase: container.resolve(FetchRecentSearchQueriesUseCase.self),
98-
updateRecentSearchQueriesUseCase: container.resolve(UpdateRecentSearchQueriesUseCase.self)
99-
)
94+
func makeSearchStore() -> StoreOf<SearchFeature> {
95+
Store(
96+
initialState: SearchFeature.State(
97+
recentQueries: container.resolve(FetchRecentSearchQueriesUseCase.self).execute()
98+
)
99+
) {
100+
SearchFeature()
101+
} withDependencies: {
102+
$0.searchFetchWebPagesUseCase = self.container.resolve(FetchWebPagesUseCase.self)
103+
$0.searchFetchTodosUseCase = self.container.resolve(FetchTodosUseCase.self)
104+
$0.searchUpdateRecentQueriesUseCase = self.container.resolve(UpdateRecentSearchQueriesUseCase.self)
105+
}
100106
}
101107
}

Application/DevLogPresentation/Sources/Login/LoginFeature.swift

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,18 @@ struct LoginFeature {
1414
@ObservableState
1515
struct State: Equatable {
1616
@Presents var alert: AlertState<Never>?
17-
var isLoading = false
17+
var loading = LoadingFeature.State()
18+
19+
var isLoading: Bool {
20+
loading.isLoading
21+
}
1822
}
1923

2024
enum Action {
2125
case alert(PresentationAction<Never>)
2226
case tapSignInButton(AuthProvider)
2327
case signInFailed(AlertType)
24-
case signInCancelled
28+
case loading(LoadingFeature.Action)
2529
}
2630

2731
enum AlertType: Equatable {
@@ -32,28 +36,19 @@ struct LoginFeature {
3236
@Dependency(\.signInUseCase) var signInUseCase
3337

3438
var body: some ReducerOf<Self> {
39+
Scope(state: \.loading, action: \.loading) {
40+
LoadingFeature()
41+
}
3542
Reduce { state, action in
3643
switch action {
3744
case .alert:
3845
break
3946
case .tapSignInButton(let provider):
40-
state.isLoading = true
41-
return .run { [signInUseCase] send in
42-
do {
43-
try await signInUseCase.execute(provider)
44-
} catch {
45-
if error.isSocialLoginCancelled {
46-
await send(.signInCancelled)
47-
return
48-
}
49-
await send(.signInFailed(alertType(for: error)))
50-
}
51-
}
52-
case .signInCancelled:
53-
state.isLoading = false
47+
return signInEffect(provider)
5448
case .signInFailed(let alertType):
55-
state.isLoading = false
56-
state.alert = alertState(for: alertType)
49+
state.alert = Self.alertState(for: alertType)
50+
case .loading:
51+
break
5752
}
5853
return .none
5954
}
@@ -79,7 +74,21 @@ private enum SignInUseCaseKey: DependencyKey {
7974
}
8075

8176
private extension LoginFeature {
82-
func alertState(for alertType: AlertType) -> AlertState<Never> {
77+
func signInEffect(_ provider: AuthProvider) -> Effect<Action> {
78+
.run { [signInUseCase] send in
79+
await send(.loading(.begin(target: .default, mode: .immediate)))
80+
do {
81+
try await signInUseCase.execute(provider)
82+
// 유스케이스 완료가 화면 전환 완료를 의미하지 않으므로 LoginView가 교체될 때까지 로딩을 유지한다.
83+
} catch {
84+
await send(.loading(.end(target: .default, mode: .immediate)))
85+
if error.isSocialLoginCancelled { return }
86+
await send(.signInFailed(Self.alertType(for: error)))
87+
}
88+
}
89+
}
90+
91+
static func alertState(for alertType: AlertType) -> AlertState<Never> {
8392
let title: String
8493
let message: String
8594

@@ -103,7 +112,7 @@ private extension LoginFeature {
103112
}
104113
}
105114

106-
func alertType(for error: Error) -> AlertType {
115+
static func alertType(for error: Error) -> AlertType {
107116
if case AuthError.emailNotFound = error {
108117
return .emailUnavailable
109118
}

0 commit comments

Comments
 (0)