Skip to content

Commit ad09ae7

Browse files
authored
[#572] AccountView에 TCA를 적용한다 (#589)
* refactor: sheet에 별도 피쳐 적용 * refactor: BindingAction 적용 * feat: AccountFeature 구현 * refactor: AccountView를 TCA Store 주입으로 전환 * refactor: DependencyKey 개선
1 parent f4521ee commit ad09ae7

13 files changed

Lines changed: 747 additions & 262 deletions

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

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,14 @@ struct CategoryManageFeature {
9797
case tapEditUserCategory(TodoCategoryItem)
9898
case tapDeleteUserCategory(TodoCategoryItem)
9999
case tapDoneButton
100+
case setCategorySheet(CategorySheetState?)
100101

101102
enum Alert: Equatable {
102103
case confirmDeleteUserCategory(TodoCategoryItem)
103104
}
104105

105-
enum CategorySheet: Equatable {
106-
case setCategoryName(String)
107-
case setCategoryColor(String)
106+
enum CategorySheet: BindableAction, Equatable {
107+
case binding(BindingAction<CategorySheetState>)
108108
case tapCloseButton
109109
case tapRandomColorButton
110110
case tapSaveButton
@@ -124,14 +124,6 @@ struct CategoryManageFeature {
124124
state.categorySheet = nil
125125
case .categorySheet(.presented(.tapCloseButton)):
126126
state.categorySheet = nil
127-
case .categorySheet(.presented(.setCategoryName(let name))):
128-
state.categorySheet?.category.name = String(name.prefix(20))
129-
case .categorySheet(.presented(.setCategoryColor(let colorHex))):
130-
state.categorySheet?.category.colorHex = colorHex
131-
case .categorySheet(.presented(.tapRandomColorButton)):
132-
if let randomHexValue = Color.randomValue.hexValue {
133-
state.categorySheet?.category.colorHex = randomHexValue
134-
}
135127
case .categorySheet(.presented(.tapSaveButton)):
136128
if var item = state.categorySheet?.todoCategoryItem {
137129
if let index = state.preferences.firstIndex(where: { $0.id == item.id }) {
@@ -175,10 +167,39 @@ struct CategoryManageFeature {
175167
}
176168
case .tapDoneButton:
177169
break
170+
case .setCategorySheet(let sheet):
171+
state.categorySheet = sheet
178172
}
179173
return .none
180174
}
181175
.ifLet(\.$alert, action: \.alert)
176+
.ifLet(\.$categorySheet, action: \.categorySheet) {
177+
CategoryManageSheetFeature()
178+
}
179+
}
180+
}
181+
182+
private struct CategoryManageSheetFeature: Reducer {
183+
typealias State = CategoryManageFeature.CategorySheetState
184+
typealias Action = CategoryManageFeature.Action.CategorySheet
185+
186+
var body: some ReducerOf<Self> {
187+
BindingReducer()
188+
Reduce { state, action in
189+
switch action {
190+
case .binding(\.category.name):
191+
state.category.name = String(state.category.name.prefix(20))
192+
case .binding:
193+
break
194+
case .tapRandomColorButton:
195+
if let randomHexValue = Color.randomValue.hexValue {
196+
state.category.colorHex = randomHexValue
197+
}
198+
case .tapCloseButton, .tapSaveButton:
199+
break
200+
}
201+
return .none
202+
}
182203
}
183204
}
184205

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

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ struct CategoryManageView: View {
6666
.navigationTitle(String(localized: "nav_todo_manage"))
6767
.navigationBarTitleDisplayMode(.inline)
6868
.navigationBarBackButtonHidden()
69-
.sheet(item: $store.scope(state: \.categorySheet, action: \.categorySheet)) { store in
70-
CategoryManageSheet(store: store)
69+
.sheet(item: $store.scope(state: \.categorySheet, action: \.categorySheet)) { sheetStore in
70+
sheetContent(sheetStore)
7171
}
7272
.alert($store.scope(state: \.alert, action: \.alert))
7373
.toolbar {
@@ -91,10 +91,17 @@ struct CategoryManageView: View {
9191
}
9292
.presentationDragIndicator(.visible)
9393
}
94+
95+
@ViewBuilder
96+
private func sheetContent(
97+
_ sheetStore: Store<CategoryManageFeature.CategorySheetState, CategoryManageFeature.Action.CategorySheet>
98+
) -> some View {
99+
CategoryManageSheet(store: sheetStore)
100+
}
94101
}
95102

96103
private struct CategoryManageSheet: View {
97-
let store: Store<CategoryManageFeature.CategorySheetState, CategoryManageFeature.Action.CategorySheet>
104+
@Bindable var store: Store<CategoryManageFeature.CategorySheetState, CategoryManageFeature.Action.CategorySheet>
98105

99106
var body: some View {
100107
NavigationStack {
@@ -103,10 +110,7 @@ private struct CategoryManageSheet: View {
103110
HStack(spacing: 8) {
104111
TextField(
105112
"",
106-
text: Binding(
107-
get: { store.category.name },
108-
set: { store.send(.setCategoryName($0)) }
109-
),
113+
text: $store.category.name,
110114
prompt: Text(store.placeholder).foregroundStyle(.secondary)
111115
)
112116
.frame(height: UIFont.preferredFont(forTextStyle: .body).lineHeight)
@@ -119,21 +123,14 @@ private struct CategoryManageSheet: View {
119123
}
120124

121125
Section {
122-
let color = Color(hexString: store.category.colorHex) ?? .randomValue
123-
ColorPicker(selection: Binding(
124-
get: { color },
125-
set: {
126-
guard let hexValue = $0.hexValue else { return }
127-
store.send(.setCategoryColor(hexValue))
128-
}
129-
), supportsOpacity: false) {
126+
ColorPicker(selection: $store.category.colorHex.colorValue, supportsOpacity: false) {
130127
Text(store.category.colorHex.isEmpty ? "#" : store.category.colorHex)
131128
.overlay(alignment: .bottom) {
132129
Rectangle()
133130
.frame(height: 1)
134131
.offset(y: 1)
135132
}
136-
.foregroundStyle(color)
133+
.foregroundStyle(store.category.colorHex.colorValue)
137134
.onTapGesture {
138135
store.send(.tapRandomColorButton)
139136
}
@@ -160,3 +157,14 @@ private struct CategoryManageSheet: View {
160157
}
161158
}
162159
}
160+
161+
private extension String {
162+
var colorValue: Color {
163+
get { Color(hexString: self) ?? .randomValue }
164+
set {
165+
if let hexValue = newValue.hexValue {
166+
self = hexValue
167+
}
168+
}
169+
}
170+
}

Application/DevLogPresentation/Sources/Login/LoginFeature.swift

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ struct LoginFeature {
2929
case error
3030
}
3131

32-
@Dependency(SignInUseCaseDependency.self) var signInUseCase
32+
@Dependency(\.signInUseCase) var signInUseCase
3333

3434
var body: some ReducerOf<Self> {
3535
Reduce { state, action in
@@ -61,32 +61,20 @@ struct LoginFeature {
6161
}
6262
}
6363

64-
struct SignInUseCaseDependency {
65-
var execute: (AuthProvider) async throws -> Void
66-
67-
init(execute: @escaping (AuthProvider) async throws -> Void) {
68-
self.execute = execute
64+
extension DependencyValues {
65+
var signInUseCase: SignInUseCase {
66+
get { self[SignInUseCaseKey.self] }
67+
set { self[SignInUseCaseKey.self] = newValue }
6968
}
7069
}
7170

72-
extension SignInUseCaseDependency: DependencyKey {
73-
static let liveValue = Self { _ in
74-
preconditionFailure("SignInUseCaseDependency must be provided.")
75-
}
76-
77-
static let testValue = liveValue
78-
79-
static func live(_ signInUseCase: SignInUseCase) -> SignInUseCaseDependency {
80-
Self {
81-
try await signInUseCase.execute($0)
82-
}
71+
private enum SignInUseCaseKey: DependencyKey {
72+
static var liveValue: SignInUseCase {
73+
preconditionFailure("SignInUseCase must be provided.")
8374
}
84-
}
8575

86-
extension DependencyValues {
87-
var signInUseCase: SignInUseCaseDependency {
88-
get { self[SignInUseCaseDependency.self] }
89-
set { self[SignInUseCaseDependency.self] = newValue }
76+
static var testValue: SignInUseCase {
77+
liveValue
9078
}
9179
}
9280

Application/DevLogPresentation/Sources/Login/LoginView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ struct LoginView: View {
2020
) {
2121
LoginFeature()
2222
} withDependencies: {
23-
$0.signInUseCase = .live(signInUseCase)
23+
$0.signInUseCase = signInUseCase
2424
})
2525
}
2626

Application/DevLogPresentation/Sources/Main/MainView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ struct MainView: View {
375375
viewModel: profileViewCoordinator.makePushNotificationSettingsViewModel()
376376
)
377377
case .account:
378-
AccountView(viewModel: profileViewCoordinator.makeAccountViewModel())
378+
AccountView(store: profileViewCoordinator.makeAccountStore())
379379
}
380380
}
381381
}

Application/DevLogPresentation/Sources/Profile/ProfileView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ struct ProfileView: View {
172172
case .pushNotification:
173173
PushNotificationSettingsView(viewModel: coordinator.makePushNotificationSettingsViewModel())
174174
case .account:
175-
AccountView(viewModel: coordinator.makeAccountViewModel())
175+
AccountView(store: coordinator.makeAccountStore())
176176
}
177177
}
178178

Application/DevLogPresentation/Sources/Profile/ProfileViewCoordinator.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,14 @@ final class ProfileViewCoordinator {
4444
viewModel.send(.fetchData)
4545
}
4646

47-
func makeAccountViewModel() -> AccountViewModel {
48-
AccountViewModel(
49-
fetchProvidersUseCase: container.resolve(FetchAuthProvidersUseCase.self),
50-
linkProviderUseCase: container.resolve(LinkAuthProviderUseCase.self),
51-
unlinkProviderUseCase: container.resolve(UnlinkAuthProviderUseCase.self)
52-
)
47+
func makeAccountStore() -> StoreOf<AccountFeature> {
48+
Store(initialState: AccountFeature.State()) {
49+
AccountFeature()
50+
} withDependencies: {
51+
$0.fetchAuthProvidersUseCase = self.container.resolve(FetchAuthProvidersUseCase.self)
52+
$0.linkAuthProviderUseCase = self.container.resolve(LinkAuthProviderUseCase.self)
53+
$0.unlinkAuthProviderUseCase = self.container.resolve(UnlinkAuthProviderUseCase.self)
54+
}
5355
}
5456

5557
func makePushNotificationSettingsViewModel() -> PushNotificationSettingsViewModel {

0 commit comments

Comments
 (0)