Skip to content

Commit 273ea71

Browse files
authored
[#435] TodayView에 Coordinator을 구성한다 (#445)
* refactor: TodayView 전용 Coordinator 구현 및 적용 * refactor: 불필요 라우터 주입 제거 * fix: row의 컨텐츠를 정확하게 탭 해야 버튼이 작동하는 현상 해결 * refactor: 불필요 모디파이어 제거
1 parent ed65075 commit 273ea71

4 files changed

Lines changed: 58 additions & 44 deletions

File tree

DevLog/UI/Main/MainView.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ struct MainView: View {
1111
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
1212
@State private var coordinator: MainViewCoordinator
1313
@State private var homeViewCoordinator: HomeViewCoordinator
14+
@State private var todayViewCoordinator: TodayViewCoordinator
1415
@Binding var selectedTab: MainTab
1516
private let container: DIContainer
1617

@@ -21,6 +22,7 @@ struct MainView: View {
2122
self.container = container
2223
self._coordinator = State(initialValue: MainViewCoordinator(container: container))
2324
self._homeViewCoordinator = State(initialValue: HomeViewCoordinator(container: container))
25+
self._todayViewCoordinator = State(initialValue: TodayViewCoordinator(container: container))
2426
self._selectedTab = selectedTab
2527
}
2628

@@ -102,7 +104,6 @@ struct MainView: View {
102104
} detail: {
103105
todayRegularDetailView
104106
}
105-
.environment(coordinator.todayNavigationRouter)
106107
case .notification:
107108
NavigationSplitView {
108109
mainSidebar
@@ -268,12 +269,11 @@ struct MainView: View {
268269
todayContentView
269270
}
270271
}
271-
.environment(coordinator.todayNavigationRouter)
272272
}
273273

274274
private var todayContentView: some View {
275275
TodayView(
276-
viewModel: coordinator.todayViewModel,
276+
coordinator: todayViewCoordinator,
277277
isCompactLayout: isCompactLayout
278278
)
279279
}
@@ -282,7 +282,7 @@ struct MainView: View {
282282
private var todayRegularDetailView: some View {
283283
NavigationStack(path: todayDetailPath) {
284284
Group {
285-
if let todayRoute = coordinator.todayNavigationRouter.root {
285+
if let todayRoute = todayViewCoordinator.router.root {
286286
todayDestinationView(todayRoute)
287287
} else {
288288
ContentUnavailableView(
@@ -366,15 +366,15 @@ private extension MainView {
366366

367367
var todayNavigationPath: Binding<[TodayRoute]> {
368368
Binding(
369-
get: { coordinator.todayNavigationRouter.path },
370-
set: { coordinator.todayNavigationRouter.path = $0 }
369+
get: { todayViewCoordinator.router.path },
370+
set: { todayViewCoordinator.router.path = $0 }
371371
)
372372
}
373373

374374
var todayDetailPath: Binding<[TodayRoute]> {
375375
Binding(
376-
get: { coordinator.todayNavigationRouter.detailPath },
377-
set: { coordinator.todayNavigationRouter.detailPath = $0 }
376+
get: { todayViewCoordinator.router.detailPath },
377+
set: { todayViewCoordinator.router.detailPath = $0 }
378378
)
379379
}
380380

DevLog/UI/Main/MainViewCoordinator.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,14 @@ import Foundation
1111
@Observable
1212
final class MainViewCoordinator {
1313
let mainViewModel: MainViewModel
14-
let todayViewModel: TodayViewModel
1514
let pushNotificationListViewModel: PushNotificationListViewModel
1615
let profileViewModel: ProfileViewModel
17-
let todayNavigationRouter = NavigationRouter<TodayRoute>()
1816
var todoIdToPresent: TodoIdItem?
1917

2018
init(container: DIContainer) {
2119
self.mainViewModel = MainViewModel(
2220
unreadPushCountUseCase: container.resolve(ObserveUnreadPushCountUseCase.self)
2321
)
24-
self.todayViewModel = TodayViewModel(
25-
fetchTodosUseCase: container.resolve(FetchTodosUseCase.self),
26-
fetchTodoByIdUseCase: container.resolve(FetchTodoByIdUseCase.self),
27-
upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self),
28-
fetchTodayDisplayOptionsUseCase: container.resolve(FetchTodayDisplayOptionsUseCase.self),
29-
updateTodayDisplayOptionsUseCase: container.resolve(UpdateTodayDisplayOptionsUseCase.self)
30-
)
3122
self.pushNotificationListViewModel = PushNotificationListViewModel(
3223
fetchUseCase: container.resolve(FetchPushNotificationsUseCase.self),
3324
deleteUseCase: container.resolve(DeletePushNotificationUseCase.self),

DevLog/UI/Today/TodayView.swift

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,16 @@
88
import SwiftUI
99

1010
struct TodayView: View {
11-
@Environment(NavigationRouter<TodayRoute>.self) private var router
12-
@State var viewModel: TodayViewModel
11+
let coordinator: TodayViewCoordinator
1312
let isCompactLayout: Bool
1413

1514
var body: some View {
1615
List {
1716
summarySection
18-
if viewModel.sections.isEmpty, !viewModel.state.isLoading {
17+
if coordinator.viewModel.sections.isEmpty, !coordinator.viewModel.state.isLoading {
1918
emptySection
2019
} else {
21-
ForEach(viewModel.sections) { section in
20+
ForEach(coordinator.viewModel.sections) { section in
2221
todoSection(section.title, items: section.items)
2322
}
2423
}
@@ -27,21 +26,21 @@ struct TodayView: View {
2726
.navigationTitle(String(localized: "nav_today"))
2827
.toolbar { toolbarContent }
2928
.background(NavigationBarConfigurator())
30-
.refreshable { viewModel.send(.refresh) }
31-
.onAppear { viewModel.send(.onAppear) }
29+
.refreshable { coordinator.viewModel.send(.refresh) }
30+
.onAppear { coordinator.viewModel.send(.onAppear) }
3231
.alert(
33-
viewModel.state.alertTitle,
32+
coordinator.viewModel.state.alertTitle,
3433
isPresented: Binding(
35-
get: { viewModel.state.showAlert },
36-
set: { viewModel.send(.setAlert($0)) }
34+
get: { coordinator.viewModel.state.showAlert },
35+
set: { coordinator.viewModel.send(.setAlert($0)) }
3736
)
3837
) {
3938
Button(String(localized: "common_close"), role: .cancel) { }
4039
} message: {
41-
Text(viewModel.state.alertMessage)
40+
Text(coordinator.viewModel.state.alertMessage)
4241
}
4342
.overlay {
44-
if viewModel.state.isLoading {
43+
if coordinator.viewModel.state.isLoading {
4544
LoadingView()
4645
}
4746
}
@@ -54,14 +53,14 @@ struct TodayView: View {
5453
ForEach(TodayViewModel.SectionScope.allCases, id: \.self) { scope in
5554
Button {
5655
withAnimation(.easeInOut) {
57-
viewModel.send(.setSectionScope(scope))
56+
coordinator.viewModel.send(.setSectionScope(scope))
5857
}
5958
} label: {
6059
SummaryCard(
6160
title: scope.title,
62-
value: viewModel.summaryValue(for: scope),
61+
value: coordinator.viewModel.summaryValue(for: scope),
6362
accentColor: scope.accentColor,
64-
isSelected: viewModel.state.selectedSectionScope == scope
63+
isSelected: coordinator.viewModel.state.selectedSectionScope == scope
6564
)
6665
}
6766
.buttonStyle(.plain)
@@ -81,8 +80,8 @@ struct TodayView: View {
8180
Picker(
8281
String(localized: "today_due_visibility_label"),
8382
selection: Binding(
84-
get: { viewModel.state.displayOptions.dueDateVisibility },
85-
set: { viewModel.send(.setDueDateVisibility($0)) }
83+
get: { coordinator.viewModel.state.displayOptions.dueDateVisibility },
84+
set: { coordinator.viewModel.send(.setDueDateVisibility($0)) }
8685
)
8786
) {
8887
ForEach(TodayDisplayOptions.DueDateVisibility.allCases, id: \.self) { option in
@@ -93,20 +92,20 @@ struct TodayView: View {
9392
Toggle(
9493
String(localized: "today_pinned_only"),
9594
isOn: Binding(
96-
get: { viewModel.state.displayOptions.focusVisibility == .focusedOnly },
95+
get: { coordinator.viewModel.state.displayOptions.focusVisibility == .focusedOnly },
9796
set: {
98-
viewModel.send(.setFocusVisibility($0 ? .focusedOnly : .all))
97+
coordinator.viewModel.send(.setFocusVisibility($0 ? .focusedOnly : .all))
9998
}
10099
)
101100
)
102101
.tint(.orange)
103102

104-
if viewModel.state.displayOptions.focusVisibility == .focusedOnly {
103+
if coordinator.viewModel.state.displayOptions.focusVisibility == .focusedOnly {
105104
Text(String(localized: "today_pinned_only_description"))
106105
.font(.caption)
107106
}
108107
} label: {
109-
let options = viewModel.state.displayOptions
108+
let options = coordinator.viewModel.state.displayOptions
110109
Image(systemName: "line.3.horizontal.decrease.circle\(options == .default ? "" : ".fill")")
111110
}
112111
}
@@ -135,15 +134,15 @@ struct TodayView: View {
135134
todoRow(item)
136135
.swipeActions(edge: .leading, allowsFullSwipe: false) {
137136
Button {
138-
viewModel.send(.togglePinned(item))
137+
coordinator.viewModel.send(.togglePinned(item))
139138
} label: {
140139
Image(systemName: item.isPinned ? "star.slash" : "star.fill")
141140
}
142141
.tint(.orange)
143142
}
144143
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
145144
Button {
146-
viewModel.send(.completeTodo(item))
145+
coordinator.viewModel.send(.completeTodo(item))
147146
} label: {
148147
Label(String(localized: "today_complete_action"), systemImage: "checkmark")
149148
}
@@ -162,24 +161,23 @@ struct TodayView: View {
162161
if isCompactLayout {
163162
NavigationLink(value: TodayRoute.todo(TodoIdItem(id: item.id))) {
164163
TodayTodoRow(item: item)
165-
.listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
166164
}
167165
} else {
168166
Button {
169-
router.replace(with: .todo(TodoIdItem(id: item.id)))
167+
coordinator.router.replace(with: .todo(TodoIdItem(id: item.id)))
170168
} label: {
171169
TodayTodoRow(item: item)
172170
.frame(maxWidth: .infinity, alignment: .leading)
173-
.listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
171+
.contentShape(.rect)
174172
}
175173
.buttonStyle(.plain)
176174
}
177175
}
178176

179177
private var emptyStateContent: EmptyStateContent {
180-
switch viewModel.state.selectedSectionScope {
178+
switch coordinator.viewModel.state.selectedSectionScope {
181179
case .all:
182-
if viewModel.state.todos.isEmpty {
180+
if coordinator.viewModel.state.todos.isEmpty {
183181
return EmptyStateContent(
184182
title: String(localized: "today_empty_all_title"),
185183
message: String(localized: "today_empty_all_message")
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// TodayViewCoordinator.swift
3+
// DevLog
4+
//
5+
// Created by opfic on 5/10/26.
6+
//
7+
8+
import Foundation
9+
10+
@MainActor
11+
@Observable
12+
final class TodayViewCoordinator {
13+
let viewModel: TodayViewModel
14+
let router = NavigationRouter<TodayRoute>()
15+
16+
init(container: DIContainer) {
17+
self.viewModel = TodayViewModel(
18+
fetchTodosUseCase: container.resolve(FetchTodosUseCase.self),
19+
fetchTodoByIdUseCase: container.resolve(FetchTodoByIdUseCase.self),
20+
upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self),
21+
fetchTodayDisplayOptionsUseCase: container.resolve(FetchTodayDisplayOptionsUseCase.self),
22+
updateTodayDisplayOptionsUseCase: container.resolve(UpdateTodayDisplayOptionsUseCase.self)
23+
)
24+
}
25+
}

0 commit comments

Comments
 (0)