Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions DevLog/UI/Main/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct MainView: View {
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
@State private var coordinator: MainViewCoordinator
@State private var homeViewCoordinator: HomeViewCoordinator
@State private var todayViewCoordinator: TodayViewCoordinator
@Binding var selectedTab: MainTab
private let container: DIContainer

Expand All @@ -21,6 +22,7 @@ struct MainView: View {
self.container = container
self._coordinator = State(initialValue: MainViewCoordinator(container: container))
self._homeViewCoordinator = State(initialValue: HomeViewCoordinator(container: container))
self._todayViewCoordinator = State(initialValue: TodayViewCoordinator(container: container))
self._selectedTab = selectedTab
}

Expand Down Expand Up @@ -102,7 +104,6 @@ struct MainView: View {
} detail: {
todayRegularDetailView
}
.environment(coordinator.todayNavigationRouter)
case .notification:
NavigationSplitView {
mainSidebar
Expand Down Expand Up @@ -268,12 +269,11 @@ struct MainView: View {
todayContentView
}
}
.environment(coordinator.todayNavigationRouter)
}

private var todayContentView: some View {
TodayView(
viewModel: coordinator.todayViewModel,
coordinator: todayViewCoordinator,
isCompactLayout: isCompactLayout
)
}
Expand All @@ -282,7 +282,7 @@ struct MainView: View {
private var todayRegularDetailView: some View {
NavigationStack(path: todayDetailPath) {
Group {
if let todayRoute = coordinator.todayNavigationRouter.root {
if let todayRoute = todayViewCoordinator.router.root {
todayDestinationView(todayRoute)
} else {
ContentUnavailableView(
Expand Down Expand Up @@ -366,15 +366,15 @@ private extension MainView {

var todayNavigationPath: Binding<[TodayRoute]> {
Binding(
get: { coordinator.todayNavigationRouter.path },
set: { coordinator.todayNavigationRouter.path = $0 }
get: { todayViewCoordinator.router.path },
set: { todayViewCoordinator.router.path = $0 }
)
}

var todayDetailPath: Binding<[TodayRoute]> {
Binding(
get: { coordinator.todayNavigationRouter.detailPath },
set: { coordinator.todayNavigationRouter.detailPath = $0 }
get: { todayViewCoordinator.router.detailPath },
set: { todayViewCoordinator.router.detailPath = $0 }
)
}

Expand Down
9 changes: 0 additions & 9 deletions DevLog/UI/Main/MainViewCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,14 @@ import Foundation
@Observable
final class MainViewCoordinator {
let mainViewModel: MainViewModel
let todayViewModel: TodayViewModel
let pushNotificationListViewModel: PushNotificationListViewModel
let profileViewModel: ProfileViewModel
let todayNavigationRouter = NavigationRouter<TodayRoute>()
var todoIdToPresent: TodoIdItem?

init(container: DIContainer) {
self.mainViewModel = MainViewModel(
unreadPushCountUseCase: container.resolve(ObserveUnreadPushCountUseCase.self)
)
self.todayViewModel = TodayViewModel(
fetchTodosUseCase: container.resolve(FetchTodosUseCase.self),
fetchTodoByIdUseCase: container.resolve(FetchTodoByIdUseCase.self),
upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self),
fetchTodayDisplayOptionsUseCase: container.resolve(FetchTodayDisplayOptionsUseCase.self),
updateTodayDisplayOptionsUseCase: container.resolve(UpdateTodayDisplayOptionsUseCase.self)
)
self.pushNotificationListViewModel = PushNotificationListViewModel(
fetchUseCase: container.resolve(FetchPushNotificationsUseCase.self),
deleteUseCase: container.resolve(DeletePushNotificationUseCase.self),
Expand Down
52 changes: 25 additions & 27 deletions DevLog/UI/Today/TodayView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@
import SwiftUI

struct TodayView: View {
@Environment(NavigationRouter<TodayRoute>.self) private var router
@State var viewModel: TodayViewModel
let coordinator: TodayViewCoordinator
let isCompactLayout: Bool

var body: some View {
List {
summarySection
if viewModel.sections.isEmpty, !viewModel.state.isLoading {
if coordinator.viewModel.sections.isEmpty, !coordinator.viewModel.state.isLoading {
emptySection
} else {
ForEach(viewModel.sections) { section in
ForEach(coordinator.viewModel.sections) { section in
todoSection(section.title, items: section.items)
}
}
Expand All @@ -27,21 +26,21 @@ struct TodayView: View {
.navigationTitle(String(localized: "nav_today"))
.toolbar { toolbarContent }
.background(NavigationBarConfigurator())
.refreshable { viewModel.send(.refresh) }
.onAppear { viewModel.send(.onAppear) }
.refreshable { coordinator.viewModel.send(.refresh) }
.onAppear { coordinator.viewModel.send(.onAppear) }
.alert(
viewModel.state.alertTitle,
coordinator.viewModel.state.alertTitle,
isPresented: Binding(
get: { viewModel.state.showAlert },
set: { viewModel.send(.setAlert($0)) }
get: { coordinator.viewModel.state.showAlert },
set: { coordinator.viewModel.send(.setAlert($0)) }
)
) {
Button(String(localized: "common_close"), role: .cancel) { }
} message: {
Text(viewModel.state.alertMessage)
Text(coordinator.viewModel.state.alertMessage)
}
.overlay {
if viewModel.state.isLoading {
if coordinator.viewModel.state.isLoading {
LoadingView()
}
}
Expand All @@ -54,14 +53,14 @@ struct TodayView: View {
ForEach(TodayViewModel.SectionScope.allCases, id: \.self) { scope in
Button {
withAnimation(.easeInOut) {
viewModel.send(.setSectionScope(scope))
coordinator.viewModel.send(.setSectionScope(scope))
}
} label: {
SummaryCard(
title: scope.title,
value: viewModel.summaryValue(for: scope),
value: coordinator.viewModel.summaryValue(for: scope),
accentColor: scope.accentColor,
isSelected: viewModel.state.selectedSectionScope == scope
isSelected: coordinator.viewModel.state.selectedSectionScope == scope
)
}
.buttonStyle(.plain)
Expand All @@ -81,8 +80,8 @@ struct TodayView: View {
Picker(
String(localized: "today_due_visibility_label"),
selection: Binding(
get: { viewModel.state.displayOptions.dueDateVisibility },
set: { viewModel.send(.setDueDateVisibility($0)) }
get: { coordinator.viewModel.state.displayOptions.dueDateVisibility },
set: { coordinator.viewModel.send(.setDueDateVisibility($0)) }
)
) {
ForEach(TodayDisplayOptions.DueDateVisibility.allCases, id: \.self) { option in
Expand All @@ -93,20 +92,20 @@ struct TodayView: View {
Toggle(
String(localized: "today_pinned_only"),
isOn: Binding(
get: { viewModel.state.displayOptions.focusVisibility == .focusedOnly },
get: { coordinator.viewModel.state.displayOptions.focusVisibility == .focusedOnly },
set: {
viewModel.send(.setFocusVisibility($0 ? .focusedOnly : .all))
coordinator.viewModel.send(.setFocusVisibility($0 ? .focusedOnly : .all))
}
)
)
.tint(.orange)

if viewModel.state.displayOptions.focusVisibility == .focusedOnly {
if coordinator.viewModel.state.displayOptions.focusVisibility == .focusedOnly {
Text(String(localized: "today_pinned_only_description"))
.font(.caption)
}
} label: {
let options = viewModel.state.displayOptions
let options = coordinator.viewModel.state.displayOptions
Image(systemName: "line.3.horizontal.decrease.circle\(options == .default ? "" : ".fill")")
}
}
Expand Down Expand Up @@ -135,15 +134,15 @@ struct TodayView: View {
todoRow(item)
.swipeActions(edge: .leading, allowsFullSwipe: false) {
Button {
viewModel.send(.togglePinned(item))
coordinator.viewModel.send(.togglePinned(item))
} label: {
Image(systemName: item.isPinned ? "star.slash" : "star.fill")
}
.tint(.orange)
}
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button {
viewModel.send(.completeTodo(item))
coordinator.viewModel.send(.completeTodo(item))
} label: {
Label(String(localized: "today_complete_action"), systemImage: "checkmark")
}
Expand All @@ -162,24 +161,23 @@ struct TodayView: View {
if isCompactLayout {
NavigationLink(value: TodayRoute.todo(TodoIdItem(id: item.id))) {
TodayTodoRow(item: item)
.listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
}
} else {
Button {
router.replace(with: .todo(TodoIdItem(id: item.id)))
coordinator.router.replace(with: .todo(TodoIdItem(id: item.id)))
} label: {
TodayTodoRow(item: item)
.frame(maxWidth: .infinity, alignment: .leading)
.listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
.contentShape(.rect)
}
.buttonStyle(.plain)
}
Comment thread
opficdev marked this conversation as resolved.
}

private var emptyStateContent: EmptyStateContent {
switch viewModel.state.selectedSectionScope {
switch coordinator.viewModel.state.selectedSectionScope {
case .all:
if viewModel.state.todos.isEmpty {
if coordinator.viewModel.state.todos.isEmpty {
return EmptyStateContent(
title: String(localized: "today_empty_all_title"),
message: String(localized: "today_empty_all_message")
Expand Down
25 changes: 25 additions & 0 deletions DevLog/UI/Today/TodayViewCoordinator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// TodayViewCoordinator.swift
// DevLog
//
// Created by opfic on 5/10/26.
//

import Foundation

@MainActor
@Observable
final class TodayViewCoordinator {
let viewModel: TodayViewModel
let router = NavigationRouter<TodayRoute>()

init(container: DIContainer) {
self.viewModel = TodayViewModel(
fetchTodosUseCase: container.resolve(FetchTodosUseCase.self),
fetchTodoByIdUseCase: container.resolve(FetchTodoByIdUseCase.self),
upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self),
fetchTodayDisplayOptionsUseCase: container.resolve(FetchTodayDisplayOptionsUseCase.self),
updateTodayDisplayOptionsUseCase: container.resolve(UpdateTodayDisplayOptionsUseCase.self)
)
}
}