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: 16 additions & 0 deletions Application/DevLogApp/Sources/App/DevLogApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct DevLogApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
@Environment(\.diContainer) var container: DIContainer
@Environment(\.scenePhase) var scenePhase
@State private var windowEvent = TodoEditorWindowEvent()

init() {
AppAssembler().assemble(AppDIContainer.shared)
Expand All @@ -29,6 +30,7 @@ struct DevLogApp: App {
systemThemeUseCase: container.resolve(ObserveSystemThemeUseCase.self),
trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self),
widgetURLTab: { MainTab(widgetURL: $0) },
windowEvent: windowEvent,
pushNotificationTodoIdPublisher: PushNotificationRoute.shared.observe(),
clearPushNotificationRoute: { PushNotificationRoute.shared.clear() }
)
Expand All @@ -38,5 +40,19 @@ struct DevLogApp: App {
container.resolve(WidgetSyncEventBus.self).publish(.syncRequested)
}
}
WindowGroup(id: TodoEditorWindowValue.sceneId, for: TodoEditorWindowValue.self) { value in
if let value = value.wrappedValue {
TodoEditorWindowView(
value: value,
windowEvent: windowEvent
)
.autocorrectionDisabled()
} else {
ContentUnavailableView(
String(localized: "todo_edit"),
systemImage: "square.and.pencil"
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ extension EnvironmentValues {
self[SceneHeightKey.self]
}

var isiOSAppOnMac: Bool {
self[IOSAppOnMacKey.self]
}

private struct SafeAreaInsetsKey: EnvironmentKey {
static var defaultValue: EdgeInsets {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
Expand Down Expand Up @@ -48,6 +52,12 @@ extension EnvironmentValues {
return windowScene.screen.bounds.height
}
}

private struct IOSAppOnMacKey: EnvironmentKey {
static var defaultValue: Bool {
ProcessInfo.processInfo.isiOSAppOnMac
}
}
}

extension UIEdgeInsets {
Expand Down
21 changes: 18 additions & 3 deletions Application/DevLogPresentation/Sources/Home/Home/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import SwiftUI
import DevLogDomain

struct HomeView: View {
@Environment(\.openWindow) private var openWindow
@Environment(\.isiOSAppOnMac) private var isiOSAppOnMac
@ScaledMetric(relativeTo: .largeTitle) private var labelWidth = CGFloat(34)
let coordinator: HomeViewCoordinator
let isCompactLayout: Bool
Expand Down Expand Up @@ -48,8 +50,7 @@ struct HomeView: View {
)) {
if let selectedCategory = coordinator.viewModel.state.selectedTodoCategory {
TodoEditorView(
viewModel: coordinator.makeTodoEditorViewModel(category: selectedCategory),
onSubmit: { coordinator.viewModel.send(.addTodo($0)) }
viewModel: coordinator.makeTodoEditorViewModel(category: selectedCategory)
)
}
}
Expand Down Expand Up @@ -272,6 +273,7 @@ struct HomeView: View {
} label: {
RecentTodoRow(todo: item)
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(.rect)
}
.buttonStyle(.plain)
}
Expand All @@ -290,6 +292,7 @@ struct HomeView: View {
} label: {
WebItemRow(item: item, showsChevron: false)
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(.rect)
}
.buttonStyle(.plain)
}
Expand All @@ -314,7 +317,7 @@ struct HomeView: View {
ForEach(preferences, id: \.id) { item in
Button {
DispatchQueue.main.async {
coordinator.viewModel.send(.tapTodoCategory(item.category))
openTodoEditor(for: item.category)
}
} label: {
labelImage(
Expand Down Expand Up @@ -384,6 +387,18 @@ struct HomeView: View {
.contentShape(.rect)
}

private func openTodoEditor(for todoCategory: TodoCategory) {
if isiOSAppOnMac {
coordinator.viewModel.send(.setPresentation(.contentPicker, false))
openWindow(
id: TodoEditorWindowValue.sceneId,
value: TodoEditorWindowValue(todoCategory: todoCategory, source: .home)
)
} else {
coordinator.viewModel.send(.tapTodoCategory(todoCategory))
}
}

}

enum HomeRoute: Hashable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by opfic on 5/10/26.
//

import Combine
import Foundation
import DevLogCore
import DevLogDomain
Expand All @@ -14,33 +15,20 @@ import DevLogDomain
final class HomeViewCoordinator {
let viewModel: HomeViewModel
let router = NavigationRouter<HomeRoute>()
private let fetchTodoCategoryPreferencesUseCase: FetchTodoCategoryPreferencesUseCase
private let fetchReferenceItemsUseCase: FetchReferenceItemsUseCase
private let fetchWebPagesUseCase: FetchWebPagesUseCase
private let fetchTodosUseCase: FetchTodosUseCase
private let fetchRecentSearchQueriesUseCase: FetchRecentSearchQueriesUseCase
private let updateRecentSearchQueriesUseCase: UpdateRecentSearchQueriesUseCase
private let container: DIContainer
@ObservationIgnored
private var cancellable: AnyCancellable?

init(container: DIContainer) {
let fetchTodoCategoryPreferencesUseCase = container.resolve(FetchTodoCategoryPreferencesUseCase.self)
let fetchWebPagesUseCase = container.resolve(FetchWebPagesUseCase.self)
let fetchTodosUseCase = container.resolve(FetchTodosUseCase.self)

self.fetchTodoCategoryPreferencesUseCase = fetchTodoCategoryPreferencesUseCase
self.fetchReferenceItemsUseCase = container.resolve(FetchReferenceItemsUseCase.self)
self.fetchWebPagesUseCase = fetchWebPagesUseCase
self.fetchTodosUseCase = fetchTodosUseCase
self.fetchRecentSearchQueriesUseCase = container.resolve(FetchRecentSearchQueriesUseCase.self)
self.updateRecentSearchQueriesUseCase = container.resolve(UpdateRecentSearchQueriesUseCase.self)
self.container = container
self.viewModel = HomeViewModel(
fetchPreferencesUseCase: fetchTodoCategoryPreferencesUseCase,
fetchPreferencesUseCase: container.resolve(FetchTodoCategoryPreferencesUseCase.self),
updatePreferencesUseCase: container.resolve(UpdateTodoCategoryPreferencesUseCase.self),
addWebPageUseCase: container.resolve(AddWebPageUseCase.self),
deleteWebPageUseCase: container.resolve(DeleteWebPageUseCase.self),
undoDeleteWebPageUseCase: container.resolve(UndoDeleteWebPageUseCase.self),
upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self),
fetchTodosUseCase: fetchTodosUseCase,
fetchWebPagesUseCase: fetchWebPagesUseCase,
fetchTodosUseCase: container.resolve(FetchTodosUseCase.self),
fetchWebPagesUseCase: container.resolve(FetchWebPagesUseCase.self),
networkConnectivityUseCase: container.resolve(ObserveNetworkConnectivityUseCase.self),
trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self)
)
Expand All @@ -50,24 +38,40 @@ final class HomeViewCoordinator {
viewModel.send(.fetchData)
}

func bindWindowEvent(_ windowEvent: TodoEditorWindowEvent) {
guard cancellable == nil else { return }

cancellable = windowEvent.submits
.sink { [weak self] submit in
guard submit.value.matchesCreate(source: .home) else { return }
self?.viewModel.send(.fetchData)
}
}

func makeTodoManageViewModel() -> TodoManageViewModel {
TodoManageViewModel(viewModel.state.preferences)
}

func makeTodoEditorViewModel(category: TodoCategory) -> TodoEditorViewModel {
TodoEditorViewModel(
category: category,
fetchPreferencesUseCase: fetchTodoCategoryPreferencesUseCase,
fetchReferenceItemsUseCase: fetchReferenceItemsUseCase
fetchPreferencesUseCase: container.resolve(FetchTodoCategoryPreferencesUseCase.self),
fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self),
upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self),
trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self),
onUpsertSuccess: { [weak self] _ in
self?.viewModel.send(.setPresentation(.todoEditor, false))
self?.viewModel.send(.fetchData)
}
)
}

func makeSearchViewModel() -> SearchViewModel {
SearchViewModel(
fetchWebPagesUseCase: fetchWebPagesUseCase,
fetchTodosUseCase: fetchTodosUseCase,
fetchRecentSearchQueriesUseCase: fetchRecentSearchQueriesUseCase,
updateRecentSearchQueriesUseCase: updateRecentSearchQueriesUseCase
fetchWebPagesUseCase: container.resolve(FetchWebPagesUseCase.self),
fetchTodosUseCase: container.resolve(FetchTodosUseCase.self),
fetchRecentSearchQueriesUseCase: container.resolve(FetchRecentSearchQueriesUseCase.self),
updateRecentSearchQueriesUseCase: container.resolve(UpdateRecentSearchQueriesUseCase.self)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ final class HomeViewModel: Store {
case tapTodoCategory(TodoCategory)
case orderTodoCategory([TodoCategoryItem])
case setTodoCategory([TodoCategoryItem])
case addTodo(Todo)
case updateRecentTodos([RecentTodoItem])
case updateWebPageURLInput(String)
case addWebPage
Expand All @@ -60,7 +59,6 @@ final class HomeViewModel: Store {
}

enum SideEffect {
case addTodo(Todo)
case addWebPage(String)
case deleteWebPage(WebPageItem)
case undoDeleteWebPage(String)
Expand Down Expand Up @@ -103,7 +101,6 @@ final class HomeViewModel: Store {
private(set) var state = State()
private let fetchPreferencesUseCase: FetchTodoCategoryPreferencesUseCase
private let updatePreferencesUseCase: UpdateTodoCategoryPreferencesUseCase
private let upsertTodoUseCase: UpsertTodoUseCase
private let addWebPageUseCase: AddWebPageUseCase
private let deleteWebPageUseCase: DeleteWebPageUseCase
private let undoDeleteWebPageUseCase: UndoDeleteWebPageUseCase
Expand All @@ -121,7 +118,6 @@ final class HomeViewModel: Store {
addWebPageUseCase: AddWebPageUseCase,
deleteWebPageUseCase: DeleteWebPageUseCase,
undoDeleteWebPageUseCase: UndoDeleteWebPageUseCase,
upsertTodoUseCase: UpsertTodoUseCase,
fetchTodosUseCase: FetchTodosUseCase,
fetchWebPagesUseCase: FetchWebPagesUseCase,
networkConnectivityUseCase: ObserveNetworkConnectivityUseCase,
Expand All @@ -132,7 +128,6 @@ final class HomeViewModel: Store {
self.addWebPageUseCase = addWebPageUseCase
self.deleteWebPageUseCase = deleteWebPageUseCase
self.undoDeleteWebPageUseCase = undoDeleteWebPageUseCase
self.upsertTodoUseCase = upsertTodoUseCase
self.fetchTodosUseCase = fetchTodosUseCase
self.fetchWebPagesUseCase = fetchWebPagesUseCase
self.networkConnectivityUseCase = networkConnectivityUseCase
Expand All @@ -149,7 +144,7 @@ final class HomeViewModel: Store {
case .networkStatusChanged(let isConnected):
state.isNetworkConnected = isConnected
case .fetchData, .setPresentation, .setAlert, .setToast, .refreshWebPages,
.tapTodoCategory, .orderTodoCategory, .addTodo, .updateWebPageURLInput,
.tapTodoCategory, .orderTodoCategory, .updateWebPageURLInput,
.addWebPage, .deleteWebPage, .undoDeleteWebPage:
effects = reduceByView(action, state: &state)

Expand Down Expand Up @@ -183,23 +178,6 @@ final class HomeViewModel: Store {
send(.setAlert(isPresented: true, type: .error))
}
}
case .addTodo(let todo):
beginLoading(for: .overlay, mode: .delayed)
Task {
do {
defer { endLoading(for: .overlay, mode: .delayed) }
try await upsertTodoUseCase.execute(todo)
trackAnalyticsEventUseCase.execute(.todoCreate)
let page = try await fetchRecentTodos()
let items = page.items
.filter { $0.createdAt != $0.updatedAt }
.prefix(5)
.compactMap { RecentTodoItem(from: $0) }
send(.updateRecentTodos(items))
} catch {
send(.setAlert(isPresented: true, type: .error))
}
}
case .fetchRecentTodos:
beginLoading(for: .recentTodos, mode: .immediate)
Task {
Expand Down Expand Up @@ -305,8 +283,6 @@ private extension HomeViewModel {
state.preferences = preferences
state.recentTodos = syncRecentTodos(state.recentTodos, preferences: preferences)
return [.updateTodoCategoryPreferences(preferences)]
case .addTodo(let todo):
return [.addTodo(todo)]
case .updateWebPageURLInput(let text):
state.webPageURLInput = text
case .addWebPage:
Expand Down
27 changes: 22 additions & 5 deletions Application/DevLogPresentation/Sources/Home/TodoDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import DevLogDomain

struct TodoDetailView: View {
@Environment(\.diContainer) private var container: DIContainer
@Environment(\.openWindow) private var openWindow
@Environment(\.isiOSAppOnMac) private var isiOSAppOnMac
@State var viewModel: TodoDetailViewModel

var body: some View {
Expand Down Expand Up @@ -44,7 +46,6 @@ struct TodoDetailView: View {
TodoDetailView(viewModel: TodoDetailViewModel(
fetchTodoUseCase: container.resolve(FetchTodoByIdUseCase.self),
fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self),
upsertUseCase: container.resolve(UpsertTodoUseCase.self),
todoId: item.id,
showEditButton: false
))
Expand All @@ -66,9 +67,13 @@ struct TodoDetailView: View {
viewModel: TodoEditorViewModel(
todo: todo,
fetchPreferencesUseCase: container.resolve(FetchTodoCategoryPreferencesUseCase.self),
fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self)
),
onSubmit: { viewModel.send(.upsertTodo($0)) }
fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self),
upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self),
onUpsertSuccess: { todo in
viewModel.send(.setShowEditor(false))
viewModel.send(.setTodo(todo))
}
)
)
}
}
Expand All @@ -90,14 +95,26 @@ struct TodoDetailView: View {
}
ToolbarItem(placement: .topBarTrailing) {
Button {
viewModel.send(.setShowEditor(true))
openTodoEditor()
} label: {
Text(String(localized: "todo_edit"))
}
}
}
}

private func openTodoEditor() {
if isiOSAppOnMac {
guard let todo = viewModel.state.todo else { return }
openWindow(
id: TodoEditorWindowValue.sceneId,
value: TodoEditorWindowValue(todo: todo)
)
} else {
viewModel.send(.setShowEditor(true))
}
}

@ViewBuilder
private var sheetContent: some View {
if let todo = viewModel.state.todo {
Expand Down
Loading