diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 129e39d2..e73678a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -85,6 +85,7 @@ jobs: Application/DevLogInfra/DevLogInfra.xcodeproj \ Application/DevLogPersistence/DevLogPersistence.xcodeproj \ Application/DevLogPresentation/DevLogPresentation.xcodeproj \ + Application/DevLogUI/DevLogUI.xcodeproj \ Widget/DevLogWidgetCore/DevLogWidgetCore.xcodeproj \ Widget/DevLogWidgetExtension/DevLogWidgetExtension.xcodeproj diff --git a/.hermes/skills/devlog-architecture-harness/SKILL.md b/.hermes/skills/devlog-architecture-harness/SKILL.md index a26d4a8e..8dd3d477 100644 --- a/.hermes/skills/devlog-architecture-harness/SKILL.md +++ b/.hermes/skills/devlog-architecture-harness/SKILL.md @@ -74,8 +74,9 @@ Ask the user before editing when: - Core vs Domain ownership is unclear. - A shared type is being moved only because multiple modules need access. - Firebase/Auth/Firestore/Functions/Messaging-specific logic would leave Infra. -- WidgetCore would depend on Domain, Data, Infra, Persistence, Presentation, or App. -- Presentation would depend on Data, Infra, Persistence, or App. +- WidgetCore would depend on Domain, Data, Infra, Persistence, Presentation, UI, or App. +- Presentation would depend on UI, Data, Infra, Persistence, or App. +- UI would depend on Domain, Data, Infra, Persistence, or App. - Data would gain concrete SDK or storage implementation details. - The Presentation `Store` flow or reducer responsibility would change. - A compile fix requires relaxing the intended architecture. diff --git a/.hermes/skills/devlog-architecture-harness/references/devlog-architecture-flow.md b/.hermes/skills/devlog-architecture-harness/references/devlog-architecture-flow.md index a3bb838e..42373d5f 100644 --- a/.hermes/skills/devlog-architecture-harness/references/devlog-architecture-flow.md +++ b/.hermes/skills/devlog-architecture-harness/references/devlog-architecture-flow.md @@ -73,7 +73,8 @@ flowchart TD ```mermaid flowchart TD App["DevLogApp\nComposition root\nApp lifecycle\nAssembler wiring"] - Presentation["DevLogPresentation\nSwiftUI views\nViewModels\nCoordinators\nUI state"] + UI["DevLogUI\nSwiftUI views\nReusable components\nView composition"] + Presentation["DevLogPresentation\nStore\nViewModels\nCoordinators\nUI state"] Domain["DevLogDomain\nEntities\nRepository protocols\nUse cases"] Data["DevLogData\nRepository implementations\nDTOs\nMappers\nService/store protocols"] Infra["DevLogInfra\nFirebase\nSocial login\nNetwork\nLink metadata\nMessaging"] @@ -83,6 +84,7 @@ flowchart TD WidgetExtension["DevLogWidgetExtension\nWidgetKit UI\nProviders\nTimelines"] App --> Presentation + App --> UI App --> Domain App --> Data App --> Infra @@ -90,6 +92,9 @@ flowchart TD App --> Core App --> WidgetCore + UI --> Presentation + UI --> Core + Presentation --> Domain Presentation --> Core @@ -114,13 +119,14 @@ flowchart TD | Layer | Owns | Allowed direction | Ask before | | --- | --- | --- | --- | | `DevLogCore` | DI primitives, logger, shared value/query types, widget snapshot values | No DevLog layer dependency | Moving domain entities into Core | -| `DevLogDomain` | entities, repository protocols, use cases | Core only | Adding Data, Infra, Persistence, Presentation, App, Widget UI, or SDK dependency | +| `DevLogDomain` | entities, repository protocols, use cases | Core only | Adding Data, Infra, Persistence, Presentation, UI, App, Widget UI, or SDK dependency | | `DevLogData` | repository implementations, DTOs, mappers, data protocols | Domain, Core | Adding concrete Firebase, WidgetKit, storage, or platform implementation details | | `DevLogInfra` | Firebase, social login, network, metadata, messaging implementations | Data, Core | Moving SDK-specific behavior out of Infra | | `DevLogPersistence` | local stores, image cache, widget snapshot persistence | Data, Core, WidgetCore | Moving domain logic or remote SDK behavior into Persistence | -| `DevLogPresentation` | UI, view models, coordinators, presentation state | Domain, Core | Adding Data, Infra, Persistence, or App dependency | +| `DevLogPresentation` | Store, view models, coordinators, presentation state | Domain, Core | Adding UI, Data, Infra, Persistence, or App dependency | +| `DevLogUI` | SwiftUI views, reusable UI components, view composition | Presentation, Core | Adding Domain, Data, Infra, Persistence, or App dependency | | `DevLogApp` | composition root, lifecycle, assembler wiring | Concrete app layers | Moving feature logic into App | -| `DevLogWidgetCore` | widget data contracts and pure snapshot logic | Core | Adding Domain, Data, Infra, Persistence, Presentation, or App dependency | +| `DevLogWidgetCore` | widget data contracts and pure snapshot logic | Core | Adding Domain, Data, Infra, Persistence, Presentation, UI, or App dependency | | `DevLogWidgetExtension` | WidgetKit rendering and timeline plumbing | WidgetCore | Calling app/domain services directly | ## Presentation Store flow @@ -159,6 +165,7 @@ flowchart TD Shared{"Moved only because shared?"} NewDependency{"New module dependency?"} ExternalSDK{"External SDK crosses layer?"} + UIBoundary{"UI sees Domain/Data/Infra/Persistence/App?"} WidgetBoundary{"WidgetCore sees app/domain/data?"} BuildShortcut{"Build fix relaxes boundary?"} ScopeDrift{"Outside current task scope?"} @@ -173,7 +180,9 @@ flowchart TD NewDependency -->|Yes| Ask NewDependency -->|No| ExternalSDK ExternalSDK -->|Yes| Ask - ExternalSDK -->|No| WidgetBoundary + ExternalSDK -->|No| UIBoundary + UIBoundary -->|Yes| Ask + UIBoundary -->|No| WidgetBoundary WidgetBoundary -->|Yes| Ask WidgetBoundary -->|No| BuildShortcut BuildShortcut -->|Yes| Ask diff --git a/AGENTS.md b/AGENTS.md index ffe89dec..2d0cf39b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,13 +43,14 @@ Treat this repository as an Xcode workspace-based modular iOS app. There is no r ### Current layer map - `Application/DevLogCore`: shared app primitives such as DI, logging, query/value types, display options, and widget snapshot value types. Core is shared, but shared access alone is not enough reason to move domain entities into Core. -- `Application/DevLogDomain`: entities, repository protocols, use case protocols, and use case implementations. Domain may depend on Core. Domain must not depend on Data, Infra, Persistence, Presentation, App, Widget extension UI, Firebase SDKs, or storage implementations. +- `Application/DevLogDomain`: entities, repository protocols, use case protocols, and use case implementations. Domain may depend on Core. Domain must not depend on Data, Infra, Persistence, Presentation, UI, App, Widget extension UI, Firebase SDKs, or storage implementations. - `Application/DevLogData`: repository implementations, DTOs, mappers, data-layer protocols for external services/stores, and widget sync coordination. Data may depend on Domain and Core. Data should not gain direct Firebase, GoogleSignIn, WidgetKit, or concrete storage implementation details unless the user explicitly approves the boundary change. - `Application/DevLogInfra`: Firebase, social login, network, link metadata, messaging, and platform service implementations. Infra may depend on Data and Core. Firebase/Auth/Firestore/Functions/Messaging-specific behavior belongs here unless the user approves another boundary. - `Application/DevLogPersistence`: local persistence, user defaults, image store, and widget snapshot persistence/updating. Persistence may depend on Data, Core, and WidgetCore when needed for snapshot persistence. -- `Application/DevLogPresentation`: SwiftUI views, view models, coordinators, UI state structures, and presentation-only helpers. Presentation may depend on Domain and Core. It must not depend on Data, Infra, Persistence, or App. +- `Application/DevLogPresentation`: Store, view models, coordinators, UI state structures, and presentation-only helpers. Presentation may depend on Domain and Core. It must not depend on UI, Data, Infra, Persistence, or App. +- `Application/DevLogUI`: SwiftUI views, reusable UI components, and view composition. UI may depend on Presentation and Core. It must not depend on Domain, Data, Infra, Persistence, or App. - `Application/DevLogApp`: composition root, app lifecycle, app delegate, app-level routing, and assembler wiring. App may import concrete layers to assemble the dependency graph. -- `Widget/DevLogWidgetCore`: widget snapshot models, factories, keys, app-group constants, and widget-only pure helpers. WidgetCore may depend on Core. It must not depend on Domain, Data, Infra, Persistence, Presentation, or App without explicit user approval. +- `Widget/DevLogWidgetCore`: widget snapshot models, factories, keys, app-group constants, and widget-only pure helpers. WidgetCore may depend on Core. It must not depend on Domain, Data, Infra, Persistence, Presentation, UI, or App without explicit user approval. - `Widget/DevLogWidgetExtension`: WidgetKit UI, widget providers, entries, timelines, and extension resources. It should consume WidgetCore outputs rather than app/domain services directly. - `Firebase/functions`: TypeScript Cloud Functions. Deploy updated functions one by one separately. @@ -68,6 +69,7 @@ Ask the user before editing when any of these are true: - A type could plausibly live in both Core and Domain. - A shared type is being moved only because multiple modules need access to it. - A new target dependency would make a lower-level module know a higher-level module. +- UI would start depending on Domain, Data, Infra, Persistence, or App. - A build fix would be achieved by loosening an architecture boundary. - Firebase, GoogleSignIn, AuthenticationServices, UserNotifications, LinkPresentation, Network, WidgetKit, or storage implementation details would move to another layer. - A repository protocol, service protocol, assembler, or DI ownership boundary would change. diff --git a/Application/DevLogApp/DevLogApp.xcodeproj/project.pbxproj b/Application/DevLogApp/DevLogApp.xcodeproj/project.pbxproj index 9ed8dab0..dca91965 100644 --- a/Application/DevLogApp/DevLogApp.xcodeproj/project.pbxproj +++ b/Application/DevLogApp/DevLogApp.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 014266F6F7BFECD66B6DFFAE /* DevLogPersistence.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 2242DBABD8ABF11A23160669 /* DevLogPersistence.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 0839F65046F9E9A7CD41A531 /* DevLogUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B1D82B3A3D99364567064B1 /* DevLogUI.framework */; }; 088CF111FEB9773D36BFDAFB /* LocalFirebaseRESTSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6750095BE316E855E5B6DFFB /* LocalFirebaseRESTSupport.swift */; }; 0B79D44CFEC6A20F873CF3A9 /* DevLogCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B1F60100641A6FBC29ED402D /* DevLogCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 1056A18C4306853A83487239 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53D7AC935B97855AA743EFC0 /* Assets.xcassets */; }; @@ -15,6 +16,7 @@ 23B23964D44D78E8CFF0D97C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC12B5E227050E4BAEF315D /* AppDelegate.swift */; }; 25E825500273F1F887CCC6DC /* NotificationName+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EADB630F7E3DEFAA6A8DEF4 /* NotificationName+.swift */; }; 2C2180289EEF65F3CF5C1273 /* DevLogWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D0044D9310C29492954FD1F7 /* DevLogWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 37996815654B34E69C60877B /* DevLogUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4B1D82B3A3D99364567064B1 /* DevLogUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3BFF117D0E2682ECB0044884 /* MainTab+WidgetDeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAEEC97A4EC302C6974E1A4E /* MainTab+WidgetDeepLink.swift */; }; 3CC295543F33CF6BE9560AE0 /* WidgetSessionSyncHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 462271192C47C593C36AC403 /* WidgetSessionSyncHandlerTests.swift */; }; 423070F73AE0E059BC417D30 /* DevLogPresentation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECA33E1403AF8BDB84D46C01 /* DevLogPresentation.framework */; }; @@ -35,7 +37,9 @@ AE8FAFB84865A9798429346B /* DevLogPersistence.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2242DBABD8ABF11A23160669 /* DevLogPersistence.framework */; }; B154A155136A179166B00EAC /* DevLogApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44AFCDB8890714D634C4536 /* DevLogApp.swift */; }; CED2148B1FA2117B58665B63 /* DevLogWidgetExtension.appex in Dependencies */ = {isa = PBXBuildFile; fileRef = D0044D9310C29492954FD1F7 /* DevLogWidgetExtension.appex */; }; + CF9A2B14D691948BA64EAF7D /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8713EA9CC7AFF3F0A584863 /* RootView.swift */; }; D892FE40671E7C165DBE4E4D /* DevLogInfra.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 28ECC31B0B7F9C0A9BB4F55C /* DevLogInfra.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + DAA07C6782ECCD9D9047D56D /* TodoEditorWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4CC786C01FCD38075E60D0 /* TodoEditorWindowView.swift */; }; DF971A154668D99F2A646957 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 026FE38633FEFB0DD0575244 /* GoogleService-Info.plist */; }; E312AB98EB0B33141E499A04 /* DeleteWebPageIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B937350A0682E6F1CD20536 /* DeleteWebPageIntegrationTests.swift */; }; EDE9714BCCC50B8950F0E911 /* DevLogPresentation.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = ECA33E1403AF8BDB84D46C01 /* DevLogPresentation.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -77,6 +81,7 @@ D892FE40671E7C165DBE4E4D /* DevLogInfra.framework in Embed Frameworks */, 014266F6F7BFECD66B6DFFAE /* DevLogPersistence.framework in Embed Frameworks */, EDE9714BCCC50B8950F0E911 /* DevLogPresentation.framework in Embed Frameworks */, + 37996815654B34E69C60877B /* DevLogUI.framework in Embed Frameworks */, F2F380C38E8953DB78F85FC2 /* DevLogWidgetCore.framework in Embed Frameworks */, ); name = "Embed Frameworks"; @@ -111,11 +116,13 @@ 2242DBABD8ABF11A23160669 /* DevLogPersistence.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogPersistence.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 28ECC31B0B7F9C0A9BB4F55C /* DevLogInfra.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogInfra.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2B937350A0682E6F1CD20536 /* DeleteWebPageIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteWebPageIntegrationTests.swift; sourceTree = ""; }; + 2C4CC786C01FCD38075E60D0 /* TodoEditorWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoEditorWindowView.swift; sourceTree = ""; }; 346173767E6ED68E9DFDA51C /* FCMTokenSyncHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMTokenSyncHandler.swift; sourceTree = ""; }; 3494F50D6E7BC9510DE43F65 /* DevLogDomain.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogDomain.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3AC12B5E227050E4BAEF315D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 462271192C47C593C36AC403 /* WidgetSessionSyncHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetSessionSyncHandlerTests.swift; sourceTree = ""; }; 4635BA36DE82C73A105E7157 /* Version.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Version.xcconfig; sourceTree = ""; }; + 4B1D82B3A3D99364567064B1 /* DevLogUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 534E5D11EAA2E34D71F066DB /* PushNotificationRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationRoute.swift; sourceTree = ""; }; 53D7AC935B97855AA743EFC0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 544C918C2E050DFD80C83157 /* DevLogData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogData.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -135,6 +142,7 @@ C44AFCDB8890714D634C4536 /* DevLogApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevLogApp.swift; sourceTree = ""; }; CAEEC97A4EC302C6974E1A4E /* MainTab+WidgetDeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainTab+WidgetDeepLink.swift"; sourceTree = ""; }; D0044D9310C29492954FD1F7 /* DevLogWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = DevLogWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + D8713EA9CC7AFF3F0A584863 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; }; DCC05042BEB7C047BE4DB7E9 /* DevLogWidgetCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogWidgetCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; ECA33E1403AF8BDB84D46C01 /* DevLogPresentation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogPresentation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F078E79DE65AE8D92F556B27 /* DeletePushNotificationIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletePushNotificationIntegrationTests.swift; sourceTree = ""; }; @@ -151,6 +159,7 @@ AA5C5ACB75796DECF2BE6939 /* DevLogInfra.framework in Frameworks */, AE8FAFB84865A9798429346B /* DevLogPersistence.framework in Frameworks */, 423070F73AE0E059BC417D30 /* DevLogPresentation.framework in Frameworks */, + 0839F65046F9E9A7CD41A531 /* DevLogUI.framework in Frameworks */, A93F228DD04515584B150BCE /* DevLogWidgetCore.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -195,6 +204,7 @@ 28ECC31B0B7F9C0A9BB4F55C /* DevLogInfra.framework */, 2242DBABD8ABF11A23160669 /* DevLogPersistence.framework */, ECA33E1403AF8BDB84D46C01 /* DevLogPresentation.framework */, + 4B1D82B3A3D99364567064B1 /* DevLogUI.framework */, DCC05042BEB7C047BE4DB7E9 /* DevLogWidgetCore.framework */, D0044D9310C29492954FD1F7 /* DevLogWidgetExtension.appex */, ); @@ -255,6 +265,7 @@ FC39B353F5CB051101B824C1 /* Handler */, D4C5B7F48B3861AC6A9E4972 /* Notification */, 8B85D312D1D8054055A7F251 /* Routing */, + A3B5438D0D9EDCCCA37FEB88 /* Scene */, C44AFCDB8890714D634C4536 /* DevLogApp.swift */, B347482071F5035929CB7CFB /* TempView.swift */, ); @@ -289,6 +300,15 @@ name = Project; sourceTree = ""; }; + A3B5438D0D9EDCCCA37FEB88 /* Scene */ = { + isa = PBXGroup; + children = ( + D8713EA9CC7AFF3F0A584863 /* RootView.swift */, + 2C4CC786C01FCD38075E60D0 /* TodoEditorWindowView.swift */, + ); + path = Scene; + sourceTree = ""; + }; A921B89C54E83E78FB2AC773 /* Derived */ = { isa = PBXGroup; children = ( @@ -484,6 +504,8 @@ 25E825500273F1F887CCC6DC /* NotificationName+.swift in Sources */, 3BFF117D0E2682ECB0044884 /* MainTab+WidgetDeepLink.swift in Sources */, 5B1CB12023BE41B32EA64B95 /* PushNotificationRoute.swift in Sources */, + CF9A2B14D691948BA64EAF7D /* RootView.swift in Sources */, + DAA07C6782ECCD9D9047D56D /* TodoEditorWindowView.swift in Sources */, 47A3EA049D3AF319B62853ED /* TempView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Application/DevLogApp/Project.swift b/Application/DevLogApp/Project.swift index c910cb7f..671d1b0a 100644 --- a/Application/DevLogApp/Project.swift +++ b/Application/DevLogApp/Project.swift @@ -26,6 +26,7 @@ let project = Project( entitlements: .file(path: "Sources/Resource/DevLog.entitlements"), dependencies: [ .project(target: "DevLogPresentation", path: "../DevLogPresentation"), + .project(target: "DevLogUI", path: "../DevLogUI"), .project(target: "DevLogPersistence", path: "../DevLogPersistence"), .project(target: "DevLogInfra", path: "../DevLogInfra"), .project(target: "DevLogData", path: "../DevLogData"), diff --git a/Application/DevLogApp/Sources/App/DevLogApp.swift b/Application/DevLogApp/Sources/App/DevLogApp.swift index 1a2dca6c..437d1306 100644 --- a/Application/DevLogApp/Sources/App/DevLogApp.swift +++ b/Application/DevLogApp/Sources/App/DevLogApp.swift @@ -10,6 +10,7 @@ import DevLogCore import DevLogData import DevLogDomain import DevLogPresentation +import DevLogUI @main struct DevLogApp: App { @@ -25,10 +26,7 @@ struct DevLogApp: App { var body: some Scene { WindowGroup { RootView( - sessionUseCase: container.resolve(ObserveAuthSessionUseCase.self), - networkConnectivityUseCase: container.resolve(ObserveNetworkConnectivityUseCase.self), - systemThemeUseCase: container.resolve(ObserveSystemThemeUseCase.self), - trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self), + dependencies: RootViewDependencies(container: container), widgetURLTab: { MainTab(widgetURL: $0) }, windowEvent: windowEvent, pushNotificationTodoIdPublisher: PushNotificationRoute.shared.observe(), @@ -44,7 +42,10 @@ struct DevLogApp: App { if let value = value.wrappedValue { TodoEditorWindowView( value: value, - windowEvent: windowEvent + dependencies: TodoEditorWindowDependencies( + container: container, + windowEvent: windowEvent + ) ) .autocorrectionDisabled() } else { diff --git a/Application/DevLogPresentation/Sources/Root/RootView.swift b/Application/DevLogApp/Sources/App/Scene/RootView.swift similarity index 59% rename from Application/DevLogPresentation/Sources/Root/RootView.swift rename to Application/DevLogApp/Sources/App/Scene/RootView.swift index b74658b2..610c01d0 100644 --- a/Application/DevLogPresentation/Sources/Root/RootView.swift +++ b/Application/DevLogApp/Sources/App/Scene/RootView.swift @@ -1,18 +1,17 @@ // // RootView.swift -// DevLogPresentation +// DevLog // // Created by opfic on 5/2/25. // import SwiftUI +import DevLogPresentation +import DevLogUI import Combine -import DevLogCore -import DevLogDomain -public struct RootView: View { - @Environment(\.diContainer) var container: DIContainer - @State var viewModel: RootViewModel +struct RootView: View { + @State private var dependencies: RootViewDependencies @State private var selectedRoute: Route? @State private var selectedMainTab: MainTab? private let widgetURLTab: (URL) -> MainTab? @@ -20,48 +19,38 @@ public struct RootView: View { private let pushNotificationTodoIdPublisher: AnyPublisher private let clearPushNotificationRoute: () -> Void - public init( - sessionUseCase: ObserveAuthSessionUseCase, - networkConnectivityUseCase: ObserveNetworkConnectivityUseCase, - systemThemeUseCase: ObserveSystemThemeUseCase, - trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase, + init( + dependencies: RootViewDependencies, widgetURLTab: @escaping (URL) -> MainTab?, windowEvent: TodoEditorWindowEvent, pushNotificationTodoIdPublisher: AnyPublisher, clearPushNotificationRoute: @escaping () -> Void ) { - self._viewModel = State(initialValue: RootViewModel( - sessionUseCase: sessionUseCase, - networkConnectivityUseCase: networkConnectivityUseCase, - systemThemeUseCase: systemThemeUseCase, - trackAnalyticsEventUseCase: trackAnalyticsEventUseCase - )) + self._dependencies = State(initialValue: dependencies) self.widgetURLTab = widgetURLTab self.windowEvent = windowEvent self.pushNotificationTodoIdPublisher = pushNotificationTodoIdPublisher self.clearPushNotificationRoute = clearPushNotificationRoute } - public var body: some View { + var body: some View { ZStack { Color(UIColor.systemGroupedBackground).ignoresSafeArea() - if let signIn = viewModel.state.signIn { + if let signIn = dependencies.viewModel.state.signIn { if signIn { MainView( - container: container, + dependencies: dependencies.mainViewDependencies, windowEvent: windowEvent, selectedTab: $selectedMainTab ) } else { - LoginView(viewModel: LoginViewModel( - signInUseCase: container.resolve(SignInUseCase.self)) - ) + LoginView(viewModel: dependencies.makeLoginViewModel()) } } } - .preferredColorScheme(viewModel.state.theme.colorScheme) - .onAppear { viewModel.send(.onAppear) } - .onChange(of: viewModel.state.signIn) { _, value in + .preferredColorScheme(dependencies.viewModel.state.theme.colorScheme) + .onAppear { dependencies.viewModel.send(.onAppear) } + .onChange(of: dependencies.viewModel.state.signIn) { _, value in guard let value else { return } if value { selectedMainTab = .home @@ -71,7 +60,7 @@ public struct RootView: View { } .onOpenURL { url in guard let mainTab = widgetURLTab(url) else { return } - switch viewModel.state.signIn { + switch dependencies.viewModel.state.signIn { case .some(false): break case .some(true): @@ -80,24 +69,22 @@ public struct RootView: View { break } } - .alert(viewModel.state.alertTitle, isPresented: Binding( - get: { viewModel.state.showAlert }, - set: { viewModel.send(.setAlert($0)) } + .alert(dependencies.viewModel.state.alertTitle, isPresented: Binding( + get: { dependencies.viewModel.state.showAlert }, + set: { dependencies.viewModel.send(.setAlert($0)) } )) { Button(String(localized: "common_close"), role: .cancel) { } } message: { - Text(viewModel.state.alertMessage) + Text(dependencies.viewModel.state.alertMessage) } .sheet(item: $selectedRoute) { route in switch route { case .todoDetail(let todoId): NavigationStack { - TodoDetailView(viewModel: TodoDetailViewModel( - fetchTodoUseCase: container.resolve(FetchTodoByIdUseCase.self), - fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self), - todoId: todoId, - showEditButton: false - )) + TodoDetailView( + viewModel: dependencies.makeTodoDetailViewModel(todoId), + todoViewModelFactory: dependencies.mainViewDependencies.todoViewModelFactory + ) .toolbar { ToolbarLeadingButton { selectedRoute = nil diff --git a/Application/DevLogApp/Sources/App/Scene/TodoEditorWindowView.swift b/Application/DevLogApp/Sources/App/Scene/TodoEditorWindowView.swift new file mode 100644 index 00000000..effbf082 --- /dev/null +++ b/Application/DevLogApp/Sources/App/Scene/TodoEditorWindowView.swift @@ -0,0 +1,69 @@ +// +// TodoEditorWindowView.swift +// DevLog +// +// Created by opfic on 5/31/26. +// + +import SwiftUI +import DevLogPresentation +import DevLogUI + +struct TodoEditorWindowView: View { + @State private var windowScene: UIWindowScene? + private let value: TodoEditorWindowValue + private let dependencies: TodoEditorWindowDependencies + + init( + value: TodoEditorWindowValue, + dependencies: TodoEditorWindowDependencies + ) { + self.value = value + self.dependencies = dependencies + } + + var body: some View { + Group { + TodoEditorView( + viewModel: dependencies.makeEditorViewModel( + value: value, + onClose: closeWindow + ), + todoViewModelFactory: dependencies.todoViewModelFactory, + onClose: closeWindow + ) + } + .background { + WindowSceneReader { windowScene = $0 } + } + } + + private func closeWindow() { + guard let windowScene else { return } + UIApplication.shared.requestSceneSessionDestruction( + windowScene.session, + options: nil, + errorHandler: nil + ) + } +} + +private struct WindowSceneReader: UIViewRepresentable { + let onResolve: (UIWindowScene?) -> Void + + func makeUIView(context: Context) -> UIView { + let view = UIView() + resolve(from: view) + return view + } + + func updateUIView(_ view: UIView, context: Context) { + resolve(from: view) + } + + private func resolve(from view: UIView) { + DispatchQueue.main.async { + onResolve(view.window?.windowScene) + } + } +} diff --git a/Application/DevLogPresentation/DevLogPresentation.xcodeproj/project.pbxproj b/Application/DevLogPresentation/DevLogPresentation.xcodeproj/project.pbxproj index 96aecf5d..ebc2034c 100644 --- a/Application/DevLogPresentation/DevLogPresentation.xcodeproj/project.pbxproj +++ b/Application/DevLogPresentation/DevLogPresentation.xcodeproj/project.pbxproj @@ -8,93 +8,64 @@ /* Begin PBXBuildFile section */ 03CF7E696626224FFE93FFDD /* TodoReferenceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B8F3DEEF6527F9FBCBDCD5A /* TodoReferenceItem.swift */; }; - 042A04373E0BF17A8C0E90A1 /* CheckBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F32D20E83ABB122630F67BD /* CheckBox.swift */; }; 053BC20B9244F93FFE9E6FA7 /* UserTodoCategoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10169ED43745C8286BAA560A /* UserTodoCategoryItem.swift */; }; - 0675430931A12884236C92E7 /* EnvironmentValues+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79043B9D995BD5D987320FE6 /* EnvironmentValues+.swift */; }; 09A7C0CA26E4129EBADE0928 /* TodoCategoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE04D300F1054596E630CF48 /* TodoCategoryItem.swift */; }; - 0B436A4BB4F4289DC5A24286 /* TodoListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34028ABF87BCFE5E95B8DF2C /* TodoListView.swift */; }; - 0BDF307FFD70E121BFE54B64 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46339787CA1BCF8EF8AACBC6 /* LoadingView.swift */; }; + 0C65DE6F8B9FB06F8D121D1E /* TodoViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D87C5787A3E9EAD0CC10A3D /* TodoViewModelFactory.swift */; }; 116BB487A24975CAAD4AC940 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 56A49A4AE77CECF9ABEC6861 /* MarkdownUI */; }; - 164FF0D15F3671EC5B8175B4 /* PushNotificationListViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F52E54B2F053507AE43819 /* PushNotificationListViewCoordinator.swift */; }; 173C43896990D2469CB29D6B /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38030D2CAA016ABB3D0C396B /* Store.swift */; }; - 175898C005C97BED56549457 /* TodoEditorWindowEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 085FC0504CE36484B655FB09 /* TodoEditorWindowEvent.swift */; }; - 19A49575DE29575B3E30D42F /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25193961AAF03B736D8BA4D /* MainViewModel.swift */; }; - 21D9F0E69F0F648AA719D3A8 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6D5BDBC6990696C3FA5B22A /* Toast.swift */; }; 26ED8F658B02153735BD8692 /* Color+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852D34A35C60375D1D9F9566 /* Color+Hex.swift */; }; + 26F47ED71B5B0701B1344A17 /* TodoEditorWindowDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C76C1FD0B57BC6E736F16F9 /* TodoEditorWindowDependencies.swift */; }; 277C3C6802370B2DB28EA92A /* WebPageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D191AD793EE0AC702B1539 /* WebPageItem.swift */; }; - 2B0C47EBD387EEABE2A90A99 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCFF8EE1B8855BA6B7BC315F /* SearchViewModel.swift */; }; 2C9840C424A0D3C85D1001C9 /* TestSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5156DCFD77B78CAEE004CDDD /* TestSupport.swift */; }; 2DB84D2FBEA0653D48DF614F /* HeatmapDay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B652BF58D45805A57ECCE0 /* HeatmapDay.swift */; }; 336D5F52E964A099E10AECFF /* TodoListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB658B8B1246F4399493FE3 /* TodoListItem.swift */; }; 34AFBED9DAEBD1F4E4A2DB2B /* TodoIdItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08ACC3D94A90881383764E6C /* TodoIdItem.swift */; }; - 3801176C353549D8D259FF4A /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4279F380763E7CFF063F4416 /* LoginView.swift */; }; - 3A78734125366B547D0C4261 /* TodoDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6BFC9AC306836245B6E5CD /* TodoDetailView.swift */; }; - 3AE04B27F085D1E67BA64537 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF52D6EE30673D682147FAFE /* LoginViewModel.swift */; }; - 40B4784EB5FDD90B5A2EC81B /* TodoManageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90A0B2F4523B8570322F54C /* TodoManageViewModel.swift */; }; + 34DFDBE6AC68454DD2A9B548 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179B4DC72A2C2994456F609A /* LoginViewModel.swift */; }; + 35639553522A1CEFDCF455D7 /* TodoEditorWindowValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9206293A713E904877705C2 /* TodoEditorWindowValue.swift */; }; + 35649E3226AD3D47FB9D2C59 /* RootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91689B5FE51289C142DFD6BC /* RootViewModel.swift */; }; + 3D0FB9275F7BC7D914D8EC55 /* NavigationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D92F895EDEF78B5D98FB2A38 /* NavigationRouter.swift */; }; + 3F8512559CCF262222ACCAE2 /* RootViewDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E7A86BE16165EE865C2CCB1 /* RootViewDependencies.swift */; }; 41A5C4626505C6BF0643E43B /* MainTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290DE2215D7061AC10A057D8 /* MainTab.swift */; }; - 439047A7AF80D876838710F2 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4CFA6FFA82E0EC3C25D527B /* AccountView.swift */; }; - 49631F09477091376DDA0E4D /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B457717DEF395F131159C04 /* MainView.swift */; }; 4BE7032EE7146793A5C58A94 /* ActivityKindItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CC8A2FCF1577B150A08D83 /* ActivityKindItem.swift */; }; - 4C07B469D6576A76BE71E639 /* TodoMarkdownContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFB7CB1992F8434647D2E8D1 /* TodoMarkdownContentView.swift */; }; - 57373F815DB5EF9310AC641D /* LoginButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C462E5D83C6B639A66ED26 /* LoginButton.swift */; }; - 58A933F1C49D9977217BE84C /* ToolbarButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4AC141E5E2C9474DDBD35C /* ToolbarButtons.swift */; }; - 5931685A7811D0DCDF5508BF /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA75873FCFFC6182662694B /* ProfileView.swift */; }; + 4C1B42EE18ECF68DE71DBE47 /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 482E7887408590D8E856673B /* MainViewModel.swift */; }; + 4E725B56D14497FA73132F9F /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91B75A48E6680752B9DE82C9 /* HomeViewModel.swift */; }; + 4ED29B9BB4B0F12363BFB927 /* TodoDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD6E606DC53732CA7DECB2D /* TodoDetailViewModel.swift */; }; + 5357B7DBEC07C590B8C70E20 /* TodayRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1E4F3FA850AB900BD0837A /* TodayRoute.swift */; }; + 5DCFBD39AF6AC33B30265D73 /* PushNotificationListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACD429F304D34C5345FCD425 /* PushNotificationListViewModel.swift */; }; 5E9F72DE540DFEC1518CDDAE /* RecentTodoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A6CF5AAB9CCEAB35FB10A8 /* RecentTodoItem.swift */; }; - 5FD86AC94DE236C77BA69CB0 /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC9F3129E8EEBF9CAC622E7 /* ProfileViewModel.swift */; }; - 5FF8EAE9E54F8359A5391EFC /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71459EB54EF7A0B3F294418 /* WebView.swift */; }; - 653BC11E9CB8F74FC090E3A1 /* View+.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4D52F1343CECB8E0AC4742 /* View+.swift */; }; 65BFE2061C37BC86E811266E /* SystemTodoCategoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0CBB0EEB3B15A4BE2EF9641 /* SystemTodoCategoryItem.swift */; }; - 6DC7980E5F659AC3FB56427E /* PushNotificationListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EACFDECD5652C67B68D4936D /* PushNotificationListViewModel.swift */; }; + 6DCE29BCA008EFF51A476D7D /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D3F311FECBA18B5EE105FD2 /* SearchViewModel.swift */; }; 6F7ED28364309E79840E1B1A /* DeleteWebPageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37323FDF36DF44C549A71A85 /* DeleteWebPageTests.swift */; }; - 70FC630DC3FC27D77EA868FD /* TodoInfoSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AED8D48F34AE2476CCF4F5A /* TodoInfoSheetView.swift */; }; - 711C3E31B02B60A4E1FB01CF /* UIKitTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87DA85DC7F13D4E5B570ED6 /* UIKitTextEditor.swift */; }; - 84866D96D8DE6F76E805050F /* RootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53892CB6FB8F39CE967A3344 /* RootViewModel.swift */; }; - 8A76D55BB0714508C1019108 /* PushNotificationListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE62ED6DF757AF15ACBB2E07 /* PushNotificationListView.swift */; }; - 8D320EC70D396E019F964166 /* HeatmapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437D0037871D0A9E16D94C59 /* HeatmapView.swift */; }; - 9060015EC9ECBFF1A2511DC6 /* HomeViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7236EA0D4F011DB8D2922A65 /* HomeViewCoordinator.swift */; }; - 909B1608BDEDA51B07F27332 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80B7DE00658AC1080A3741C9 /* SearchView.swift */; }; - 90A88D80EF02129348CE35C5 /* TodoListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DDABB53271F38BCC892B6DD /* TodoListViewModel.swift */; }; + 7B3DB4474B7B6C2E948A277C /* PushNotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC66A467D5E3A2EB391E90DE /* PushNotificationSettingsViewModel.swift */; }; 91A04437CA63E957BDD1A060 /* TodayTodoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B2119664DB4FDF4A18FFE /* TodayTodoItem.swift */; }; 92DAE6E67927B3014F7FB18A /* PushNotificationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C7A0A3DE0AB6D5E4DD116B /* PushNotificationItem.swift */; }; - 959832CAA00E2B06DF0B8A83 /* TodoEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2D739A2380C3D28C575D08 /* TodoEditorViewModel.swift */; }; - 9ADFFABEE75489A526E1ECE5 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 252057C930A70DC1D5674EE5 /* HomeView.swift */; }; - 9D69715291EB7F2AE704A722 /* NavigationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D136528209E2B94793441C78 /* NavigationRouter.swift */; }; - A302B2F9FDCDAFAE5A852E94 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2195BF2BC362FB4BD570C915 /* RootView.swift */; }; + 94358125409F95AD6FEE75F6 /* TodoListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECABD5E04C07111C89C7DDB3 /* TodoListViewModel.swift */; }; + A641ED77789933A35718DC56 /* ProfileRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2DDC14187D2E0615DBF2A5E /* ProfileRoute.swift */; }; + A7605511964265827B06E268 /* ProfileViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D15BF430A0A932B943CE74B1 /* ProfileViewCoordinator.swift */; }; A93B2FC0C15576DDAD9F1772 /* DevLogCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70A888BE16FA57ED1309E014 /* DevLogCore.framework */; }; + AC7102A1D35CB1817BD885E8 /* TodoEditorWindowEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF251D17AB6A6BE27E94F83F /* TodoEditorWindowEvent.swift */; }; + AD05101B81F4D267A87AC7A7 /* SettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 776EC7BA6989C8AEE6BBB558 /* SettingViewModel.swift */; }; + AD97294A20E386D840EADDDB /* MainViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD678C679B9975DFD646F8FE /* MainViewCoordinator.swift */; }; + ADFE22BA856BD7974978B222 /* AccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81D2699575F2C695C72E294D /* AccountViewModel.swift */; }; AECD97E6C980B7940A3112E4 /* Error+SocialLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6F1C309AC57614AD630F79 /* Error+SocialLogin.swift */; }; - AFF2EFF4181D0AB292540962 /* NavigationBarConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A16A2DF4C226ACFE7E2F2BD /* NavigationBarConfigurator.swift */; }; B11D48231BFAFAA422C7CB30 /* DevLogPresentation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D36A4E11056749B1D5DEF446 /* DevLogPresentation.framework */; }; - B81EE3C71EC5CCC8CB9D5CAC /* TodayViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DEAD71515D037F501E9B9F /* TodayViewModel.swift */; }; - B8792A002416865EF41FCB5D /* TodayViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B434E6F27EFBC9D054461A /* TodayViewCoordinator.swift */; }; - BC607762E625C0DCD3B66E8A /* PushNotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2663D0FE64234FEBBDA9362C /* PushNotificationSettingsViewModel.swift */; }; - BCDC49501F5BA61CABB12D2B /* MainViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FA78DB36F62C17E9957AFF /* MainViewCoordinator.swift */; }; - BE51244E594875155551774A /* WebItemRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA1D8AFC51309CDFB5962985 /* WebItemRow.swift */; }; - C13E62E8FB7D681C14A42AC1 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E834C7D761EA77E717C9DE /* HomeViewModel.swift */; }; - C2530FF7F76903E54626D73B /* TodoEditorWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E802368D0BA811BD50470735 /* TodoEditorWindowView.swift */; }; + C2358524334557A83CFE10DE /* TodoEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED515F28865033FBA78DCAB6 /* TodoEditorViewModel.swift */; }; C26C5A74B162EF0866093F6E /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = F44AF3158F945047473FA5E7 /* OrderedCollections */; }; - C3883077C738EF1E76DFCC4C /* RelativeTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB112B525D9D6CB7B001028A /* RelativeTimeText.swift */; }; - CAD9F0F9CDADF2FA55CBC78B /* CacheableImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C52955505427FEA95D9B3CC8 /* CacheableImage.swift */; }; + C28BB5DECB3AA31ED031D90C /* TodoManageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87EE61156B04A85F894BD7D2 /* TodoManageViewModel.swift */; }; + C9553B4DC3249D9EC36473AC /* HomeViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 600892295632C397ECDD8AF4 /* HomeViewCoordinator.swift */; }; CB6204EC0FB8557D7FAF7174 /* HeatmapActivityItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E174814D9FFAFC79746E1902 /* HeatmapActivityItem.swift */; }; - D4C92919FF08171E53C15011 /* TodoDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 334EB88BC5FC0BD7DDA6A0FE /* TodoDetailViewModel.swift */; }; + CD9C0B62EAAB0FB928703250 /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08332CE6C5C2F9DC7B78278D /* ProfileViewModel.swift */; }; + CDD5185569899FF1F600DD08 /* PushNotificationListViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD7822B9271036D58AB46DB3 /* PushNotificationListViewCoordinator.swift */; }; D73622D51033A4BAF3B68DAC /* HeatmapMonth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD8C6D9E12E040F7B612FB1 /* HeatmapMonth.swift */; }; - D7E51185AD82E7DB333300AF /* AccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D6ACE383B5F1DA964DAB252 /* AccountViewModel.swift */; }; + D7D6E761D1C930C71E445FC6 /* TodoWindowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04907C8B1F51AC9D8A164F02 /* TodoWindowCoordinator.swift */; }; D7F0530537BE180AEAA579BB /* DeletePushNotificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A05BB3C999F4A364401871C5 /* DeletePushNotificationTests.swift */; }; D878C47DE4B2E1A9525FBCBF /* LoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14DA5CF22287DA889A033F8E /* LoadingState.swift */; }; - D988D2118F62024F5BA6A095 /* TodoEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82C8541262E532FA4A218650 /* TodoEditorView.swift */; }; - D99AD1D26B1D745028A93B8A /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB82F4B31B49736F70F50EE7 /* SettingView.swift */; }; + DA951ACBCB7C625E36B2E709 /* HomeRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82BEFB32E7D34AB261823961 /* HomeRoute.swift */; }; + DC04D254D882504D1F00F609 /* TodayViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D92080C40972043791AC990C /* TodayViewCoordinator.swift */; }; DCD8775B3F51592DD956952F /* HeatmapQuarter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 573AD2720FA89362FF4C4CEF /* HeatmapQuarter.swift */; }; - DDC4E20C8BDB38D0087A5190 /* TodoDetailContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1EE8F142D93B01909B6D5C8 /* TodoDetailContentView.swift */; }; - DFF40ACAFF99281CECC2303E /* PushNotificationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C346B13A821F3DFAB3489BBB /* PushNotificationSettingsView.swift */; }; - E53B0B91BEE6857758DAF9ED /* TodoItemRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EA81C7F4127479FE7CCE033 /* TodoItemRow.swift */; }; - E548BBC38D99E0D2C7D1FA4B /* Tags.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2D1521A459AF5AEFFA99A83 /* Tags.swift */; }; - E635248ED51664A502A7F08A /* ThemeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89BC344F5437426E7A309429 /* ThemeView.swift */; }; - E8077B723C81E374416DE861 /* TodoEditorWindowValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FA597F8A6C68F9460DE11FC /* TodoEditorWindowValue.swift */; }; + E1AC015EB5592C1F39C252BB /* TodayViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA69EA219374DD1E040CBA4B /* TodayViewModel.swift */; }; E95BB5F53AE62F152798567D /* DevLogDomain.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 950469A44DFD1834A6BFC4B2 /* DevLogDomain.framework */; }; - ED2003CBD86C61AB483F9D15 /* TodoManageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41DC5D62BCD0C53ADF8E7A6 /* TodoManageView.swift */; }; - EDDAC6A024EB85257A37723E /* TodayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C91DE4D388124119156BC64E /* TodayView.swift */; }; - F20FC30D05246FD333F034DE /* SettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B81F9AF1E3CEC36D194A916C /* SettingViewModel.swift */; }; - F5D87CFA04C5DF6A6FA36129 /* TodoWindowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0435F01417FE17DE757ADB /* TodoWindowCoordinator.swift */; }; - FA2E29AB049CAC97DE350BE9 /* ProfileViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A87C034F294C1825D3AC49C3 /* ProfileViewCoordinator.swift */; }; + F62CD09589BE8D580A4197B3 /* MainViewDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6320220D0E377BC611B291CA /* MainViewDependencies.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -131,7 +102,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 085FC0504CE36484B655FB09 /* TodoEditorWindowEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoEditorWindowEvent.swift; sourceTree = ""; }; + 04907C8B1F51AC9D8A164F02 /* TodoWindowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoWindowCoordinator.swift; sourceTree = ""; }; + 08332CE6C5C2F9DC7B78278D /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModel.swift; sourceTree = ""; }; 08ACC3D94A90881383764E6C /* TodoIdItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoIdItem.swift; sourceTree = ""; }; 09CC8A2FCF1577B150A08D83 /* ActivityKindItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityKindItem.swift; sourceTree = ""; }; 0B8F3DEEF6527F9FBCBDCD5A /* TodoReferenceItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoReferenceItem.swift; sourceTree = ""; }; @@ -139,88 +111,58 @@ 10169ED43745C8286BAA560A /* UserTodoCategoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTodoCategoryItem.swift; sourceTree = ""; }; 14DA5CF22287DA889A033F8E /* LoadingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingState.swift; sourceTree = ""; }; 160AF2C6625745DEFB4DB23C /* DevLogPresentationTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "DevLogPresentationTests-Info.plist"; sourceTree = ""; }; + 179B4DC72A2C2994456F609A /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; 1B6F1C309AC57614AD630F79 /* Error+SocialLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+SocialLogin.swift"; sourceTree = ""; }; - 2195BF2BC362FB4BD570C915 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; }; - 252057C930A70DC1D5674EE5 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; - 2663D0FE64234FEBBDA9362C /* PushNotificationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationSettingsViewModel.swift; sourceTree = ""; }; 290DE2215D7061AC10A057D8 /* MainTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTab.swift; sourceTree = ""; }; - 2AED8D48F34AE2476CCF4F5A /* TodoInfoSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoInfoSheetView.swift; sourceTree = ""; }; - 334EB88BC5FC0BD7DDA6A0FE /* TodoDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoDetailViewModel.swift; sourceTree = ""; }; - 34028ABF87BCFE5E95B8DF2C /* TodoListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoListView.swift; sourceTree = ""; }; 34C7A0A3DE0AB6D5E4DD116B /* PushNotificationItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationItem.swift; sourceTree = ""; }; - 36FA78DB36F62C17E9957AFF /* MainViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewCoordinator.swift; sourceTree = ""; }; 37323FDF36DF44C549A71A85 /* DeleteWebPageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteWebPageTests.swift; sourceTree = ""; }; 38030D2CAA016ABB3D0C396B /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = ""; }; - 3CA75873FCFFC6182662694B /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; - 3D6ACE383B5F1DA964DAB252 /* AccountViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewModel.swift; sourceTree = ""; }; - 3DC9F3129E8EEBF9CAC622E7 /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModel.swift; sourceTree = ""; }; - 3EA81C7F4127479FE7CCE033 /* TodoItemRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoItemRow.swift; sourceTree = ""; }; - 4279F380763E7CFF063F4416 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; - 437D0037871D0A9E16D94C59 /* HeatmapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeatmapView.swift; sourceTree = ""; }; - 46339787CA1BCF8EF8AACBC6 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; - 48DEAD71515D037F501E9B9F /* TodayViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewModel.swift; sourceTree = ""; }; + 482E7887408590D8E856673B /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; }; 4CD8C6D9E12E040F7B612FB1 /* HeatmapMonth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeatmapMonth.swift; sourceTree = ""; }; - 4F32D20E83ABB122630F67BD /* CheckBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBox.swift; sourceTree = ""; }; + 4D87C5787A3E9EAD0CC10A3D /* TodoViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoViewModelFactory.swift; sourceTree = ""; }; + 4E7A86BE16165EE865C2CCB1 /* RootViewDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewDependencies.swift; sourceTree = ""; }; 5156DCFD77B78CAEE004CDDD /* TestSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSupport.swift; sourceTree = ""; }; - 53892CB6FB8F39CE967A3344 /* RootViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModel.swift; sourceTree = ""; }; - 55E834C7D761EA77E717C9DE /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; 573AD2720FA89362FF4C4CEF /* HeatmapQuarter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeatmapQuarter.swift; sourceTree = ""; }; 587B2119664DB4FDF4A18FFE /* TodayTodoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayTodoItem.swift; sourceTree = ""; }; - 5A16A2DF4C226ACFE7E2F2BD /* NavigationBarConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarConfigurator.swift; sourceTree = ""; }; - 5F4AC141E5E2C9474DDBD35C /* ToolbarButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtons.swift; sourceTree = ""; }; + 600892295632C397ECDD8AF4 /* HomeViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewCoordinator.swift; sourceTree = ""; }; 62A6CF5AAB9CCEAB35FB10A8 /* RecentTodoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentTodoItem.swift; sourceTree = ""; }; + 6320220D0E377BC611B291CA /* MainViewDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewDependencies.swift; sourceTree = ""; }; 6AC3FF6088F0D784788A2E07 /* DevLogPresentationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DevLogPresentationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 6B457717DEF395F131159C04 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; 70A888BE16FA57ED1309E014 /* DevLogCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 70B652BF58D45805A57ECCE0 /* HeatmapDay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeatmapDay.swift; sourceTree = ""; }; - 7236EA0D4F011DB8D2922A65 /* HomeViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewCoordinator.swift; sourceTree = ""; }; - 79043B9D995BD5D987320FE6 /* EnvironmentValues+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentValues+.swift"; sourceTree = ""; }; - 7DDABB53271F38BCC892B6DD /* TodoListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoListViewModel.swift; sourceTree = ""; }; - 7FA597F8A6C68F9460DE11FC /* TodoEditorWindowValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoEditorWindowValue.swift; sourceTree = ""; }; - 80B7DE00658AC1080A3741C9 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; - 82C8541262E532FA4A218650 /* TodoEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoEditorView.swift; sourceTree = ""; }; + 776EC7BA6989C8AEE6BBB558 /* SettingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewModel.swift; sourceTree = ""; }; + 7D3F311FECBA18B5EE105FD2 /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; }; + 81D2699575F2C695C72E294D /* AccountViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewModel.swift; sourceTree = ""; }; + 82BEFB32E7D34AB261823961 /* HomeRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeRoute.swift; sourceTree = ""; }; 852D34A35C60375D1D9F9566 /* Color+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Hex.swift"; sourceTree = ""; }; - 89BC344F5437426E7A309429 /* ThemeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeView.swift; sourceTree = ""; }; + 87EE61156B04A85F894BD7D2 /* TodoManageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoManageViewModel.swift; sourceTree = ""; }; + 8C76C1FD0B57BC6E736F16F9 /* TodoEditorWindowDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoEditorWindowDependencies.swift; sourceTree = ""; }; + 91689B5FE51289C142DFD6BC /* RootViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModel.swift; sourceTree = ""; }; + 91B75A48E6680752B9DE82C9 /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; 950469A44DFD1834A6BFC4B2 /* DevLogDomain.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogDomain.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9A6BFC9AC306836245B6E5CD /* TodoDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoDetailView.swift; sourceTree = ""; }; - 9E2D739A2380C3D28C575D08 /* TodoEditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoEditorViewModel.swift; sourceTree = ""; }; A05BB3C999F4A364401871C5 /* DeletePushNotificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletePushNotificationTests.swift; sourceTree = ""; }; - A4CFA6FFA82E0EC3C25D527B /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = ""; }; - A6F52E54B2F053507AE43819 /* PushNotificationListViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationListViewCoordinator.swift; sourceTree = ""; }; - A87C034F294C1825D3AC49C3 /* ProfileViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewCoordinator.swift; sourceTree = ""; }; - A9B434E6F27EFBC9D054461A /* TodayViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewCoordinator.swift; sourceTree = ""; }; - AB112B525D9D6CB7B001028A /* RelativeTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelativeTimeText.swift; sourceTree = ""; }; ACA48428342FE3040FE708BE /* Version.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Version.xcconfig; sourceTree = ""; }; - B1EE8F142D93B01909B6D5C8 /* TodoDetailContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoDetailContentView.swift; sourceTree = ""; }; - B2D1521A459AF5AEFFA99A83 /* Tags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tags.swift; sourceTree = ""; }; - B81F9AF1E3CEC36D194A916C /* SettingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewModel.swift; sourceTree = ""; }; - B8C462E5D83C6B639A66ED26 /* LoginButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginButton.swift; sourceTree = ""; }; - BA1D8AFC51309CDFB5962985 /* WebItemRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebItemRow.swift; sourceTree = ""; }; + ACD429F304D34C5345FCD425 /* PushNotificationListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationListViewModel.swift; sourceTree = ""; }; + AD7822B9271036D58AB46DB3 /* PushNotificationListViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationListViewCoordinator.swift; sourceTree = ""; }; + BA69EA219374DD1E040CBA4B /* TodayViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewModel.swift; sourceTree = ""; }; BAD66EB3834CFD3D64831511 /* DevLogPresentation-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "DevLogPresentation-Info.plist"; sourceTree = ""; }; - BF0435F01417FE17DE757ADB /* TodoWindowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoWindowCoordinator.swift; sourceTree = ""; }; C0CBB0EEB3B15A4BE2EF9641 /* SystemTodoCategoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemTodoCategoryItem.swift; sourceTree = ""; }; - C25193961AAF03B736D8BA4D /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; }; - C346B13A821F3DFAB3489BBB /* PushNotificationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationSettingsView.swift; sourceTree = ""; }; - C52955505427FEA95D9B3CC8 /* CacheableImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheableImage.swift; sourceTree = ""; }; C8D191AD793EE0AC702B1539 /* WebPageItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebPageItem.swift; sourceTree = ""; }; - C91DE4D388124119156BC64E /* TodayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayView.swift; sourceTree = ""; }; - CFB7CB1992F8434647D2E8D1 /* TodoMarkdownContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoMarkdownContentView.swift; sourceTree = ""; }; - D136528209E2B94793441C78 /* NavigationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouter.swift; sourceTree = ""; }; + CE1E4F3FA850AB900BD0837A /* TodayRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayRoute.swift; sourceTree = ""; }; + CF251D17AB6A6BE27E94F83F /* TodoEditorWindowEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoEditorWindowEvent.swift; sourceTree = ""; }; + D15BF430A0A932B943CE74B1 /* ProfileViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewCoordinator.swift; sourceTree = ""; }; D36A4E11056749B1D5DEF446 /* DevLogPresentation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogPresentation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D87DA85DC7F13D4E5B570ED6 /* UIKitTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitTextEditor.swift; sourceTree = ""; }; - DA4D52F1343CECB8E0AC4742 /* View+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+.swift"; sourceTree = ""; }; + D92080C40972043791AC990C /* TodayViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewCoordinator.swift; sourceTree = ""; }; + D92F895EDEF78B5D98FB2A38 /* NavigationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouter.swift; sourceTree = ""; }; DE04D300F1054596E630CF48 /* TodoCategoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoCategoryItem.swift; sourceTree = ""; }; E174814D9FFAFC79746E1902 /* HeatmapActivityItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeatmapActivityItem.swift; sourceTree = ""; }; - E41DC5D62BCD0C53ADF8E7A6 /* TodoManageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoManageView.swift; sourceTree = ""; }; - E71459EB54EF7A0B3F294418 /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = ""; }; - E802368D0BA811BD50470735 /* TodoEditorWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoEditorWindowView.swift; sourceTree = ""; }; - E90A0B2F4523B8570322F54C /* TodoManageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoManageViewModel.swift; sourceTree = ""; }; - EACFDECD5652C67B68D4936D /* PushNotificationListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationListViewModel.swift; sourceTree = ""; }; - F6D5BDBC6990696C3FA5B22A /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; - FB82F4B31B49736F70F50EE7 /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = ""; }; - FCFF8EE1B8855BA6B7BC315F /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; }; - FE62ED6DF757AF15ACBB2E07 /* PushNotificationListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationListView.swift; sourceTree = ""; }; - FF52D6EE30673D682147FAFE /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; + E2DDC14187D2E0615DBF2A5E /* ProfileRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRoute.swift; sourceTree = ""; }; + ECABD5E04C07111C89C7DDB3 /* TodoListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoListViewModel.swift; sourceTree = ""; }; + ED515F28865033FBA78DCAB6 /* TodoEditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoEditorViewModel.swift; sourceTree = ""; }; + F9206293A713E904877705C2 /* TodoEditorWindowValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoEditorWindowValue.swift; sourceTree = ""; }; + FAD6E606DC53732CA7DECB2D /* TodoDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoDetailViewModel.swift; sourceTree = ""; }; + FC66A467D5E3A2EB391E90DE /* PushNotificationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationSettingsViewModel.swift; sourceTree = ""; }; + FD678C679B9975DFD646F8FE /* MainViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewCoordinator.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -246,16 +188,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 08B3FDD8A289549DF72F6194 /* PushNotification */ = { - isa = PBXGroup; - children = ( - FE62ED6DF757AF15ACBB2E07 /* PushNotificationListView.swift */, - A6F52E54B2F053507AE43819 /* PushNotificationListViewCoordinator.swift */, - EACFDECD5652C67B68D4936D /* PushNotificationListViewModel.swift */, - ); - path = PushNotification; - sourceTree = ""; - }; 14A1A6F2D3820B4A5D46C522 /* Structure */ = { isa = PBXGroup; children = ( @@ -270,34 +202,18 @@ 27063F597048D820F9883728 /* Common */ = { isa = PBXGroup; children = ( - 371B9B2F1875AEA0F43E3E0E /* Component */, 14DA5CF22287DA889A033F8E /* LoadingState.swift */, - 5A16A2DF4C226ACFE7E2F2BD /* NavigationBarConfigurator.swift */, - D136528209E2B94793441C78 /* NavigationRouter.swift */, - B1EE8F142D93B01909B6D5C8 /* TodoDetailContentView.swift */, - 2AED8D48F34AE2476CCF4F5A /* TodoInfoSheetView.swift */, - CFB7CB1992F8434647D2E8D1 /* TodoMarkdownContentView.swift */, - E71459EB54EF7A0B3F294418 /* WebView.swift */, ); path = Common; sourceTree = ""; }; - 371B9B2F1875AEA0F43E3E0E /* Component */ = { + 37D573EBC059DB8F9B39E8A5 /* Home */ = { isa = PBXGroup; children = ( - C52955505427FEA95D9B3CC8 /* CacheableImage.swift */, - 4F32D20E83ABB122630F67BD /* CheckBox.swift */, - 46339787CA1BCF8EF8AACBC6 /* LoadingView.swift */, - B8C462E5D83C6B639A66ED26 /* LoginButton.swift */, - AB112B525D9D6CB7B001028A /* RelativeTimeText.swift */, - B2D1521A459AF5AEFFA99A83 /* Tags.swift */, - F6D5BDBC6990696C3FA5B22A /* Toast.swift */, - 3EA81C7F4127479FE7CCE033 /* TodoItemRow.swift */, - 5F4AC141E5E2C9474DDBD35C /* ToolbarButtons.swift */, - D87DA85DC7F13D4E5B570ED6 /* UIKitTextEditor.swift */, - BA1D8AFC51309CDFB5962985 /* WebItemRow.swift */, + 600892295632C397ECDD8AF4 /* HomeViewCoordinator.swift */, + 04907C8B1F51AC9D8A164F02 /* TodoWindowCoordinator.swift */, ); - path = Component; + path = Home; sourceTree = ""; }; 390E12487BFADB4E24342432 = { @@ -316,6 +232,15 @@ path = PushNotification; sourceTree = ""; }; + 42BBD3AF2069409BC92F4836 /* Window */ = { + isa = PBXGroup; + children = ( + CF251D17AB6A6BE27E94F83F /* TodoEditorWindowEvent.swift */, + F9206293A713E904877705C2 /* TodoEditorWindowValue.swift */, + ); + path = Window; + sourceTree = ""; + }; 45314146CCCDB21E58DBA0E4 /* Todo */ = { isa = PBXGroup; children = ( @@ -331,97 +256,75 @@ path = Todo; sourceTree = ""; }; + 4C1C8973B4B04EE1A85E7BC3 /* Main */ = { + isa = PBXGroup; + children = ( + FD678C679B9975DFD646F8FE /* MainViewCoordinator.swift */, + ); + path = Main; + sourceTree = ""; + }; 4D616118490727A8BF1F8245 /* Sources */ = { isa = PBXGroup; children = ( 27063F597048D820F9883728 /* Common */, + 5F58BA536C964633FB5CDF43 /* Coordinator */, + 541E93B971F2E4140A81FFAA /* Dependency */, EB3C311DC8A5D8096FAA76BC /* Extension */, - 5E947E5BECBCBF1C9366945E /* Home */, - BEA53BCE9F5CBA2B9D2748D9 /* Login */, - 95FF4A87BE2DD7696EA4E3D1 /* Main */, - 75CBC4AAD72E791B1C0D3F88 /* Profile */, F3DDEDB0AF4ACA47ACCB8AEA /* Protocol */, - 08B3FDD8A289549DF72F6194 /* PushNotification */, - E35F56087C5F3A236FD17FF2 /* Root */, D04E76D2F842BF00A4AC7226 /* Routing */, - 7530E5897034E53A8917ABEF /* Search */, - 7A7A479862A8D15E79182C88 /* Setting */, 14A1A6F2D3820B4A5D46C522 /* Structure */, - E90A426B16234A9CFAB33C3A /* Today */, + CD4F7027F750752F2BD6484D /* ViewModel */, + 42BBD3AF2069409BC92F4836 /* Window */, ); path = Sources; sourceTree = ""; }; - 553C78A90E9B182409A7EBAE /* Home */ = { - isa = PBXGroup; - children = ( - 252057C930A70DC1D5674EE5 /* HomeView.swift */, - 7236EA0D4F011DB8D2922A65 /* HomeViewCoordinator.swift */, - 55E834C7D761EA77E717C9DE /* HomeViewModel.swift */, - ); - path = Home; - sourceTree = ""; - }; - 5E947E5BECBCBF1C9366945E /* Home */ = { + 541E93B971F2E4140A81FFAA /* Dependency */ = { isa = PBXGroup; children = ( - 553C78A90E9B182409A7EBAE /* Home */, - 9A6BFC9AC306836245B6E5CD /* TodoDetailView.swift */, - 334EB88BC5FC0BD7DDA6A0FE /* TodoDetailViewModel.swift */, - 82C8541262E532FA4A218650 /* TodoEditorView.swift */, - 9E2D739A2380C3D28C575D08 /* TodoEditorViewModel.swift */, - 085FC0504CE36484B655FB09 /* TodoEditorWindowEvent.swift */, - 7FA597F8A6C68F9460DE11FC /* TodoEditorWindowValue.swift */, - E802368D0BA811BD50470735 /* TodoEditorWindowView.swift */, - 34028ABF87BCFE5E95B8DF2C /* TodoListView.swift */, - 7DDABB53271F38BCC892B6DD /* TodoListViewModel.swift */, - E41DC5D62BCD0C53ADF8E7A6 /* TodoManageView.swift */, - E90A0B2F4523B8570322F54C /* TodoManageViewModel.swift */, - BF0435F01417FE17DE757ADB /* TodoWindowCoordinator.swift */, + 6320220D0E377BC611B291CA /* MainViewDependencies.swift */, + 4E7A86BE16165EE865C2CCB1 /* RootViewDependencies.swift */, + 8C76C1FD0B57BC6E736F16F9 /* TodoEditorWindowDependencies.swift */, + 4D87C5787A3E9EAD0CC10A3D /* TodoViewModelFactory.swift */, ); - path = Home; + path = Dependency; sourceTree = ""; }; - 662F424B1BAF5366B2AE858C /* Derived */ = { + 5F58BA536C964633FB5CDF43 /* Coordinator */ = { isa = PBXGroup; children = ( - 9105D8FAAC29C3CC853C4FFB /* InfoPlists */, + 37D573EBC059DB8F9B39E8A5 /* Home */, + 4C1C8973B4B04EE1A85E7BC3 /* Main */, + CBE2F0C6D1C3C09A2123FB42 /* Profile */, + 6284DFBA7474E6C90C8AC1A8 /* PushNotification */, + 62DD3E6D02222D2A8BABFFE7 /* Today */, ); - path = Derived; + path = Coordinator; sourceTree = ""; }; - 7530E5897034E53A8917ABEF /* Search */ = { + 6284DFBA7474E6C90C8AC1A8 /* PushNotification */ = { isa = PBXGroup; children = ( - 80B7DE00658AC1080A3741C9 /* SearchView.swift */, - FCFF8EE1B8855BA6B7BC315F /* SearchViewModel.swift */, + AD7822B9271036D58AB46DB3 /* PushNotificationListViewCoordinator.swift */, ); - path = Search; + path = PushNotification; sourceTree = ""; }; - 75CBC4AAD72E791B1C0D3F88 /* Profile */ = { + 62DD3E6D02222D2A8BABFFE7 /* Today */ = { isa = PBXGroup; children = ( - 437D0037871D0A9E16D94C59 /* HeatmapView.swift */, - 3CA75873FCFFC6182662694B /* ProfileView.swift */, - A87C034F294C1825D3AC49C3 /* ProfileViewCoordinator.swift */, - 3DC9F3129E8EEBF9CAC622E7 /* ProfileViewModel.swift */, + D92080C40972043791AC990C /* TodayViewCoordinator.swift */, ); - path = Profile; + path = Today; sourceTree = ""; }; - 7A7A479862A8D15E79182C88 /* Setting */ = { + 662F424B1BAF5366B2AE858C /* Derived */ = { isa = PBXGroup; children = ( - A4CFA6FFA82E0EC3C25D527B /* AccountView.swift */, - 3D6ACE383B5F1DA964DAB252 /* AccountViewModel.swift */, - C346B13A821F3DFAB3489BBB /* PushNotificationSettingsView.swift */, - 2663D0FE64234FEBBDA9362C /* PushNotificationSettingsViewModel.swift */, - FB82F4B31B49736F70F50EE7 /* SettingView.swift */, - B81F9AF1E3CEC36D194A916C /* SettingViewModel.swift */, - 89BC344F5437426E7A309429 /* ThemeView.swift */, + 9105D8FAAC29C3CC853C4FFB /* InfoPlists */, ); - path = Setting; + path = Derived; sourceTree = ""; }; 80795F4F20E81C1E84803EB8 /* Support */ = { @@ -464,39 +367,54 @@ name = Products; sourceTree = ""; }; - 95FF4A87BE2DD7696EA4E3D1 /* Main */ = { + BF5F3862615F56298F9D9B7B /* Tests */ = { isa = PBXGroup; children = ( - 6B457717DEF395F131159C04 /* MainView.swift */, - 36FA78DB36F62C17E9957AFF /* MainViewCoordinator.swift */, - C25193961AAF03B736D8BA4D /* MainViewModel.swift */, + 3E97BDE9D214E3A24DC7BA47 /* PushNotification */, + 80795F4F20E81C1E84803EB8 /* Support */, + E58AB79C6479D3AAF6226813 /* WebPage */, ); - path = Main; + path = Tests; sourceTree = ""; }; - BEA53BCE9F5CBA2B9D2748D9 /* Login */ = { + CBE2F0C6D1C3C09A2123FB42 /* Profile */ = { isa = PBXGroup; children = ( - 4279F380763E7CFF063F4416 /* LoginView.swift */, - FF52D6EE30673D682147FAFE /* LoginViewModel.swift */, + D15BF430A0A932B943CE74B1 /* ProfileViewCoordinator.swift */, ); - path = Login; + path = Profile; sourceTree = ""; }; - BF5F3862615F56298F9D9B7B /* Tests */ = { + CD4F7027F750752F2BD6484D /* ViewModel */ = { isa = PBXGroup; children = ( - 3E97BDE9D214E3A24DC7BA47 /* PushNotification */, - 80795F4F20E81C1E84803EB8 /* Support */, - E58AB79C6479D3AAF6226813 /* WebPage */, - ); - path = Tests; + 81D2699575F2C695C72E294D /* AccountViewModel.swift */, + 91B75A48E6680752B9DE82C9 /* HomeViewModel.swift */, + 179B4DC72A2C2994456F609A /* LoginViewModel.swift */, + 482E7887408590D8E856673B /* MainViewModel.swift */, + 08332CE6C5C2F9DC7B78278D /* ProfileViewModel.swift */, + ACD429F304D34C5345FCD425 /* PushNotificationListViewModel.swift */, + FC66A467D5E3A2EB391E90DE /* PushNotificationSettingsViewModel.swift */, + 91689B5FE51289C142DFD6BC /* RootViewModel.swift */, + 7D3F311FECBA18B5EE105FD2 /* SearchViewModel.swift */, + 776EC7BA6989C8AEE6BBB558 /* SettingViewModel.swift */, + BA69EA219374DD1E040CBA4B /* TodayViewModel.swift */, + FAD6E606DC53732CA7DECB2D /* TodoDetailViewModel.swift */, + ED515F28865033FBA78DCAB6 /* TodoEditorViewModel.swift */, + ECABD5E04C07111C89C7DDB3 /* TodoListViewModel.swift */, + 87EE61156B04A85F894BD7D2 /* TodoManageViewModel.swift */, + ); + path = ViewModel; sourceTree = ""; }; D04E76D2F842BF00A4AC7226 /* Routing */ = { isa = PBXGroup; children = ( + 82BEFB32E7D34AB261823961 /* HomeRoute.swift */, 290DE2215D7061AC10A057D8 /* MainTab.swift */, + D92F895EDEF78B5D98FB2A38 /* NavigationRouter.swift */, + E2DDC14187D2E0615DBF2A5E /* ProfileRoute.swift */, + CE1E4F3FA850AB900BD0837A /* TodayRoute.swift */, ); path = Routing; sourceTree = ""; @@ -512,15 +430,6 @@ name = Project; sourceTree = ""; }; - E35F56087C5F3A236FD17FF2 /* Root */ = { - isa = PBXGroup; - children = ( - 2195BF2BC362FB4BD570C915 /* RootView.swift */, - 53892CB6FB8F39CE967A3344 /* RootViewModel.swift */, - ); - path = Root; - sourceTree = ""; - }; E58AB79C6479D3AAF6226813 /* WebPage */ = { isa = PBXGroup; children = ( @@ -529,23 +438,11 @@ path = WebPage; sourceTree = ""; }; - E90A426B16234A9CFAB33C3A /* Today */ = { - isa = PBXGroup; - children = ( - C91DE4D388124119156BC64E /* TodayView.swift */, - A9B434E6F27EFBC9D054461A /* TodayViewCoordinator.swift */, - 48DEAD71515D037F501E9B9F /* TodayViewModel.swift */, - ); - path = Today; - sourceTree = ""; - }; EB3C311DC8A5D8096FAA76BC /* Extension */ = { isa = PBXGroup; children = ( 852D34A35C60375D1D9F9566 /* Color+Hex.swift */, - 79043B9D995BD5D987320FE6 /* EnvironmentValues+.swift */, 1B6F1C309AC57614AD630F79 /* Error+SocialLogin.swift */, - DA4D52F1343CECB8E0AC4742 /* View+.swift */, ); path = Extension; sourceTree = ""; @@ -674,68 +571,25 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - CAD9F0F9CDADF2FA55CBC78B /* CacheableImage.swift in Sources */, - 042A04373E0BF17A8C0E90A1 /* CheckBox.swift in Sources */, - 0BDF307FFD70E121BFE54B64 /* LoadingView.swift in Sources */, - 57373F815DB5EF9310AC641D /* LoginButton.swift in Sources */, - C3883077C738EF1E76DFCC4C /* RelativeTimeText.swift in Sources */, - E548BBC38D99E0D2C7D1FA4B /* Tags.swift in Sources */, - 21D9F0E69F0F648AA719D3A8 /* Toast.swift in Sources */, - E53B0B91BEE6857758DAF9ED /* TodoItemRow.swift in Sources */, - 58A933F1C49D9977217BE84C /* ToolbarButtons.swift in Sources */, - 711C3E31B02B60A4E1FB01CF /* UIKitTextEditor.swift in Sources */, - BE51244E594875155551774A /* WebItemRow.swift in Sources */, D878C47DE4B2E1A9525FBCBF /* LoadingState.swift in Sources */, - AFF2EFF4181D0AB292540962 /* NavigationBarConfigurator.swift in Sources */, - 9D69715291EB7F2AE704A722 /* NavigationRouter.swift in Sources */, - DDC4E20C8BDB38D0087A5190 /* TodoDetailContentView.swift in Sources */, - 70FC630DC3FC27D77EA868FD /* TodoInfoSheetView.swift in Sources */, - 4C07B469D6576A76BE71E639 /* TodoMarkdownContentView.swift in Sources */, - 5FF8EAE9E54F8359A5391EFC /* WebView.swift in Sources */, + C9553B4DC3249D9EC36473AC /* HomeViewCoordinator.swift in Sources */, + D7D6E761D1C930C71E445FC6 /* TodoWindowCoordinator.swift in Sources */, + AD97294A20E386D840EADDDB /* MainViewCoordinator.swift in Sources */, + A7605511964265827B06E268 /* ProfileViewCoordinator.swift in Sources */, + CDD5185569899FF1F600DD08 /* PushNotificationListViewCoordinator.swift in Sources */, + DC04D254D882504D1F00F609 /* TodayViewCoordinator.swift in Sources */, + F62CD09589BE8D580A4197B3 /* MainViewDependencies.swift in Sources */, + 3F8512559CCF262222ACCAE2 /* RootViewDependencies.swift in Sources */, + 26F47ED71B5B0701B1344A17 /* TodoEditorWindowDependencies.swift in Sources */, + 0C65DE6F8B9FB06F8D121D1E /* TodoViewModelFactory.swift in Sources */, 26ED8F658B02153735BD8692 /* Color+Hex.swift in Sources */, - 0675430931A12884236C92E7 /* EnvironmentValues+.swift in Sources */, AECD97E6C980B7940A3112E4 /* Error+SocialLogin.swift in Sources */, - 653BC11E9CB8F74FC090E3A1 /* View+.swift in Sources */, - 9ADFFABEE75489A526E1ECE5 /* HomeView.swift in Sources */, - 9060015EC9ECBFF1A2511DC6 /* HomeViewCoordinator.swift in Sources */, - C13E62E8FB7D681C14A42AC1 /* HomeViewModel.swift in Sources */, - 3A78734125366B547D0C4261 /* TodoDetailView.swift in Sources */, - D4C92919FF08171E53C15011 /* TodoDetailViewModel.swift in Sources */, - D988D2118F62024F5BA6A095 /* TodoEditorView.swift in Sources */, - 959832CAA00E2B06DF0B8A83 /* TodoEditorViewModel.swift in Sources */, - 175898C005C97BED56549457 /* TodoEditorWindowEvent.swift in Sources */, - E8077B723C81E374416DE861 /* TodoEditorWindowValue.swift in Sources */, - C2530FF7F76903E54626D73B /* TodoEditorWindowView.swift in Sources */, - 0B436A4BB4F4289DC5A24286 /* TodoListView.swift in Sources */, - 90A88D80EF02129348CE35C5 /* TodoListViewModel.swift in Sources */, - ED2003CBD86C61AB483F9D15 /* TodoManageView.swift in Sources */, - 40B4784EB5FDD90B5A2EC81B /* TodoManageViewModel.swift in Sources */, - F5D87CFA04C5DF6A6FA36129 /* TodoWindowCoordinator.swift in Sources */, - 3801176C353549D8D259FF4A /* LoginView.swift in Sources */, - 3AE04B27F085D1E67BA64537 /* LoginViewModel.swift in Sources */, - 49631F09477091376DDA0E4D /* MainView.swift in Sources */, - BCDC49501F5BA61CABB12D2B /* MainViewCoordinator.swift in Sources */, - 19A49575DE29575B3E30D42F /* MainViewModel.swift in Sources */, - 8D320EC70D396E019F964166 /* HeatmapView.swift in Sources */, - 5931685A7811D0DCDF5508BF /* ProfileView.swift in Sources */, - FA2E29AB049CAC97DE350BE9 /* ProfileViewCoordinator.swift in Sources */, - 5FD86AC94DE236C77BA69CB0 /* ProfileViewModel.swift in Sources */, 173C43896990D2469CB29D6B /* Store.swift in Sources */, - 8A76D55BB0714508C1019108 /* PushNotificationListView.swift in Sources */, - 164FF0D15F3671EC5B8175B4 /* PushNotificationListViewCoordinator.swift in Sources */, - 6DC7980E5F659AC3FB56427E /* PushNotificationListViewModel.swift in Sources */, - A302B2F9FDCDAFAE5A852E94 /* RootView.swift in Sources */, - 84866D96D8DE6F76E805050F /* RootViewModel.swift in Sources */, + DA951ACBCB7C625E36B2E709 /* HomeRoute.swift in Sources */, 41A5C4626505C6BF0643E43B /* MainTab.swift in Sources */, - 909B1608BDEDA51B07F27332 /* SearchView.swift in Sources */, - 2B0C47EBD387EEABE2A90A99 /* SearchViewModel.swift in Sources */, - 439047A7AF80D876838710F2 /* AccountView.swift in Sources */, - D7E51185AD82E7DB333300AF /* AccountViewModel.swift in Sources */, - DFF40ACAFF99281CECC2303E /* PushNotificationSettingsView.swift in Sources */, - BC607762E625C0DCD3B66E8A /* PushNotificationSettingsViewModel.swift in Sources */, - D99AD1D26B1D745028A93B8A /* SettingView.swift in Sources */, - F20FC30D05246FD333F034DE /* SettingViewModel.swift in Sources */, - E635248ED51664A502A7F08A /* ThemeView.swift in Sources */, + 3D0FB9275F7BC7D914D8EC55 /* NavigationRouter.swift in Sources */, + A641ED77789933A35718DC56 /* ProfileRoute.swift in Sources */, + 5357B7DBEC07C590B8C70E20 /* TodayRoute.swift in Sources */, 4BE7032EE7146793A5C58A94 /* ActivityKindItem.swift in Sources */, CB6204EC0FB8557D7FAF7174 /* HeatmapActivityItem.swift in Sources */, 2DB84D2FBEA0653D48DF614F /* HeatmapDay.swift in Sources */, @@ -751,9 +605,23 @@ 03CF7E696626224FFE93FFDD /* TodoReferenceItem.swift in Sources */, 053BC20B9244F93FFE9E6FA7 /* UserTodoCategoryItem.swift in Sources */, 277C3C6802370B2DB28EA92A /* WebPageItem.swift in Sources */, - EDDAC6A024EB85257A37723E /* TodayView.swift in Sources */, - B8792A002416865EF41FCB5D /* TodayViewCoordinator.swift in Sources */, - B81EE3C71EC5CCC8CB9D5CAC /* TodayViewModel.swift in Sources */, + ADFE22BA856BD7974978B222 /* AccountViewModel.swift in Sources */, + 4E725B56D14497FA73132F9F /* HomeViewModel.swift in Sources */, + 34DFDBE6AC68454DD2A9B548 /* LoginViewModel.swift in Sources */, + 4C1B42EE18ECF68DE71DBE47 /* MainViewModel.swift in Sources */, + CD9C0B62EAAB0FB928703250 /* ProfileViewModel.swift in Sources */, + 5DCFBD39AF6AC33B30265D73 /* PushNotificationListViewModel.swift in Sources */, + 7B3DB4474B7B6C2E948A277C /* PushNotificationSettingsViewModel.swift in Sources */, + 35649E3226AD3D47FB9D2C59 /* RootViewModel.swift in Sources */, + 6DCE29BCA008EFF51A476D7D /* SearchViewModel.swift in Sources */, + AD05101B81F4D267A87AC7A7 /* SettingViewModel.swift in Sources */, + E1AC015EB5592C1F39C252BB /* TodayViewModel.swift in Sources */, + 4ED29B9BB4B0F12363BFB927 /* TodoDetailViewModel.swift in Sources */, + C2358524334557A83CFE10DE /* TodoEditorViewModel.swift in Sources */, + 94358125409F95AD6FEE75F6 /* TodoListViewModel.swift in Sources */, + C28BB5DECB3AA31ED031D90C /* TodoManageViewModel.swift in Sources */, + AC7102A1D35CB1817BD885E8 /* TodoEditorWindowEvent.swift in Sources */, + 35639553522A1CEFDCF455D7 /* TodoEditorWindowValue.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Application/DevLogPresentation/Sources/Common/LoadingState.swift b/Application/DevLogPresentation/Sources/Common/LoadingState.swift index 00aca6c5..4ce62b60 100644 --- a/Application/DevLogPresentation/Sources/Common/LoadingState.swift +++ b/Application/DevLogPresentation/Sources/Common/LoadingState.swift @@ -26,7 +26,7 @@ public final class LoadingState { private var visibleDelayedTargets = Set() private var visibleTargets = Set() - init(delay: Duration = .seconds(0.3)) { + public init(delay: Duration = .seconds(0.3)) { self.delay = delay } diff --git a/Application/DevLogPresentation/Sources/Home/Home/HomeViewCoordinator.swift b/Application/DevLogPresentation/Sources/Coordinator/Home/HomeViewCoordinator.swift similarity index 83% rename from Application/DevLogPresentation/Sources/Home/Home/HomeViewCoordinator.swift rename to Application/DevLogPresentation/Sources/Coordinator/Home/HomeViewCoordinator.swift index fbbc9410..7ef19f7c 100644 --- a/Application/DevLogPresentation/Sources/Home/Home/HomeViewCoordinator.swift +++ b/Application/DevLogPresentation/Sources/Coordinator/Home/HomeViewCoordinator.swift @@ -12,14 +12,14 @@ import DevLogDomain @MainActor @Observable -final class HomeViewCoordinator { - let viewModel: HomeViewModel - let router = NavigationRouter() +public final class HomeViewCoordinator { + public let viewModel: HomeViewModel + public let router = NavigationRouter() private let container: DIContainer @ObservationIgnored private var cancellable: AnyCancellable? - init(container: DIContainer) { + public init(container: DIContainer) { self.container = container self.viewModel = HomeViewModel( fetchPreferencesUseCase: container.resolve(FetchTodoCategoryPreferencesUseCase.self), @@ -34,11 +34,11 @@ final class HomeViewCoordinator { ) } - func fetchData() { + public func fetchData() { viewModel.send(.fetchData) } - func bindWindowEvent(_ windowEvent: TodoEditorWindowEvent) { + public func bindWindowEvent(_ windowEvent: TodoEditorWindowEvent) { guard cancellable == nil else { return } cancellable = windowEvent.submits @@ -48,11 +48,11 @@ final class HomeViewCoordinator { } } - func makeTodoManageViewModel() -> TodoManageViewModel { + public func makeTodoManageViewModel() -> TodoManageViewModel { TodoManageViewModel(viewModel.state.preferences) } - func makeTodoEditorViewModel(category: TodoCategory) -> TodoEditorViewModel { + public func makeTodoEditorViewModel(category: TodoCategory) -> TodoEditorViewModel { TodoEditorViewModel( category: category, fetchPreferencesUseCase: container.resolve(FetchTodoCategoryPreferencesUseCase.self), @@ -66,7 +66,7 @@ final class HomeViewCoordinator { ) } - func makeSearchViewModel() -> SearchViewModel { + public func makeSearchViewModel() -> SearchViewModel { SearchViewModel( fetchWebPagesUseCase: container.resolve(FetchWebPagesUseCase.self), fetchTodosUseCase: container.resolve(FetchTodosUseCase.self), diff --git a/Application/DevLogPresentation/Sources/Home/TodoWindowCoordinator.swift b/Application/DevLogPresentation/Sources/Coordinator/Home/TodoWindowCoordinator.swift similarity index 90% rename from Application/DevLogPresentation/Sources/Home/TodoWindowCoordinator.swift rename to Application/DevLogPresentation/Sources/Coordinator/Home/TodoWindowCoordinator.swift index 525daf60..55b638ac 100644 --- a/Application/DevLogPresentation/Sources/Home/TodoWindowCoordinator.swift +++ b/Application/DevLogPresentation/Sources/Coordinator/Home/TodoWindowCoordinator.swift @@ -12,7 +12,7 @@ import DevLogDomain @MainActor @Observable -final class TodoWindowCoordinator { +public final class TodoWindowCoordinator { private let container: DIContainer @ObservationIgnored private var listViewModel: TodoListViewModel? @@ -21,11 +21,11 @@ final class TodoWindowCoordinator { @ObservationIgnored private var cancellable: AnyCancellable? - init(container: DIContainer) { + public init(container: DIContainer) { self.container = container } - func bindWindowEvent(_ windowEvent: TodoEditorWindowEvent) { + public func bindWindowEvent(_ windowEvent: TodoEditorWindowEvent) { guard cancellable == nil else { return } cancellable = windowEvent.submits @@ -34,7 +34,7 @@ final class TodoWindowCoordinator { } } - func makeListViewModel(category: TodoCategory) -> TodoListViewModel { + public func makeListViewModel(category: TodoCategory) -> TodoListViewModel { if let listViewModel, listViewModel.category == category { return listViewModel @@ -53,7 +53,7 @@ final class TodoWindowCoordinator { return listViewModel } - func makeDetailViewModel( + public func makeDetailViewModel( todoId: String, showEditButton: Bool = true ) -> TodoDetailViewModel { diff --git a/Application/DevLogPresentation/Sources/Main/MainViewCoordinator.swift b/Application/DevLogPresentation/Sources/Coordinator/Main/MainViewCoordinator.swift similarity index 77% rename from Application/DevLogPresentation/Sources/Main/MainViewCoordinator.swift rename to Application/DevLogPresentation/Sources/Coordinator/Main/MainViewCoordinator.swift index 825265f8..28911670 100644 --- a/Application/DevLogPresentation/Sources/Main/MainViewCoordinator.swift +++ b/Application/DevLogPresentation/Sources/Coordinator/Main/MainViewCoordinator.swift @@ -11,10 +11,10 @@ import DevLogDomain @MainActor @Observable -final class MainViewCoordinator { - let viewModel: MainViewModel +public final class MainViewCoordinator { + public let viewModel: MainViewModel - init(container: DIContainer) { + public init(container: DIContainer) { self.viewModel = MainViewModel( trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self), unreadPushCountUseCase: container.resolve(ObserveUnreadPushCountUseCase.self) diff --git a/Application/DevLogPresentation/Sources/Profile/ProfileViewCoordinator.swift b/Application/DevLogPresentation/Sources/Coordinator/Profile/ProfileViewCoordinator.swift similarity index 82% rename from Application/DevLogPresentation/Sources/Profile/ProfileViewCoordinator.swift rename to Application/DevLogPresentation/Sources/Coordinator/Profile/ProfileViewCoordinator.swift index 2a90f684..28b0180d 100644 --- a/Application/DevLogPresentation/Sources/Profile/ProfileViewCoordinator.swift +++ b/Application/DevLogPresentation/Sources/Coordinator/Profile/ProfileViewCoordinator.swift @@ -11,13 +11,13 @@ import DevLogDomain @MainActor @Observable -final class ProfileViewCoordinator { - let viewModel: ProfileViewModel - let settingViewModel: SettingViewModel - var router = NavigationRouter() +public final class ProfileViewCoordinator { + public let viewModel: ProfileViewModel + public let settingViewModel: SettingViewModel + public var router = NavigationRouter() private let container: DIContainer - init(container: DIContainer) { + public init(container: DIContainer) { self.container = container self.viewModel = ProfileViewModel( fetchUserDataUseCase: container.resolve(FetchUserDataUseCase.self), @@ -38,11 +38,11 @@ final class ProfileViewCoordinator { ) } - func fetchData() { + public func fetchData() { viewModel.send(.fetchData) } - func makeAccountViewModel() -> AccountViewModel { + public func makeAccountViewModel() -> AccountViewModel { AccountViewModel( fetchProvidersUseCase: container.resolve(FetchAuthProvidersUseCase.self), linkProviderUseCase: container.resolve(LinkAuthProviderUseCase.self), @@ -50,14 +50,14 @@ final class ProfileViewCoordinator { ) } - func makePushNotificationSettingsViewModel() -> PushNotificationSettingsViewModel { + public func makePushNotificationSettingsViewModel() -> PushNotificationSettingsViewModel { PushNotificationSettingsViewModel( fetchPushSettingsUseCase: container.resolve(FetchPushSettingsUseCase.self), updatePushSettingsUseCase: container.resolve(UpdatePushSettingsUseCase.self) ) } - func makeTodoDetailViewModel(todoId: String) -> TodoDetailViewModel { + public func makeTodoDetailViewModel(todoId: String) -> TodoDetailViewModel { TodoDetailViewModel( fetchTodoUseCase: container.resolve(FetchTodoByIdUseCase.self), fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self), diff --git a/Application/DevLogPresentation/Sources/PushNotification/PushNotificationListViewCoordinator.swift b/Application/DevLogPresentation/Sources/Coordinator/PushNotification/PushNotificationListViewCoordinator.swift similarity index 83% rename from Application/DevLogPresentation/Sources/PushNotification/PushNotificationListViewCoordinator.swift rename to Application/DevLogPresentation/Sources/Coordinator/PushNotification/PushNotificationListViewCoordinator.swift index 23b9bee8..c5830a64 100644 --- a/Application/DevLogPresentation/Sources/PushNotification/PushNotificationListViewCoordinator.swift +++ b/Application/DevLogPresentation/Sources/Coordinator/PushNotification/PushNotificationListViewCoordinator.swift @@ -11,14 +11,14 @@ import DevLogDomain @MainActor @Observable -final class PushNotificationListViewCoordinator { - let viewModel: PushNotificationListViewModel - var todoIdToPresent: TodoIdItem? +public final class PushNotificationListViewCoordinator { + public let viewModel: PushNotificationListViewModel + public var todoIdToPresent: TodoIdItem? private let container: DIContainer @ObservationIgnored private var todoDetailViewModel: TodoDetailViewModel? - init(container: DIContainer) { + public init(container: DIContainer) { self.container = container self.viewModel = PushNotificationListViewModel( fetchUseCase: container.resolve(FetchPushNotificationsUseCase.self), @@ -30,11 +30,11 @@ final class PushNotificationListViewCoordinator { ) } - func fetchData() { + public func fetchData() { viewModel.send(.fetchNotifications) } - func makeTodoDetailViewModel(todoId: String) -> TodoDetailViewModel { + public func makeTodoDetailViewModel(todoId: String) -> TodoDetailViewModel { if let todoDetailViewModel, todoDetailViewModel.todoId == todoId, !todoDetailViewModel.showEditButton { diff --git a/Application/DevLogPresentation/Sources/Today/TodayViewCoordinator.swift b/Application/DevLogPresentation/Sources/Coordinator/Today/TodayViewCoordinator.swift similarity index 79% rename from Application/DevLogPresentation/Sources/Today/TodayViewCoordinator.swift rename to Application/DevLogPresentation/Sources/Coordinator/Today/TodayViewCoordinator.swift index 22b052bf..e16aeb87 100644 --- a/Application/DevLogPresentation/Sources/Today/TodayViewCoordinator.swift +++ b/Application/DevLogPresentation/Sources/Coordinator/Today/TodayViewCoordinator.swift @@ -11,11 +11,11 @@ import DevLogDomain @MainActor @Observable -final class TodayViewCoordinator { - let viewModel: TodayViewModel - let router = NavigationRouter() +public final class TodayViewCoordinator { + public let viewModel: TodayViewModel + public let router = NavigationRouter() - init(container: DIContainer) { + public init(container: DIContainer) { self.viewModel = TodayViewModel( fetchTodosUseCase: container.resolve(FetchTodosUseCase.self), fetchTodoByIdUseCase: container.resolve(FetchTodoByIdUseCase.self), @@ -26,7 +26,7 @@ final class TodayViewCoordinator { ) } - func fetchData() { + public func fetchData() { viewModel.send(.fetchData) } } diff --git a/Application/DevLogPresentation/Sources/Dependency/MainViewDependencies.swift b/Application/DevLogPresentation/Sources/Dependency/MainViewDependencies.swift new file mode 100644 index 00000000..72b74be5 --- /dev/null +++ b/Application/DevLogPresentation/Sources/Dependency/MainViewDependencies.swift @@ -0,0 +1,29 @@ +// +// MainViewDependencies.swift +// DevLogPresentation +// +// Created by opfic on 6/2/26. +// + +import DevLogCore + +@MainActor +public struct MainViewDependencies { + public let coordinator: MainViewCoordinator + public let todoWindowCoordinator: TodoWindowCoordinator + public let homeViewCoordinator: HomeViewCoordinator + public let todayViewCoordinator: TodayViewCoordinator + public let pushNotificationListViewCoordinator: PushNotificationListViewCoordinator + public let profileViewCoordinator: ProfileViewCoordinator + public let todoViewModelFactory: TodoViewModelFactory + + public init(container: DIContainer) { + self.coordinator = MainViewCoordinator(container: container) + self.todoWindowCoordinator = TodoWindowCoordinator(container: container) + self.homeViewCoordinator = HomeViewCoordinator(container: container) + self.todayViewCoordinator = TodayViewCoordinator(container: container) + self.pushNotificationListViewCoordinator = PushNotificationListViewCoordinator(container: container) + self.profileViewCoordinator = ProfileViewCoordinator(container: container) + self.todoViewModelFactory = TodoViewModelFactory(container: container) + } +} diff --git a/Application/DevLogPresentation/Sources/Dependency/RootViewDependencies.swift b/Application/DevLogPresentation/Sources/Dependency/RootViewDependencies.swift new file mode 100644 index 00000000..9ea13444 --- /dev/null +++ b/Application/DevLogPresentation/Sources/Dependency/RootViewDependencies.swift @@ -0,0 +1,38 @@ +// +// RootViewDependencies.swift +// DevLogPresentation +// +// Created by opfic on 6/2/26. +// + +import DevLogCore +import DevLogDomain + +@MainActor +public struct RootViewDependencies { + public let viewModel: RootViewModel + public let mainViewDependencies: MainViewDependencies + public let makeLoginViewModel: () -> LoginViewModel + public let makeTodoDetailViewModel: (String) -> TodoDetailViewModel + + public init(container: DIContainer) { + self.viewModel = RootViewModel( + sessionUseCase: container.resolve(ObserveAuthSessionUseCase.self), + networkConnectivityUseCase: container.resolve(ObserveNetworkConnectivityUseCase.self), + systemThemeUseCase: container.resolve(ObserveSystemThemeUseCase.self), + trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self) + ) + self.mainViewDependencies = MainViewDependencies(container: container) + self.makeLoginViewModel = { + LoginViewModel(signInUseCase: container.resolve(SignInUseCase.self)) + } + self.makeTodoDetailViewModel = { todoId in + TodoDetailViewModel( + fetchTodoUseCase: container.resolve(FetchTodoByIdUseCase.self), + fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self), + todoId: todoId, + showEditButton: false + ) + } + } +} diff --git a/Application/DevLogPresentation/Sources/Dependency/TodoEditorWindowDependencies.swift b/Application/DevLogPresentation/Sources/Dependency/TodoEditorWindowDependencies.swift new file mode 100644 index 00000000..919ec9f2 --- /dev/null +++ b/Application/DevLogPresentation/Sources/Dependency/TodoEditorWindowDependencies.swift @@ -0,0 +1,47 @@ +// +// TodoEditorWindowDependencies.swift +// DevLogPresentation +// +// Created by opfic on 6/2/26. +// + +import DevLogCore +import DevLogDomain + +@MainActor +public struct TodoEditorWindowDependencies { + public let todoViewModelFactory: TodoViewModelFactory + private let windowEvent: TodoEditorWindowEvent + + public init( + container: DIContainer, + windowEvent: TodoEditorWindowEvent + ) { + self.todoViewModelFactory = TodoViewModelFactory(container: container) + self.windowEvent = windowEvent + } + + public func makeEditorViewModel( + value: TodoEditorWindowValue, + onClose: @escaping () -> Void + ) -> TodoEditorViewModel { + switch value { + case .create(let windowCategory, _): + return todoViewModelFactory.makeEditorViewModel( + category: windowCategory.todoCategory, + onUpsertSuccess: { todo in + windowEvent.submit(value: value, todo: todo) + onClose() + } + ) + case .edit(let windowTodo): + return todoViewModelFactory.makeEditorViewModel( + todo: windowTodo.todo, + onUpsertSuccess: { todo in + windowEvent.submit(value: value, todo: todo) + onClose() + } + ) + } + } +} diff --git a/Application/DevLogPresentation/Sources/Dependency/TodoViewModelFactory.swift b/Application/DevLogPresentation/Sources/Dependency/TodoViewModelFactory.swift new file mode 100644 index 00000000..fa00d82b --- /dev/null +++ b/Application/DevLogPresentation/Sources/Dependency/TodoViewModelFactory.swift @@ -0,0 +1,57 @@ +// +// TodoViewModelFactory.swift +// DevLogPresentation +// +// Created by opfic on 6/2/26. +// + +import DevLogCore +import DevLogDomain + +@MainActor +public struct TodoViewModelFactory { + private let container: DIContainer + + public init(container: DIContainer) { + self.container = container + } + + public func makeDetailViewModel( + todoId: String, + showEditButton: Bool = true + ) -> TodoDetailViewModel { + TodoDetailViewModel( + fetchTodoUseCase: container.resolve(FetchTodoByIdUseCase.self), + fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self), + todoId: todoId, + showEditButton: showEditButton + ) + } + + public func makeEditorViewModel( + category: TodoCategory, + onUpsertSuccess: ((Todo) -> Void)? = nil + ) -> TodoEditorViewModel { + TodoEditorViewModel( + category: category, + fetchPreferencesUseCase: container.resolve(FetchTodoCategoryPreferencesUseCase.self), + fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self), + upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self), + trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self), + onUpsertSuccess: onUpsertSuccess + ) + } + + public func makeEditorViewModel( + todo: Todo, + onUpsertSuccess: ((Todo) -> Void)? = nil + ) -> TodoEditorViewModel { + TodoEditorViewModel( + todo: todo, + fetchPreferencesUseCase: container.resolve(FetchTodoCategoryPreferencesUseCase.self), + fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self), + upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self), + onUpsertSuccess: onUpsertSuccess + ) + } +} diff --git a/Application/DevLogPresentation/Sources/Extension/Color+Hex.swift b/Application/DevLogPresentation/Sources/Extension/Color+Hex.swift index 43f2b88d..12c91e48 100644 --- a/Application/DevLogPresentation/Sources/Extension/Color+Hex.swift +++ b/Application/DevLogPresentation/Sources/Extension/Color+Hex.swift @@ -6,9 +6,8 @@ // import SwiftUI -import DevLogDomain -extension Color { +public extension Color { static var randomValue: Color { Color( red: Double(Int.random(in: 0...255)) / 255, diff --git a/Application/DevLogPresentation/Sources/Extension/Error+SocialLogin.swift b/Application/DevLogPresentation/Sources/Extension/Error+SocialLogin.swift index a6d061bf..8f24bf52 100644 --- a/Application/DevLogPresentation/Sources/Extension/Error+SocialLogin.swift +++ b/Application/DevLogPresentation/Sources/Extension/Error+SocialLogin.swift @@ -8,7 +8,7 @@ import AuthenticationServices import Foundation -extension Error { +public extension Error { var isSocialLoginCancelled: Bool { switch self { case let authError as ASAuthorizationError: diff --git a/Application/DevLogPresentation/Sources/Home/TodoEditorWindowView.swift b/Application/DevLogPresentation/Sources/Home/TodoEditorWindowView.swift deleted file mode 100644 index fa95eac0..00000000 --- a/Application/DevLogPresentation/Sources/Home/TodoEditorWindowView.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// TodoEditorWindowView.swift -// DevLogPresentation -// -// Created by opfic on 5/31/26. -// - -import SwiftUI -import DevLogCore -import DevLogDomain - -public struct TodoEditorWindowView: View { - @Environment(\.diContainer) private var container: DIContainer - @State private var windowScene: UIWindowScene? - private let value: TodoEditorWindowValue - private let windowEvent: TodoEditorWindowEvent - - public init( - value: TodoEditorWindowValue, - windowEvent: TodoEditorWindowEvent - ) { - self.value = value - self.windowEvent = windowEvent - } - - public var body: some View { - Group { - switch value { - case .create(let windowCategory, _): - TodoEditorView( - viewModel: TodoEditorViewModel( - category: windowCategory.todoCategory, - fetchPreferencesUseCase: container.resolve(FetchTodoCategoryPreferencesUseCase.self), - fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self), - upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self), - trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self), - onUpsertSuccess: upsert - ), - onClose: closeWindow - ) - case .edit(let windowTodo): - TodoEditorView( - viewModel: TodoEditorViewModel( - todo: windowTodo.todo, - fetchPreferencesUseCase: container.resolve(FetchTodoCategoryPreferencesUseCase.self), - fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self), - upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self), - onUpsertSuccess: upsert - ), - onClose: closeWindow - ) - } - } - .background { - WindowSceneReader { windowScene = $0 } - } - } - - private func upsert(_ todo: Todo) { - windowEvent.submit(value: value, todo: todo) - closeWindow() - } - - private func closeWindow() { - guard let windowScene else { return } - UIApplication.shared.requestSceneSessionDestruction( - windowScene.session, - options: nil, - errorHandler: nil - ) - } -} - -private struct WindowSceneReader: UIViewRepresentable { - let onResolve: (UIWindowScene?) -> Void - - func makeUIView(context: Context) -> UIView { - let view = UIView() - resolve(from: view) - return view - } - - func updateUIView(_ view: UIView, context: Context) { - resolve(from: view) - } - - private func resolve(from view: UIView) { - DispatchQueue.main.async { - onResolve(view.window?.windowScene) - } - } -} diff --git a/Application/DevLogPresentation/Sources/Protocol/Store.swift b/Application/DevLogPresentation/Sources/Protocol/Store.swift index b99e183a..da7e27fc 100644 --- a/Application/DevLogPresentation/Sources/Protocol/Store.swift +++ b/Application/DevLogPresentation/Sources/Protocol/Store.swift @@ -20,7 +20,7 @@ public protocol Store: AnyObject { func run(_ effect: SideEffect) } -extension Store { +public extension Store { func send(_ action: Action) { let sideEffects = reduce(with: action) sideEffects.forEach(run) diff --git a/Application/DevLogPresentation/Sources/Routing/HomeRoute.swift b/Application/DevLogPresentation/Sources/Routing/HomeRoute.swift new file mode 100644 index 00000000..b36aa0d8 --- /dev/null +++ b/Application/DevLogPresentation/Sources/Routing/HomeRoute.swift @@ -0,0 +1,14 @@ +// +// HomeRoute.swift +// DevLogPresentation +// +// Created by opfic on 6/2/26. +// + +import Foundation + +public enum HomeRoute: Hashable { + case category(TodoCategoryItem) + case todo(TodoIdItem) + case webPage(WebPageItem) +} diff --git a/Application/DevLogPresentation/Sources/Common/NavigationRouter.swift b/Application/DevLogPresentation/Sources/Routing/NavigationRouter.swift similarity index 64% rename from Application/DevLogPresentation/Sources/Common/NavigationRouter.swift rename to Application/DevLogPresentation/Sources/Routing/NavigationRouter.swift index ce420e9f..a26fb296 100644 --- a/Application/DevLogPresentation/Sources/Common/NavigationRouter.swift +++ b/Application/DevLogPresentation/Sources/Routing/NavigationRouter.swift @@ -9,14 +9,16 @@ import SwiftUI import DevLogDomain @Observable -final class NavigationRouter { - var path: [Route] = [] +public final class NavigationRouter { + public var path: [Route] = [] - var root: Route? { + public init() { } + + public var root: Route? { path.first } - var detailPath: [Route] { + public var detailPath: [Route] { get { Array(path.dropFirst()) } @@ -29,11 +31,11 @@ final class NavigationRouter { } } - func replace(with route: Route) { + public func replace(with route: Route) { path = [route] } - func push(_ route: Route) { + public func push(_ route: Route) { path.append(route) } } diff --git a/Application/DevLogPresentation/Sources/Routing/ProfileRoute.swift b/Application/DevLogPresentation/Sources/Routing/ProfileRoute.swift new file mode 100644 index 00000000..e57b4c95 --- /dev/null +++ b/Application/DevLogPresentation/Sources/Routing/ProfileRoute.swift @@ -0,0 +1,16 @@ +// +// ProfileRoute.swift +// DevLogPresentation +// +// Created by opfic on 6/2/26. +// + +import Foundation + +public enum ProfileRoute: Hashable { + case settings + case activity(String) + case theme + case pushNotification + case account +} diff --git a/Application/DevLogPresentation/Sources/Routing/TodayRoute.swift b/Application/DevLogPresentation/Sources/Routing/TodayRoute.swift new file mode 100644 index 00000000..bbe34f7a --- /dev/null +++ b/Application/DevLogPresentation/Sources/Routing/TodayRoute.swift @@ -0,0 +1,12 @@ +// +// TodayRoute.swift +// DevLogPresentation +// +// Created by opfic on 6/2/26. +// + +import Foundation + +public enum TodayRoute: Hashable { + case todo(TodoIdItem) +} diff --git a/Application/DevLogPresentation/Sources/Structure/Profile/ActivityKindItem.swift b/Application/DevLogPresentation/Sources/Structure/Profile/ActivityKindItem.swift index b5311bf2..347b525d 100644 --- a/Application/DevLogPresentation/Sources/Structure/Profile/ActivityKindItem.swift +++ b/Application/DevLogPresentation/Sources/Structure/Profile/ActivityKindItem.swift @@ -11,12 +11,16 @@ import DevLogCore public struct ActivityKindItem: Identifiable, Hashable { private let activityKind: ActivityKind - init(from activityKind: ActivityKind) { + public init(from activityKind: ActivityKind) { self.activityKind = activityKind } + public static let created = ActivityKindItem(from: .created) + public static let completed = ActivityKindItem(from: .completed) + public static let deleted = ActivityKindItem(from: .deleted) + public static var selectableItems: [ActivityKindItem] {[ - .init(from: .created), .init(from: .completed), .init(from: .deleted) ] + .created, .completed, .deleted ] } public var id: String { activityKind.rawValue } diff --git a/Application/DevLogPresentation/Sources/Structure/PushNotificationItem.swift b/Application/DevLogPresentation/Sources/Structure/PushNotificationItem.swift index 9bfe900f..547277e5 100644 --- a/Application/DevLogPresentation/Sources/Structure/PushNotificationItem.swift +++ b/Application/DevLogPresentation/Sources/Structure/PushNotificationItem.swift @@ -18,7 +18,7 @@ public struct PushNotificationItem: Identifiable, Hashable { public let todoId: String public let todoCategory: TodoCategory - init(from notification: PushNotification) { + public init(from notification: PushNotification) { self.id = notification.id self.title = notification.title self.body = notification.body diff --git a/Application/DevLogPresentation/Sources/Structure/Todo/SystemTodoCategoryItem.swift b/Application/DevLogPresentation/Sources/Structure/Todo/SystemTodoCategoryItem.swift index a83b50f3..f6bfeefc 100644 --- a/Application/DevLogPresentation/Sources/Structure/Todo/SystemTodoCategoryItem.swift +++ b/Application/DevLogPresentation/Sources/Structure/Todo/SystemTodoCategoryItem.swift @@ -11,7 +11,7 @@ import DevLogDomain public struct SystemTodoCategoryItem: Identifiable, Hashable { public let systemTodoCategory: SystemTodoCategory - init(from systemTodoCategory: SystemTodoCategory) { + public init(from systemTodoCategory: SystemTodoCategory) { self.systemTodoCategory = systemTodoCategory } diff --git a/Application/DevLogPresentation/Sources/Structure/Todo/TodoCategoryItem.swift b/Application/DevLogPresentation/Sources/Structure/Todo/TodoCategoryItem.swift index a986255f..5b2d769e 100644 --- a/Application/DevLogPresentation/Sources/Structure/Todo/TodoCategoryItem.swift +++ b/Application/DevLogPresentation/Sources/Structure/Todo/TodoCategoryItem.swift @@ -12,12 +12,12 @@ public struct TodoCategoryItem: Identifiable, Hashable { public var category: TodoCategory public var isVisible: Bool - init(from preference: TodoCategoryPreference) { + public init(from preference: TodoCategoryPreference) { self.category = preference.category self.isVisible = preference.isVisible } - init( + public init( from category: TodoCategory, isVisible: Bool = true ) { diff --git a/Application/DevLogPresentation/Sources/Structure/Todo/TodoIdItem.swift b/Application/DevLogPresentation/Sources/Structure/Todo/TodoIdItem.swift index 1e3d0ea8..cdcf5ac7 100644 --- a/Application/DevLogPresentation/Sources/Structure/Todo/TodoIdItem.swift +++ b/Application/DevLogPresentation/Sources/Structure/Todo/TodoIdItem.swift @@ -10,4 +10,8 @@ import DevLogDomain public struct TodoIdItem: Identifiable, Hashable { public let id: String + + public init(id: String) { + self.id = id + } } diff --git a/Application/DevLogPresentation/Sources/Structure/Todo/TodoReferenceItem.swift b/Application/DevLogPresentation/Sources/Structure/Todo/TodoReferenceItem.swift index a6e396f3..928763fd 100644 --- a/Application/DevLogPresentation/Sources/Structure/Todo/TodoReferenceItem.swift +++ b/Application/DevLogPresentation/Sources/Structure/Todo/TodoReferenceItem.swift @@ -13,7 +13,7 @@ public struct TodoReferenceItem: Equatable { public let title: String public let category: TodoCategoryItem - init(from todoReference: TodoReference) { + public init(from todoReference: TodoReference) { self.id = todoReference.id self.title = todoReference.title self.category = TodoCategoryItem(from: todoReference.category) diff --git a/Application/DevLogPresentation/Sources/Structure/Todo/UserTodoCategoryItem.swift b/Application/DevLogPresentation/Sources/Structure/Todo/UserTodoCategoryItem.swift index 517189cd..e0aa2fcf 100644 --- a/Application/DevLogPresentation/Sources/Structure/Todo/UserTodoCategoryItem.swift +++ b/Application/DevLogPresentation/Sources/Structure/Todo/UserTodoCategoryItem.swift @@ -11,7 +11,7 @@ import DevLogDomain public struct UserTodoCategoryItem: Identifiable, Hashable { public let userTodoCategory: UserTodoCategory - init(from userTodoCategory: UserTodoCategory) { + public init(from userTodoCategory: UserTodoCategory) { self.userTodoCategory = userTodoCategory } diff --git a/Application/DevLogPresentation/Sources/Structure/WebPageItem.swift b/Application/DevLogPresentation/Sources/Structure/WebPageItem.swift index 666d1aaf..6c797401 100644 --- a/Application/DevLogPresentation/Sources/Structure/WebPageItem.swift +++ b/Application/DevLogPresentation/Sources/Structure/WebPageItem.swift @@ -12,7 +12,7 @@ public struct WebPageItem: Identifiable, Hashable { private let metadata: WebPage public var isHidden = false - init(from metadata: WebPage) { + public init(from metadata: WebPage) { self.metadata = metadata } diff --git a/Application/DevLogPresentation/Sources/Setting/AccountViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/AccountViewModel.swift similarity index 84% rename from Application/DevLogPresentation/Sources/Setting/AccountViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/AccountViewModel.swift index 134a561d..07e11c3c 100644 --- a/Application/DevLogPresentation/Sources/Setting/AccountViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/AccountViewModel.swift @@ -9,22 +9,24 @@ import Foundation import DevLogDomain @Observable -final class AccountViewModel: Store { - struct State: Equatable { - var currentProvider: AuthProvider? - var connectedProviders: [AuthProvider] = [] - var disconnectedProviders: [AuthProvider] = [] - var showAlert: Bool = false - var alertTitle: String = "" - var alertType: AlertType? - var alertMessage: String = "" - var showToast: Bool = false - var toastType: ToastType? - var toastMessage: String = "" - var isLoading: Bool = false +public final class AccountViewModel: Store { + public typealias Provider = AuthProvider + + public struct State: Equatable { + public var currentProvider: AuthProvider? + public var connectedProviders: [AuthProvider] = [] + public var disconnectedProviders: [AuthProvider] = [] + public var showAlert: Bool = false + public var alertTitle: String = "" + public var alertType: AlertType? + public var alertMessage: String = "" + public var showToast: Bool = false + public var toastType: ToastType? + public var toastMessage: String = "" + public var isLoading: Bool = false } - enum Action { + public enum Action { case onAppear case linkWithProvider(AuthProvider) case unlinkFromProvider(AuthProvider) @@ -34,31 +36,31 @@ final class AccountViewModel: Store { case updateProviders(currentProvider: AuthProvider?, allProviders: [AuthProvider]) } - enum SideEffect { + public enum SideEffect { case fetch case link(AuthProvider) case unlink(AuthProvider) } - enum AlertType { + public enum AlertType { case linkEmailNotFound case linkEmailMismatch case linkCredentialAlreadyInUse case error } - enum ToastType { + public enum ToastType { case linkSuccess case unlinkSuccess } - private(set) var state: State = .init() + public private(set) var state: State = .init() private let fetchProvidersUseCase: FetchAuthProvidersUseCase private let linkProviderUseCase: LinkAuthProviderUseCase private let unlinkProviderUseCase: UnlinkAuthProviderUseCase private let loadingState = LoadingState() - init( + public init( fetchProvidersUseCase: FetchAuthProvidersUseCase, linkProviderUseCase: LinkAuthProviderUseCase, unlinkProviderUseCase: UnlinkAuthProviderUseCase @@ -68,7 +70,7 @@ final class AccountViewModel: Store { self.unlinkProviderUseCase = unlinkProviderUseCase } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var effects: [SideEffect] = [] @@ -96,12 +98,12 @@ final class AccountViewModel: Store { return effects } - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .fetch: Task { do { - let (currentProvider, allProviders) = try await fetchProvidersUseCase.execute() + let(currentProvider, allProviders) = try await fetchProvidersUseCase.execute() send(.updateProviders(currentProvider: currentProvider, allProviders: allProviders)) } catch { send(.setAlert(isPresented: true, type: .error)) @@ -115,7 +117,7 @@ final class AccountViewModel: Store { try await linkProviderUseCase.execute(provider) send(.setToast(isPresented: true, type: .linkSuccess)) - let (currentProvider, allProviders) = try await fetchProvidersUseCase.execute() + let(currentProvider, allProviders) = try await fetchProvidersUseCase.execute() send(.updateProviders(currentProvider: currentProvider, allProviders: allProviders)) } catch { if error.isSocialLoginCancelled { return } @@ -130,7 +132,7 @@ final class AccountViewModel: Store { try await unlinkProviderUseCase.execute(provider) send(.setToast(isPresented: true, type: .unlinkSuccess)) - let (currentProvider, allProviders) = try await fetchProvidersUseCase.execute() + let(currentProvider, allProviders) = try await fetchProvidersUseCase.execute() send(.updateProviders(currentProvider: currentProvider, allProviders: allProviders)) } catch { send(.setAlert(isPresented: true, type: .error)) diff --git a/Application/DevLogPresentation/Sources/Home/Home/HomeViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/HomeViewModel.swift similarity index 91% rename from Application/DevLogPresentation/Sources/Home/Home/HomeViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/HomeViewModel.swift index 1fa8b7ce..bd8291c6 100644 --- a/Application/DevLogPresentation/Sources/Home/Home/HomeViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/HomeViewModel.swift @@ -11,33 +11,33 @@ import DevLogCore import DevLogDomain @Observable -final class HomeViewModel: Store { - struct State: Equatable { - var preferences: [TodoCategoryItem] = [] - var recentTodos: [RecentTodoItem] = [] - var webPages: [WebPageItem] = [] - var needsWebPageRefresh = false - var isNetworkConnected: Bool = true - var showContentPicker: Bool = false - var showTodoEditor: Bool = false - var showSearchView: Bool = false - var webPageURLInput: String = "https://" - var selectedTodoCategory: TodoCategory? - var reorderTodo: Bool = false - var isPreferencesLoading: Bool = false - var isRecentTodosLoading: Bool = false - var isWebPageLoading: Bool = false - var isAppending: Bool = false - var showAlert: Bool = false - var alertTitle: String = "" - var alertType: AlertType? - var alertMessage: String = "" - var showToast: Bool = false - var toastType: ToastType? - var toastMessage: String = "" +public final class HomeViewModel: Store { + public struct State: Equatable { + public var preferences: [TodoCategoryItem] = [] + public var recentTodos: [RecentTodoItem] = [] + public var webPages: [WebPageItem] = [] + public var needsWebPageRefresh = false + public var isNetworkConnected: Bool = true + public var showContentPicker: Bool = false + public var showTodoEditor: Bool = false + public var showSearchView: Bool = false + public var webPageURLInput: String = "https://" + public var selectedTodoCategory: TodoCategory? + public var reorderTodo: Bool = false + public var isPreferencesLoading: Bool = false + public var isRecentTodosLoading: Bool = false + public var isWebPageLoading: Bool = false + public var isAppending: Bool = false + public var showAlert: Bool = false + public var alertTitle: String = "" + public var alertType: AlertType? + public var alertMessage: String = "" + public var showToast: Bool = false + public var toastType: ToastType? + public var toastMessage: String = "" } - enum Action { + public enum Action { case fetchData case networkStatusChanged(Bool) case setPresentation(Presentation, Bool) @@ -58,7 +58,7 @@ final class HomeViewModel: Store { case updateWebPages([WebPageItem]) } - enum SideEffect { + public enum SideEffect { case addWebPage(String) case deleteWebPage(WebPageItem) case undoDeleteWebPage(String) @@ -69,36 +69,36 @@ final class HomeViewModel: Store { case showModalAfterDelay(ModalType) } - enum AlertType { + public enum AlertType { case webPageInput case invalidURL case error } - enum ToastType { + public enum ToastType { case deleteWebPage } - enum ModalType { + public enum ModalType { case todoEditor case urlInputAlert } - enum Presentation { + public enum Presentation { case reorderTodo case todoEditor case contentPicker case searchView } - enum LoadingTarget: Hashable { + public enum LoadingTarget: Hashable { case preferences case recentTodos case webPage case overlay } - private(set) var state = State() + public private(set) var state = State() private let fetchPreferencesUseCase: FetchTodoCategoryPreferencesUseCase private let updatePreferencesUseCase: UpdateTodoCategoryPreferencesUseCase private let addWebPageUseCase: AddWebPageUseCase @@ -112,7 +112,7 @@ final class HomeViewModel: Store { private var deletedWebPageURLString: String? private var cancellables = Set() - init( + public init( fetchPreferencesUseCase: FetchTodoCategoryPreferencesUseCase, updatePreferencesUseCase: UpdateTodoCategoryPreferencesUseCase, addWebPageUseCase: AddWebPageUseCase, @@ -136,7 +136,7 @@ final class HomeViewModel: Store { setupNetworkObserving() } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var effects: [SideEffect] = [] @@ -157,7 +157,7 @@ final class HomeViewModel: Store { return effects } - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .fetchTodoCategoryPreferences: beginLoading(for: .preferences, mode: .immediate) diff --git a/Application/DevLogPresentation/Sources/Login/LoginViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/LoginViewModel.swift similarity index 83% rename from Application/DevLogPresentation/Sources/Login/LoginViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/LoginViewModel.swift index dffd2361..0a82b9ca 100644 --- a/Application/DevLogPresentation/Sources/Login/LoginViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/LoginViewModel.swift @@ -9,26 +9,26 @@ import Foundation import DevLogDomain @Observable -final class LoginViewModel: Store { - struct State: Equatable { - var isLoading = false - var showAlert: Bool = false - var alertType: AlertType? - var alertTitle: String = "" - var alertMessage: String = "" +public final class LoginViewModel: Store { + public struct State: Equatable { + public var isLoading = false + public var showAlert: Bool = false + public var alertType: AlertType? + public var alertTitle: String = "" + public var alertMessage: String = "" } - enum Action { + public enum Action { case setAlert(Bool, AlertType? = nil) case tapSignInButton(AuthProvider) case setLoading(Bool) } - enum SideEffect { + public enum SideEffect { case signIn(AuthProvider) } - enum AlertType { + public enum AlertType { case emailUnavailable case error } @@ -36,15 +36,15 @@ final class LoginViewModel: Store { private let signInUseCase: SignInUseCase private let loadingState = LoadingState() - private(set) var state = State() + public private(set) var state = State() - init( + public init( signInUseCase: SignInUseCase ) { self.signInUseCase = signInUseCase } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var effects: [SideEffect] = [] @@ -61,7 +61,7 @@ final class LoginViewModel: Store { return effects } - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .signIn(let authProvider): beginLoading(.immediate) diff --git a/Application/DevLogPresentation/Sources/Main/MainViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/MainViewModel.swift similarity index 90% rename from Application/DevLogPresentation/Sources/Main/MainViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/MainViewModel.swift index 4679faaf..eb33fe05 100644 --- a/Application/DevLogPresentation/Sources/Main/MainViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/MainViewModel.swift @@ -12,35 +12,35 @@ import DevLogDomain import DevLogCore @Observable -final class MainViewModel: Store { - struct State: Equatable { - var unreadPushCount = 0 - var showAlert = false - var alertTitle = "" - var alertMessage = "" +public final class MainViewModel: Store { + public struct State: Equatable { + public var unreadPushCount = 0 + public var showAlert = false + public var alertTitle = "" + public var alertMessage = "" } - enum Action { + public enum Action { case onAppear case selectedTabChanged(MainTab) case setUnreadPushCount(Int) case setAlert(Bool) } - enum SideEffect { + public enum SideEffect { case observeUnreadPushCount case trackScreenView(MainTab) case updateBadgeCount(Int) } - private(set) var state = State() + public private(set) var state = State() private let logger = Logger(category: "MainViewModel") private var cancellables = Set() private var isObservingUnreadPushCount = false private let trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase private let unreadPushCountUseCase: ObserveUnreadPushCountUseCase - init( + public init( trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase, unreadPushCountUseCase: ObserveUnreadPushCountUseCase ) { @@ -48,7 +48,7 @@ final class MainViewModel: Store { self.unreadPushCountUseCase = unreadPushCountUseCase } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var sideEffects: [SideEffect] = [] @@ -73,7 +73,7 @@ final class MainViewModel: Store { return sideEffects } - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .observeUnreadPushCount: observeUnreadPushCount() diff --git a/Application/DevLogPresentation/Sources/Profile/ProfileViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/ProfileViewModel.swift similarity index 90% rename from Application/DevLogPresentation/Sources/Profile/ProfileViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/ProfileViewModel.swift index af46da00..4b259c66 100644 --- a/Application/DevLogPresentation/Sources/Profile/ProfileViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/ProfileViewModel.swift @@ -11,29 +11,29 @@ import DevLogCore import DevLogDomain @Observable -final class ProfileViewModel: Store { - struct State: Equatable { - var name: String = "" - var email: String = "" - var isNetworkConnected: Bool = true - var isLoading: Bool = false - var statusMessage: String = "" - var avatarURL: URL? - var earliestQuarterStart: Date? - var selectedQuarterStart: Date? - var showQuarterPicker: Bool = false - var selectedQuarterPickerYear = Calendar.current.component(.year, from: Date()) - var activityQuarter: HeatmapQuarter? - var dayActivitiesByDate: [Date: [HeatmapActivityItem]] = [:] - var selectedActivityKinds: Set = [.created, .completed, .deleted] - var selectedDay: HeatmapDay? - var showDoneButton: Bool = false - var showAlert: Bool = false - var alertTitle: String = "" - var alertMessage: String = "" - } - - enum Action { +public final class ProfileViewModel: Store { + public struct State: Equatable { + public var name: String = "" + public var email: String = "" + public var isNetworkConnected: Bool = true + public var isLoading: Bool = false + public var statusMessage: String = "" + public var avatarURL: URL? + public var earliestQuarterStart: Date? + public var selectedQuarterStart: Date? + public var showQuarterPicker: Bool = false + public var selectedQuarterPickerYear = Calendar.current.component(.year, from: Date()) + public var activityQuarter: HeatmapQuarter? + public var dayActivitiesByDate: [Date: [HeatmapActivityItem]] = [:] + public var selectedActivityKinds: Set = [.created, .completed, .deleted] + public var selectedDay: HeatmapDay? + public var showDoneButton: Bool = false + public var showAlert: Bool = false + public var alertTitle: String = "" + public var alertMessage: String = "" + } + + public enum Action { case fetchData, refresh case networkStatusChanged(Bool) case setLoading(Bool) @@ -58,14 +58,18 @@ final class ProfileViewModel: Store { case updateStatusTextFieldFocus(Bool) } - enum SideEffect { + public enum SideEffect { case fetchUserData case fetchActivityQuarter(Date) case updateStatusMessage(String) case updateHeatmapActivityKinds(Set) } - private(set) var state = State() + public private(set) var state = State() + public var selectedActivityKindItems: Set { + Set(state.selectedActivityKinds.map(ActivityKindItem.init(from:))) + } + private let fetchUserDataUseCase: FetchUserDataUseCase private let fetchTodosUseCase: FetchTodosUseCase private let upsertStatusMessageUseCase: UpsertStatusMessageUseCase @@ -76,7 +80,7 @@ final class ProfileViewModel: Store { private let loadingState = LoadingState() private var cancellables = Set() - init( + public init( fetchUserDataUseCase: FetchUserDataUseCase, fetchTodosUseCase: FetchTodosUseCase, upsertStatusMessageUseCase: UpsertStatusMessageUseCase, @@ -93,8 +97,33 @@ final class ProfileViewModel: Store { setupNetworkObserving() } + public func isSelected(_ activityKindItem: ActivityKindItem) -> Bool { + guard let activityKind = ActivityKind(rawValue: activityKindItem.rawValue) else { + return false + } + + return state.selectedActivityKinds.contains(activityKind) + } + + public func isOnlySelected(_ activityKindItem: ActivityKindItem) -> Bool { + guard let activityKind = ActivityKind(rawValue: activityKindItem.rawValue) else { + return false + } + + return state.selectedActivityKinds.count == 1 + && state.selectedActivityKinds.contains(activityKind) + } + + public func toggle(_ activityKindItem: ActivityKindItem) { + guard let activityKind = ActivityKind(rawValue: activityKindItem.rawValue) else { + return + } + + send(.toggleActivityKind(activityKind)) + } + // swiftlint:disable cyclomatic_complexity - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var effects: [SideEffect] = [] switch action { @@ -191,7 +220,7 @@ final class ProfileViewModel: Store { } // swiftlint:enable cyclomatic_complexity - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .fetchUserData: Task { @@ -263,7 +292,7 @@ private struct HeatmapActivityEntry { var activityKinds: Set } -extension ProfileViewModel { +public extension ProfileViewModel { private func setupNetworkObserving() { networkConnectivityUseCase.observe() .receive(on: DispatchQueue.main) diff --git a/Application/DevLogPresentation/Sources/PushNotification/PushNotificationListViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/PushNotificationListViewModel.swift similarity index 92% rename from Application/DevLogPresentation/Sources/PushNotification/PushNotificationListViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/PushNotificationListViewModel.swift index a46c630b..0f30350a 100644 --- a/Application/DevLogPresentation/Sources/PushNotification/PushNotificationListViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/PushNotificationListViewModel.swift @@ -11,23 +11,25 @@ import DevLogCore import DevLogDomain @Observable -final class PushNotificationListViewModel: Store { - struct State: Equatable { - var notifications: [PushNotificationItem] = [] - var showAlert: Bool = false - var showToast: Bool = false - var alertTitle: String = "" - var alertMessage: String = "" - var toastMessage: String = "" - var isLoading: Bool = false - var hasMore: Bool = false - var nextCursor: PushNotificationCursor? - var query: PushNotificationQuery - var selectedNotificationId: String? - var selectedTodoId: TodoIdItem? +public final class PushNotificationListViewModel: Store { + public typealias TimeFilter = PushNotificationQuery.TimeFilter + + public struct State: Equatable { + public var notifications: [PushNotificationItem] = [] + public var showAlert: Bool = false + public var showToast: Bool = false + public var alertTitle: String = "" + public var alertMessage: String = "" + public var toastMessage: String = "" + public var isLoading: Bool = false + public var hasMore: Bool = false + public var nextCursor: PushNotificationCursor? + public var query: PushNotificationQuery + public var selectedNotificationId: String? + public var selectedTodoId: TodoIdItem? } - enum Action { + public enum Action { case fetchNotifications case loadNextPage case deleteNotification(PushNotificationItem) @@ -48,14 +50,14 @@ final class PushNotificationListViewModel: Store { case selectNotification(String?) } - enum SideEffect { + public enum SideEffect { case fetchNotifications(PushNotificationQuery, cursor: PushNotificationCursor?) case delete(PushNotificationItem) case undoDelete(String) case toggleRead(String) } - private(set) var state: State + public private(set) var state: State private let fetchUseCase: FetchPushNotificationsUseCase private let deleteUseCase: DeletePushNotificationUseCase private let undoDeleteUseCase: UndoDeletePushNotificationUseCase @@ -66,7 +68,7 @@ final class PushNotificationListViewModel: Store { private var undoNotificationId: String? private var cancellable: AnyCancellable? - init( + public init( fetchUseCase: FetchPushNotificationsUseCase, deleteUseCase: DeletePushNotificationUseCase, undoDeleteUseCase: UndoDeletePushNotificationUseCase, @@ -85,7 +87,7 @@ final class PushNotificationListViewModel: Store { ) } - var appliedFilterCount: Int { + public var appliedFilterCount: Int { var count = 0 if state.query.sortOrder != .latest { count += 1 } if state.query.timeFilter != .none { count += 1 } @@ -93,7 +95,7 @@ final class PushNotificationListViewModel: Store { return count } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var effects: [SideEffect] = [] @@ -114,7 +116,7 @@ final class PushNotificationListViewModel: Store { return effects } - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .fetchNotifications(let query, let cursor): if cursor == nil { @@ -386,7 +388,7 @@ private extension PushNotificationListViewModel { } } -extension PushNotificationQuery.SortOrder { +public extension PushNotificationQuery.SortOrder { var title: String { switch self { case .latest: return String(localized: "push_sort_latest") @@ -395,7 +397,7 @@ extension PushNotificationQuery.SortOrder { } } -extension PushNotificationQuery.TimeFilter { +public extension PushNotificationQuery.TimeFilter { var title: String { switch self { case .none: diff --git a/Application/DevLogPresentation/Sources/Setting/PushNotificationSettingsViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/PushNotificationSettingsViewModel.swift similarity index 84% rename from Application/DevLogPresentation/Sources/Setting/PushNotificationSettingsViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/PushNotificationSettingsViewModel.swift index c73731e3..6b3a44fc 100644 --- a/Application/DevLogPresentation/Sources/Setting/PushNotificationSettingsViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/PushNotificationSettingsViewModel.swift @@ -9,27 +9,27 @@ import Foundation import DevLogDomain @Observable -final class PushNotificationSettingsViewModel: Store { - struct State: Equatable { - var pushNotificationEnable: Bool = false - var viewPushNotificationTime: Date = .init() - var sheetPushNotificationTime: Date = .init() - var showTimePicker: Bool = false - var isLoading: Bool = false - var sheetHeight: CGFloat = .pi - var showSheet: Bool = false - var showAlert: Bool = false - var alertTitle: String = "" - var alertMessage: String = "" - var pushNotificationHour: Int { +public final class PushNotificationSettingsViewModel: Store { + public struct State: Equatable { + public var pushNotificationEnable: Bool = false + public var viewPushNotificationTime: Date = .init() + public var sheetPushNotificationTime: Date = .init() + public var showTimePicker: Bool = false + public var isLoading: Bool = false + public var sheetHeight: CGFloat = .pi + public var showSheet: Bool = false + public var showAlert: Bool = false + public var alertTitle: String = "" + public var alertMessage: String = "" + public var pushNotificationHour: Int { Calendar.current.component(.hour, from: viewPushNotificationTime) } - var pushNotificationMinute: Int { + public var pushNotificationMinute: Int { Calendar.current.component(.minute, from: viewPushNotificationTime) } } - enum Action { + public enum Action { case fetchSettings case setAlert(Bool) case setLoading(Bool) @@ -42,18 +42,18 @@ final class PushNotificationSettingsViewModel: Store { case rollbackUpdate } - enum SideEffect { + public enum SideEffect { case fetchPushNotificationSettings case updatePushNotificationSettings } - private(set) var state: State = .init() + public private(set) var state: State = .init() private let calendar = Calendar.current private let fetchPushSettingsUseCase: FetchPushSettingsUseCase private let updatePushSettingsUseCase: UpdatePushSettingsUseCase private let loadingState = LoadingState() - init( + public init( fetchPushSettingsUseCase: FetchPushSettingsUseCase, updatePushSettingsUseCase: UpdatePushSettingsUseCase ) { @@ -61,7 +61,7 @@ final class PushNotificationSettingsViewModel: Store { self.updatePushSettingsUseCase = updatePushSettingsUseCase } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var effects: [SideEffect] = [] @@ -105,7 +105,7 @@ final class PushNotificationSettingsViewModel: Store { return effects } - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .fetchPushNotificationSettings: beginLoading(.delayed) @@ -146,7 +146,7 @@ final class PushNotificationSettingsViewModel: Store { } } -extension PushNotificationSettingsViewModel { +public extension PushNotificationSettingsViewModel { func setAlert( _ state: inout State, isPresented: Bool diff --git a/Application/DevLogPresentation/Sources/Root/RootViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/RootViewModel.swift similarity index 87% rename from Application/DevLogPresentation/Sources/Root/RootViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/RootViewModel.swift index 3c63f0da..4d00e43d 100644 --- a/Application/DevLogPresentation/Sources/Root/RootViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/RootViewModel.swift @@ -12,17 +12,17 @@ import DevLogCore import DevLogDomain @Observable -final class RootViewModel: Store { - struct State: Equatable { - var showAlert: Bool = false - var alertTitle: String = "" - var alertMessage: String = "" - var isNetworkConnected: Bool = true - var signIn: Bool? - var theme: SystemTheme = .automatic +public final class RootViewModel: Store { + public struct State: Equatable { + public var showAlert: Bool = false + public var alertTitle: String = "" + public var alertMessage: String = "" + public var isNetworkConnected: Bool = true + public var signIn: Bool? + public var theme: SystemTheme = .automatic } - enum Action { + public enum Action { case onAppear case setAlert(Bool) case networkStatusChanged(Bool) @@ -30,19 +30,19 @@ final class RootViewModel: Store { case didLogined(Bool) } - enum SideEffect { + public enum SideEffect { case clearApplicationBadgeCount case trackLoginScreen } - private(set) var state: State + public private(set) var state: State private var cancellables = Set() private let sessionUseCase: ObserveAuthSessionUseCase private let networkConnectivityUseCase: ObserveNetworkConnectivityUseCase private let systemThemeUseCase: ObserveSystemThemeUseCase private let trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase - init( + public init( sessionUseCase: ObserveAuthSessionUseCase, networkConnectivityUseCase: ObserveNetworkConnectivityUseCase, systemThemeUseCase: ObserveSystemThemeUseCase, @@ -59,7 +59,7 @@ final class RootViewModel: Store { setupThemeObserving() } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var effects: [SideEffect] = [] @@ -87,7 +87,7 @@ final class RootViewModel: Store { return effects } - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .clearApplicationBadgeCount: UNUserNotificationCenter.current().setBadgeCount(0) { _ in } diff --git a/Application/DevLogPresentation/Sources/Search/SearchViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/SearchViewModel.swift similarity index 90% rename from Application/DevLogPresentation/Sources/Search/SearchViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/SearchViewModel.swift index 31532af3..744bf9e0 100644 --- a/Application/DevLogPresentation/Sources/Search/SearchViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/SearchViewModel.swift @@ -11,22 +11,22 @@ import DevLogCore import DevLogDomain @Observable -final class SearchViewModel: Store { - struct State: Equatable { - var isLoading: Bool = false - var isSearching: Bool = false - var searchQuery: String = "" - var webPages: [WebPageItem] = [] - var todos: [TodoListItem] = [] - var recentQueries: OrderedSet = [] - var showAlert: Bool = false - var alertTitle: String = "" - var alertMessage: String = "" - var showAllTodos: Bool = false - var showAllWebPages: Bool = false +public final class SearchViewModel: Store { + public struct State: Equatable { + public var isLoading: Bool = false + public var isSearching: Bool = false + public var searchQuery: String = "" + public var webPages: [WebPageItem] = [] + public var todos: [TodoListItem] = [] + public var recentQueries: OrderedSet = [] + public var showAlert: Bool = false + public var alertTitle: String = "" + public var alertMessage: String = "" + public var showAllTodos: Bool = false + public var showAllWebPages: Bool = false } - enum Action { + public enum Action { case fetchWebPage([WebPageItem]) case fetchTodos([TodoListItem]) case addRecentQuery(String) @@ -41,7 +41,7 @@ final class SearchViewModel: Store { case setShowAllWebPages(Bool) } - enum SideEffect { + public enum SideEffect { case cancelSearch case debounceFetch(String) case fetch(String) @@ -52,19 +52,19 @@ final class SearchViewModel: Store { case request } - private(set) var state: State = .init() + public private(set) var state: State = .init() private let fetchWebPagesUseCase: FetchWebPagesUseCase private let fetchTodosUseCase: FetchTodosUseCase private let fetchRecentSearchQueriesUseCase: FetchRecentSearchQueriesUseCase private let updateRecentSearchQueriesUseCase: UpdateRecentSearchQueriesUseCase private let loadingState = LoadingState() - let contentsLimit: Int = 5 + public let contentsLimit: Int = 5 private let maxRecentQueries = 20 private let searchDebounceDelay: Double = 0.4 private var searchTasks: [SearchTaskKind: Task] = [:] - init( + public init( fetchWebPagesUseCase: FetchWebPagesUseCase, fetchTodosUseCase: FetchTodosUseCase, fetchRecentSearchQueriesUseCase: FetchRecentSearchQueriesUseCase, @@ -77,7 +77,7 @@ final class SearchViewModel: Store { self.state.recentQueries = OrderedSet(fetchRecentSearchQueriesUseCase.execute()) } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var effects: [SideEffect] = [] @@ -142,7 +142,7 @@ final class SearchViewModel: Store { return effects } - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .cancelSearch: cancelSearch() diff --git a/Application/DevLogPresentation/Sources/Setting/SettingViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/SettingViewModel.swift similarity index 87% rename from Application/DevLogPresentation/Sources/Setting/SettingViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/SettingViewModel.swift index aaddbab2..5405c5c0 100644 --- a/Application/DevLogPresentation/Sources/Setting/SettingViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/SettingViewModel.swift @@ -11,19 +11,21 @@ import DevLogCore import DevLogDomain @Observable -final class SettingViewModel: Store { - struct State: Equatable { - var theme: SystemTheme = .automatic - var dirSize: Int64 = 0 - var isNetworkConnected = true - var isLoading = false - var showAlert: Bool = false - var alertTitle: String = "" - var alertType: AlertType? - var alertMessage: String = "" +public final class SettingViewModel: Store { + public typealias Theme = SystemTheme + + public struct State: Equatable { + public var theme: SystemTheme = .automatic + public var dirSize: Int64 = 0 + public var isNetworkConnected = true + public var isLoading = false + public var showAlert: Bool = false + public var alertTitle: String = "" + public var alertType: AlertType? + public var alertMessage: String = "" } - enum Action { + public enum Action { case networkStatusChanged(Bool) case setAlert(isPresented: Bool, type: AlertType? = nil) case setDirSize(Int64) @@ -36,18 +38,18 @@ final class SettingViewModel: Store { case confirmRemoveCache } - enum SideEffect { + public enum SideEffect { case clearWebPageImageDirectory case deleteAuth case fetchWebPageImageDirSize case signOut } - enum AlertType { + public enum AlertType { case signOut, deleteAuth, error, removeCache } - private(set) var state = State() + public private(set) var state = State() private let deleteAuthuseCase: DeleteAuthUseCase private let signOutUseCase: SignOutUseCase private let networkConnectivityUseCase: ObserveNetworkConnectivityUseCase @@ -58,11 +60,11 @@ final class SettingViewModel: Store { private let loadingState = LoadingState() private var cancellables = Set() - let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String - let appstoreUrl = Bundle.main.object(forInfoDictionaryKey: "APPSTORE_URL") as? String - let policyURL = Bundle.main.object(forInfoDictionaryKey: "PRIVACY_POLICY_URL") as? String + public let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + public let appstoreUrl = Bundle.main.object(forInfoDictionaryKey: "APPSTORE_URL") as? String + public let policyURL = Bundle.main.object(forInfoDictionaryKey: "PRIVACY_POLICY_URL") as? String - init( + public init( deleteAuthUseCase: DeleteAuthUseCase, signOutUseCase: SignOutUseCase, networkConnectivityUseCase: ObserveNetworkConnectivityUseCase, @@ -82,7 +84,7 @@ final class SettingViewModel: Store { setupThemeMonitoring() } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var effects: [SideEffect] = [] @@ -115,7 +117,7 @@ final class SettingViewModel: Store { return effects } - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .clearWebPageImageDirectory: Task { diff --git a/Application/DevLogPresentation/Sources/Today/TodayViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/TodayViewModel.swift similarity index 90% rename from Application/DevLogPresentation/Sources/Today/TodayViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/TodayViewModel.swift index ccaef572..234e6bc7 100644 --- a/Application/DevLogPresentation/Sources/Today/TodayViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/TodayViewModel.swift @@ -10,9 +10,12 @@ import DevLogCore import DevLogDomain @Observable -final class TodayViewModel: Store { +public final class TodayViewModel: Store { + public typealias DueDateVisibility = TodayDisplayOptions.DueDateVisibility + public typealias FocusVisibility = TodayDisplayOptions.FocusVisibility + // TodayView 상단에서 사용자가 선택하는 요약 탭 범위. - enum SectionScope: Hashable, CaseIterable { + public enum SectionScope: Hashable, CaseIterable { case all case focused case overdue @@ -20,7 +23,7 @@ final class TodayViewModel: Store { } // 요약 탭 아래 실제 리스트에 렌더링되는 섹션 분류. - enum SectionCategory: Hashable { + public enum SectionCategory: Hashable { case later case unscheduled case focused @@ -28,32 +31,32 @@ final class TodayViewModel: Store { case dueSoon } - struct SectionContent: Identifiable, Equatable { - var id: SectionCategory { category } - let category: SectionCategory - let title: String - let items: [TodayTodoItem] + public struct SectionContent: Identifiable, Equatable { + public var id: SectionCategory { category } + public let category: SectionCategory + public let title: String + public let items: [TodayTodoItem] } - struct SectionCollection { - var focused: [TodayTodoItem] = [] - var overdue: [TodayTodoItem] = [] - var dueSoon: [TodayTodoItem] = [] - var later: [TodayTodoItem] = [] - var unscheduled: [TodayTodoItem] = [] + public struct SectionCollection { + public var focused: [TodayTodoItem] = [] + public var overdue: [TodayTodoItem] = [] + public var dueSoon: [TodayTodoItem] = [] + public var later: [TodayTodoItem] = [] + public var unscheduled: [TodayTodoItem] = [] } - struct State: Equatable { - var todos: [TodayTodoItem] = [] - var isLoading: Bool = false - var showAlert: Bool = false - var alertTitle: String = "" - var alertMessage: String = "" - var selectedSectionScope: SectionScope = .all - var displayOptions: TodayDisplayOptions = .default + public struct State: Equatable { + public var todos: [TodayTodoItem] = [] + public var isLoading: Bool = false + public var showAlert: Bool = false + public var alertTitle: String = "" + public var alertMessage: String = "" + public var selectedSectionScope: SectionScope = .all + public var displayOptions: TodayDisplayOptions = .default } - enum Action { + public enum Action { case refresh case setAlert(Bool) case setSectionScope(SectionScope) @@ -69,13 +72,13 @@ final class TodayViewModel: Store { case removeTodo(String) } - enum SideEffect { + public enum SideEffect { case fetchTodos case completeTodo(TodayTodoItem) case togglePinned(TodayTodoItem) } - private(set) var state = State() + public private(set) var state = State() private let calendar = Calendar.current private let pageSize = 20 private let upcomingWindowDays = 7 @@ -86,7 +89,7 @@ final class TodayViewModel: Store { private let trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase private let loadingState = LoadingState() - init( + public init( fetchTodosUseCase: FetchTodosUseCase, fetchTodoByIdUseCase: FetchTodoByIdUseCase, upsertTodoUseCase: UpsertTodoUseCase, @@ -102,7 +105,7 @@ final class TodayViewModel: Store { self.state.displayOptions = fetchTodayDisplayOptionsUseCase.execute() } - var sections: [SectionContent] { + public var sections: [SectionContent] { let items = groupedSectionItems(from: displayedTodos) switch state.selectedSectionScope { @@ -160,7 +163,7 @@ final class TodayViewModel: Store { } } - func summaryValue(for scope: SectionScope) -> Int { + public func summaryValue(for scope: SectionScope) -> Int { switch scope { case .all: return displayedTodos.count @@ -173,7 +176,7 @@ final class TodayViewModel: Store { } } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var effects: [SideEffect] = [] @@ -191,7 +194,7 @@ final class TodayViewModel: Store { return effects } - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .fetchTodos: beginLoading(.delayed) diff --git a/Application/DevLogPresentation/Sources/Home/TodoDetailViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/TodoDetailViewModel.swift similarity index 82% rename from Application/DevLogPresentation/Sources/Home/TodoDetailViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/TodoDetailViewModel.swift index dc068268..b8523ca7 100644 --- a/Application/DevLogPresentation/Sources/Home/TodoDetailViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/TodoDetailViewModel.swift @@ -9,20 +9,20 @@ import Foundation import DevLogDomain @Observable -final class TodoDetailViewModel: Store { - struct State: Equatable { - var todo: Todo? - var selectedTodoId: TodoIdItem? - var referenceItems: [Int: TodoReferenceItem] = [:] - var isLoading: Bool = false - var showAlert: Bool = false - var showEditor: Bool = false - var showInfo: Bool = false - var alertTitle: String = "" - var alertMessage: String = "" +public final class TodoDetailViewModel: Store { + public struct State: Equatable { + public var todo: Todo? + public var selectedTodoId: TodoIdItem? + public var referenceItems: [Int: TodoReferenceItem] = [:] + public var isLoading: Bool = false + public var showAlert: Bool = false + public var showEditor: Bool = false + public var showInfo: Bool = false + public var alertTitle: String = "" + public var alertMessage: String = "" } - enum Action { + public enum Action { case onAppear case setAlert(Bool) case setShowEditor(Bool) @@ -33,19 +33,19 @@ final class TodoDetailViewModel: Store { case setLoading(Bool) } - enum SideEffect { + public enum SideEffect { case fetchTodo case resolveMarkdown(String) } - private(set) var state: State = .init() - let todoId: String - let showEditButton: Bool + public private(set) var state: State = .init() + public let todoId: String + public let showEditButton: Bool private let fetchTodoUseCase: FetchTodoByIdUseCase private let fetchReferenceItemsUseCase: FetchReferenceItemsUseCase private let loadingState = LoadingState() - init( + public init( fetchTodoUseCase: FetchTodoByIdUseCase, fetchReferenceItemsUseCase: FetchReferenceItemsUseCase, todoId: String, @@ -57,7 +57,7 @@ final class TodoDetailViewModel: Store { self.showEditButton = showEditButton } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var effects: [SideEffect] = [] @@ -86,7 +86,7 @@ final class TodoDetailViewModel: Store { return effects } - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .fetchTodo: beginLoading(.delayed) diff --git a/Application/DevLogPresentation/Sources/Home/TodoEditorViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/TodoEditorViewModel.swift similarity index 88% rename from Application/DevLogPresentation/Sources/Home/TodoEditorViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/TodoEditorViewModel.swift index 82fac208..1c7682b2 100644 --- a/Application/DevLogPresentation/Sources/Home/TodoEditorViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/TodoEditorViewModel.swift @@ -10,7 +10,7 @@ import OrderedCollections import DevLogDomain @Observable -final class TodoEditorViewModel: Store { +public final class TodoEditorViewModel: Store { private struct Draft: Equatable { let isCompleted: Bool let completedAt: Date? @@ -44,36 +44,36 @@ final class TodoEditorViewModel: Store { } } - struct State: Equatable { - var isCompleted: Bool = false - var completedAt: Date? - var isPinned: Bool = false - var selectedTodoId: TodoIdItem? - var title: String = "" - var content: String = "" - var referenceItems: [Int: TodoReferenceItem] = [:] - var dueDate: Date? - var showInfo: Bool = false - var showAlert: Bool = false - var alertTitle: String = "" - var alertMessage: String = "" - var isLoading: Bool = false - var tags: OrderedSet = [] - var tagText: String = "" - var focusOnEditor: Bool = false - var tabViewTag: Tag = .editor - var categories: [TodoCategoryItem] = [] - var category = TodoCategoryItem(from: .system(.etc)) - var isValidToSave: Bool { + public struct State: Equatable { + public var isCompleted: Bool = false + public var completedAt: Date? + public var isPinned: Bool = false + public var selectedTodoId: TodoIdItem? + public var title: String = "" + public var content: String = "" + public var referenceItems: [Int: TodoReferenceItem] = [:] + public var dueDate: Date? + public var showInfo: Bool = false + public var showAlert: Bool = false + public var alertTitle: String = "" + public var alertMessage: String = "" + public var isLoading: Bool = false + public var tags: OrderedSet = [] + public var tagText: String = "" + public var focusOnEditor: Bool = false + public var tabViewTag: Tag = .editor + public var categories: [TodoCategoryItem] = [] + public var category = TodoCategoryItem(from: .system(.etc)) + public var isValidToSave: Bool { !title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } } - enum Tag { + public enum Tag { case editor, preview } - enum Action { + public enum Action { case onAppear case addTag(String) case removeTag(String) @@ -94,13 +94,13 @@ final class TodoEditorViewModel: Store { case upsertTodo(Todo) } - enum SideEffect { + public enum SideEffect { case fetchCategories case resolveMarkdown(String) case upsertTodo(Todo) } - private(set) var state = State() + public private(set) var state = State() private let calendar = Calendar.current private let fetchPreferencesUseCase: FetchTodoCategoryPreferencesUseCase private let fetchReferenceItemsUseCase: FetchReferenceItemsUseCase @@ -115,7 +115,7 @@ final class TodoEditorViewModel: Store { private let deletedAt: Date? private let originalDraft: Draft? - var navigationTitle: String { + public var navigationTitle: String { if originalDraft == nil { return String.localizedStringWithFormat( String(localized: "todo_editor_new_format"), @@ -126,17 +126,17 @@ final class TodoEditorViewModel: Store { return String(localized: "todo_edit") } - var hasChanges: Bool { + public var hasChanges: Bool { guard let originalDraft else { return true } return originalDraft != Draft(state: state) } - var isReadyToSubmit: Bool { + public var isReadyToSubmit: Bool { state.isValidToSave && hasChanges } // 새로운 Todo 생성용 생성자 - init( + public init( category: TodoCategory, fetchPreferencesUseCase: FetchTodoCategoryPreferencesUseCase, fetchReferenceItemsUseCase: FetchReferenceItemsUseCase, @@ -161,7 +161,7 @@ final class TodoEditorViewModel: Store { } // 기존 Todo 편집용 생성자 - init( + public init( todo: Todo, fetchPreferencesUseCase: FetchTodoCategoryPreferencesUseCase, fetchReferenceItemsUseCase: FetchReferenceItemsUseCase, @@ -191,7 +191,7 @@ final class TodoEditorViewModel: Store { state.category = TodoCategoryItem(from: todo.category) } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var effects: [SideEffect] = [] @@ -251,7 +251,7 @@ final class TodoEditorViewModel: Store { return effects } - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .fetchCategories: Task { @@ -294,7 +294,7 @@ final class TodoEditorViewModel: Store { } } -extension TodoEditorViewModel { +public extension TodoEditorViewModel { private func handleStringAction( _ action: Action, stringValue: String, diff --git a/Application/DevLogPresentation/Sources/Home/TodoListViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/TodoListViewModel.swift similarity index 92% rename from Application/DevLogPresentation/Sources/Home/TodoListViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/TodoListViewModel.swift index 9467e77b..fd7778c8 100644 --- a/Application/DevLogPresentation/Sources/Home/TodoListViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/TodoListViewModel.swift @@ -10,25 +10,29 @@ import DevLogCore import DevLogDomain @Observable -final class TodoListViewModel: Store { - struct State: Equatable { - var todos: [TodoListItem] = [] - var searchText: String = "" - var searchResults: [TodoListItem] = [] - var showEditor: Bool = false - var showAlert: Bool = false - var alertTitle: String = "" - var alertMessage: String = "" - var isSearching: Bool = false - var showAllSearchResults: Bool = false - var query: TodoQuery - var isLoading: Bool = false - var showToast: Bool = false - var toastMessage: String = "" - var hasMore: Bool = false +public final class TodoListViewModel: Store { + public typealias SortTarget = TodoQuery.SortTarget + public typealias SortOrder = TodoQuery.SortOrder + public typealias CompletionFilter = TodoQuery.CompletionFilter + + public struct State: Equatable { + public var todos: [TodoListItem] = [] + public var searchText: String = "" + public var searchResults: [TodoListItem] = [] + public var showEditor: Bool = false + public var showAlert: Bool = false + public var alertTitle: String = "" + public var alertMessage: String = "" + public var isSearching: Bool = false + public var showAllSearchResults: Bool = false + public var query: TodoQuery + public var isLoading: Bool = false + public var showToast: Bool = false + public var toastMessage: String = "" + public var hasMore: Bool = false } - enum Action { + public enum Action { // User case refresh case setAlert(Bool) @@ -63,7 +67,7 @@ final class TodoListViewModel: Store { case setHasMore(Bool) } - enum SideEffect { + public enum SideEffect { case cancelSearch case debounceSearch(String) case fetch @@ -80,8 +84,8 @@ final class TodoListViewModel: Store { case request } - let category: TodoCategory - private(set) var state: State + public let category: TodoCategory + public private(set) var state: State private let fetchTodosUseCase: FetchTodosUseCase private let fetchTodoByIdUseCase: FetchTodoByIdUseCase private let upsertTodoUseCase: UpsertTodoUseCase @@ -94,7 +98,7 @@ final class TodoListViewModel: Store { private var searchTasks: [SearchTaskKind: Task] = [:] private let searchDebounceDelay: Double = 0.4 - init( + public init( fetchTodosUseCase: FetchTodosUseCase, fetchTodoByIdUseCase: FetchTodoByIdUseCase, upsertTodoUseCase: UpsertTodoUseCase, @@ -115,9 +119,9 @@ final class TodoListViewModel: Store { ) } - let searchResultsLimit = 5 + public let searchResultsLimit = 5 - var appliedFilterCount: Int { + public var appliedFilterCount: Int { var count = 0 if state.query.sortTarget != .createdAt { count += 1 } if state.query.sortOrder != .latest { count += 1 } @@ -126,7 +130,7 @@ final class TodoListViewModel: Store { return count } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state var effects: [SideEffect] = [] @@ -149,7 +153,7 @@ final class TodoListViewModel: Store { } // swiftlint:disable function_body_length - func run(_ effect: SideEffect) { + public func run(_ effect: SideEffect) { switch effect { case .cancelSearch: cancelSearch() @@ -467,7 +471,7 @@ private extension TodoListViewModel { } } -extension TodoQuery.SortTarget { +public extension TodoQuery.SortTarget { var title: String { switch self { case .createdAt: @@ -484,7 +488,7 @@ extension TodoQuery.SortTarget { } } -extension TodoQuery.SortOrder { +public extension TodoQuery.SortOrder { var title: String { switch self { case .latest: @@ -495,7 +499,7 @@ extension TodoQuery.SortOrder { } } -extension TodoQuery.CompletionFilter { +public extension TodoQuery.CompletionFilter { var title: String { switch self { case .all: diff --git a/Application/DevLogPresentation/Sources/Home/TodoManageViewModel.swift b/Application/DevLogPresentation/Sources/ViewModel/TodoManageViewModel.swift similarity index 90% rename from Application/DevLogPresentation/Sources/Home/TodoManageViewModel.swift rename to Application/DevLogPresentation/Sources/ViewModel/TodoManageViewModel.swift index 72c30bc5..5c5ea986 100644 --- a/Application/DevLogPresentation/Sources/Home/TodoManageViewModel.swift +++ b/Application/DevLogPresentation/Sources/ViewModel/TodoManageViewModel.swift @@ -9,15 +9,15 @@ import SwiftUI import DevLogDomain @Observable -final class TodoManageViewModel: Store { - struct State: Equatable { - var preferences: [TodoCategoryItem] - var category: TodoCategoryItem? - var showSheet: Bool = false - var showAlert: Bool = false +public final class TodoManageViewModel: Store { + public struct State: Equatable { + public var preferences: [TodoCategoryItem] + public var category: TodoCategoryItem? + public var showSheet: Bool = false + public var showAlert: Bool = false } - enum Action { + public enum Action { case tapAddUserCategory case moveItem(from: IndexSet, target: Int) case tapItem(TodoCategoryItem) @@ -32,11 +32,11 @@ final class TodoManageViewModel: Store { case saveUserCategory } - enum SideEffect { } + public enum SideEffect { } - private(set) var state: State + public private(set) var state: State - var isEditing: Bool { + public var isEditing: Bool { guard let categoryItem = state.category else { return false } @@ -44,19 +44,19 @@ final class TodoManageViewModel: Store { return state.preferences.contains { $0.id == categoryItem.id } } - var navigationTitle: String { + public var navigationTitle: String { isEditing ? String(localized: "todo_manage_edit_category_title") : String(localized: "todo_manage_add_category_title") } - var submitTitle: String { + public var submitTitle: String { isEditing ? String(localized: "todo_manage_save") : String(localized: "todo_add") } - var placeholder: String { + public var placeholder: String { guard let item = state.category, case .user(let category) = item.category @@ -67,7 +67,7 @@ final class TodoManageViewModel: Store { return category.name } - var categoryNameCountText: String { + public var categoryNameCountText: String { guard let item = state.category, case .user(let category) = item.category @@ -78,7 +78,7 @@ final class TodoManageViewModel: Store { return "\(category.name.count)/20" } - var canSubmitUserCategory: Bool { + public var canSubmitUserCategory: Bool { guard let item = state.category, case .user(let category) = item.category @@ -119,11 +119,11 @@ final class TodoManageViewModel: Store { return true } - init(_ preferences: [TodoCategoryItem]) { + public init(_ preferences: [TodoCategoryItem]) { self.state = State(preferences: preferences) } - func reduce(with action: Action) -> [SideEffect] { + public func reduce(with action: Action) -> [SideEffect] { var state = self.state switch action { diff --git a/Application/DevLogPresentation/Sources/Home/TodoEditorWindowEvent.swift b/Application/DevLogPresentation/Sources/Window/TodoEditorWindowEvent.swift similarity index 83% rename from Application/DevLogPresentation/Sources/Home/TodoEditorWindowEvent.swift rename to Application/DevLogPresentation/Sources/Window/TodoEditorWindowEvent.swift index c76f9750..966f997c 100644 --- a/Application/DevLogPresentation/Sources/Home/TodoEditorWindowEvent.swift +++ b/Application/DevLogPresentation/Sources/Window/TodoEditorWindowEvent.swift @@ -11,13 +11,13 @@ import DevLogDomain public final class TodoEditorWindowEvent { private let subject = PassthroughSubject() - var submits: AnyPublisher { + public var submits: AnyPublisher { subject.eraseToAnyPublisher() } public init() { } - func submit( + public func submit( value: TodoEditorWindowValue, todo: Todo ) { diff --git a/Application/DevLogPresentation/Sources/Home/TodoEditorWindowValue.swift b/Application/DevLogPresentation/Sources/Window/TodoEditorWindowValue.swift similarity index 91% rename from Application/DevLogPresentation/Sources/Home/TodoEditorWindowValue.swift rename to Application/DevLogPresentation/Sources/Window/TodoEditorWindowValue.swift index ec27f458..8dbf7c58 100644 --- a/Application/DevLogPresentation/Sources/Home/TodoEditorWindowValue.swift +++ b/Application/DevLogPresentation/Sources/Window/TodoEditorWindowValue.swift @@ -21,7 +21,7 @@ public enum TodoEditorWindowValue: Codable, Hashable { self = .create(TodoEditorWindowCategory(todoCategory: todoCategory), source) } - init(todo: Todo) { + public init(todo: Todo) { self = .edit(TodoEditorWindowTodo(todo: todo)) } } @@ -42,7 +42,7 @@ public struct TodoEditorWindowCategory: Codable, Hashable { private let name: String private let colorHex: String - init(todoCategory: TodoCategory) { + public init(todoCategory: TodoCategory) { switch todoCategory { case .system(let systemTodoCategory): self.kind = .system @@ -57,7 +57,7 @@ public struct TodoEditorWindowCategory: Codable, Hashable { } } - var todoCategory: TodoCategory { + public var todoCategory: TodoCategory { switch kind { case .system: let systemTodoCategory = SystemTodoCategory(rawValue: id) ?? .etc @@ -88,7 +88,7 @@ public struct TodoEditorWindowTodo: Codable, Hashable { private let tags: [String] private let category: TodoEditorWindowCategory - init(todo: Todo) { + public init(todo: Todo) { self.id = todo.id self.isPinned = todo.isPinned self.isCompleted = todo.isCompleted @@ -116,7 +116,7 @@ public struct TodoEditorWindowTodo: Codable, Hashable { hasher.combine(id) } - var todo: Todo { + public var todo: Todo { Todo( id: id, isPinned: isPinned, @@ -136,13 +136,13 @@ public struct TodoEditorWindowTodo: Codable, Hashable { } } -struct TodoEditorWindowSubmit: Equatable { - let id = UUID() - let value: TodoEditorWindowValue - let todo: Todo +public struct TodoEditorWindowSubmit: Equatable { + public let id = UUID() + public let value: TodoEditorWindowValue + public let todo: Todo } -extension TodoEditorWindowValue { +public extension TodoEditorWindowValue { func matchesCreate( category: TodoCategory? = nil, source: TodoEditorWindowSource diff --git a/Application/DevLogUI/DevLogUI.xcodeproj/project.pbxproj b/Application/DevLogUI/DevLogUI.xcodeproj/project.pbxproj new file mode 100644 index 00000000..7ff00ded --- /dev/null +++ b/Application/DevLogUI/DevLogUI.xcodeproj/project.pbxproj @@ -0,0 +1,712 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 07DC30D6EE53D3FEB18D4560 /* WebItemRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F78B3C0ACB40D516459405 /* WebItemRow.swift */; }; + 1F194D9E0C40F9C8D0BBFD81 /* CacheableImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 211DFAB11C0329E438E5D600 /* CacheableImage.swift */; }; + 35A74D1DFFD61848769F68B9 /* DevLogCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6127A27F1781E689752EA11A /* DevLogCore.framework */; }; + 3BC362E7B3729B9B766D0E82 /* TodoItemRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529B9370AF656D07E6CF9C75 /* TodoItemRow.swift */; }; + 3CD4C5F7BA6AE10FD96EED2D /* PushNotificationListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11529C090F3011129AC4A3 /* PushNotificationListView.swift */; }; + 436190F41C9108F6D4936027 /* TodoInfoSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19FC36435309F9D84B455253 /* TodoInfoSheetView.swift */; }; + 4726811A94C0478F5A807A02 /* View+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B63ED91862D0C29DCF3ACA6 /* View+.swift */; }; + 534E2A2C85E646406895773D /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BFDA486635D32624EED7F8D /* MainView.swift */; }; + 62A8F2367EF38AD98E4C4539 /* HeatmapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E82400E6B2C33B0E139E7C5D /* HeatmapView.swift */; }; + 62DB113F0728D6B2A24D4ECA /* RelativeTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E6647B22EA064CB79BABF1 /* RelativeTimeText.swift */; }; + 6878F8DBE4009B0462BFCAB4 /* TodoListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508B6C083CF1FB1B18A7A7C0 /* TodoListView.swift */; }; + 69E94B7AD0D2AF37B418A8F4 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80338B4AC83F0BC401B73C81 /* ProfileView.swift */; }; + 76DBAFDBB18F6EC9ABACFC2D /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D9416BB473F30DCCD7E859 /* HomeView.swift */; }; + 76DFE29F22D4336EE4C36B73 /* ThemeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A44E5A8322D9ADC2D2709471 /* ThemeView.swift */; }; + 7856BF83CB7D2D0F7B4774CF /* EnvironmentValues+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6056412772C2E91882B9C3D3 /* EnvironmentValues+.swift */; }; + 7DE3B4F05C4BEEF05B58FCF5 /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EB00F69C800B922FF8A48A /* SettingView.swift */; }; + 81BC04E40F6CED522AF09D8F /* PushNotificationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C9CD1A01AFBD1B64281D2 /* PushNotificationSettingsView.swift */; }; + 823A5A6BA41BCA257AD3586D /* TodoManageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF9DFAB631D9D4BFE8D5891 /* TodoManageView.swift */; }; + 87FF186B7907E7F1B2DE5E60 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56823A0901F960041578908A /* AccountView.swift */; }; + 9D8ECA3815FA283133EBFE93 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = CCFA0DE9FF601686A0FFA868 /* OrderedCollections */; }; + 9E96551C79A75284146EC61D /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3EC0620703CDC1D064C2F8 /* LoginView.swift */; }; + 9FC8F731D0C5F71AE08884ED /* TodoEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E155E338E408858E973C7C /* TodoEditorView.swift */; }; + AC8A6800BA486E5B49DF24D3 /* DevLogPresentation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D26E37F394F95F6AA0B184A4 /* DevLogPresentation.framework */; }; + AD69309B3A8854AC0C659DFF /* CheckBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CE52F7F2F780808645E2DBF /* CheckBox.swift */; }; + AF285B7139AEDA9D38AF387F /* ToolbarButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F84711E9735427E326A3D6 /* ToolbarButtons.swift */; }; + B32465C08D91FC006E5CF3D2 /* LoginButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC25959F0F7C16E491AF3A74 /* LoginButton.swift */; }; + B440FE5BC83625806D5137DC /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A2025370CB07568F045A6E /* WebView.swift */; }; + BF37121E20F7AE5443957E16 /* TodayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F2B7063B902FE6BD1DAD25 /* TodayView.swift */; }; + CD5C10D07CBA3369F4BE94D5 /* UIKitTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8001CAEEA6AF0CD88497E1 /* UIKitTextEditor.swift */; }; + E18A468C5AC5A60C18CF7A99 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B2F723F05245A13C55118AC /* SearchView.swift */; }; + E1F211A3100CD23E688CFEAD /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00F4FF1816573AFDF15BC06F /* LoadingView.swift */; }; + E20A6D6F2126952F83EF28AD /* TodoDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CBD74F72172BA31A47E1961 /* TodoDetailView.swift */; }; + E4AFAE6984B3251648B09C49 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEB00DB87DA750172D839934 /* Toast.swift */; }; + EC510917A2262FEE5842FADD /* Tags.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70BAD6474F5C8DA82B47AA2 /* Tags.swift */; }; + EE11A7B71A16E9A205B163D1 /* NavigationBarConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E8FBE8B634CD815D40C928 /* NavigationBarConfigurator.swift */; }; + F0AA0AF4F10C13242BBD4025 /* TodoDetailContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D51ADB239DCB3639943C6EE7 /* TodoDetailContentView.swift */; }; + FBAFCE11F1766FBFBB24807C /* TodoMarkdownContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3BBD8DF91AE3A8B7FAC599 /* TodoMarkdownContentView.swift */; }; + FD6C0E913FFA9988FFCF8419 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 7A26AD882B24E0CD5F4DEC5C /* MarkdownUI */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 1E92F96D37C3D41CAC9C29AF /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 00F4FF1816573AFDF15BC06F /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; + 19FC36435309F9D84B455253 /* TodoInfoSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoInfoSheetView.swift; sourceTree = ""; }; + 1BFDA486635D32624EED7F8D /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; + 1CE52F7F2F780808645E2DBF /* CheckBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBox.swift; sourceTree = ""; }; + 1F083216D01A83A4386A1258 /* DevLogUI-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "DevLogUI-Info.plist"; sourceTree = ""; }; + 211DFAB11C0329E438E5D600 /* CacheableImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheableImage.swift; sourceTree = ""; }; + 2B2F723F05245A13C55118AC /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; + 2C3EC0620703CDC1D064C2F8 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; + 508B6C083CF1FB1B18A7A7C0 /* TodoListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoListView.swift; sourceTree = ""; }; + 52512F0A61A001595E088847 /* Version.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Version.xcconfig; sourceTree = ""; }; + 529B9370AF656D07E6CF9C75 /* TodoItemRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoItemRow.swift; sourceTree = ""; }; + 53E6647B22EA064CB79BABF1 /* RelativeTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelativeTimeText.swift; sourceTree = ""; }; + 56823A0901F960041578908A /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = ""; }; + 5AF9DFAB631D9D4BFE8D5891 /* TodoManageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoManageView.swift; sourceTree = ""; }; + 6056412772C2E91882B9C3D3 /* EnvironmentValues+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentValues+.swift"; sourceTree = ""; }; + 6127A27F1781E689752EA11A /* DevLogCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6B63ED91862D0C29DCF3ACA6 /* View+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+.swift"; sourceTree = ""; }; + 77A2025370CB07568F045A6E /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = ""; }; + 80338B4AC83F0BC401B73C81 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; + 83E8FBE8B634CD815D40C928 /* NavigationBarConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarConfigurator.swift; sourceTree = ""; }; + 99F78B3C0ACB40D516459405 /* WebItemRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebItemRow.swift; sourceTree = ""; }; + 9CBD74F72172BA31A47E1961 /* TodoDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoDetailView.swift; sourceTree = ""; }; + 9F3BBD8DF91AE3A8B7FAC599 /* TodoMarkdownContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoMarkdownContentView.swift; sourceTree = ""; }; + A44E5A8322D9ADC2D2709471 /* ThemeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeView.swift; sourceTree = ""; }; + A8D9416BB473F30DCCD7E859 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; + AEB00DB87DA750172D839934 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; + BD11529C090F3011129AC4A3 /* PushNotificationListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationListView.swift; sourceTree = ""; }; + C41C9CD1A01AFBD1B64281D2 /* PushNotificationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationSettingsView.swift; sourceTree = ""; }; + C70BAD6474F5C8DA82B47AA2 /* Tags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tags.swift; sourceTree = ""; }; + CA0F9AEA78082777BD930922 /* DevLogUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CC25959F0F7C16E491AF3A74 /* LoginButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginButton.swift; sourceTree = ""; }; + CE8001CAEEA6AF0CD88497E1 /* UIKitTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitTextEditor.swift; sourceTree = ""; }; + D26E37F394F95F6AA0B184A4 /* DevLogPresentation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DevLogPresentation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D51ADB239DCB3639943C6EE7 /* TodoDetailContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoDetailContentView.swift; sourceTree = ""; }; + D5F2B7063B902FE6BD1DAD25 /* TodayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayView.swift; sourceTree = ""; }; + E2EB00F69C800B922FF8A48A /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = ""; }; + E82400E6B2C33B0E139E7C5D /* HeatmapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeatmapView.swift; sourceTree = ""; }; + F0F84711E9735427E326A3D6 /* ToolbarButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtons.swift; sourceTree = ""; }; + F6E155E338E408858E973C7C /* TodoEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoEditorView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + FE93059CE51F086DC6395122 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 35A74D1DFFD61848769F68B9 /* DevLogCore.framework in Frameworks */, + AC8A6800BA486E5B49DF24D3 /* DevLogPresentation.framework in Frameworks */, + FD6C0E913FFA9988FFCF8419 /* MarkdownUI in Frameworks */, + 9D8ECA3815FA283133EBFE93 /* OrderedCollections in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0F62DEECBFA6B482A490C188 /* Sources */ = { + isa = PBXGroup; + children = ( + AC7918DF414EAC42574C43C8 /* Common */, + 1BD39CF102FF36BDEDB10517 /* Extension */, + E30D4244433A5F1D744A152C /* Home */, + 82B843E51D57EFD360E76AA1 /* Login */, + 2C336E1602D5099AF3FA0726 /* Main */, + 9BC94E41246B01D219246605 /* Profile */, + CBE61554E7CA53C42DEA3473 /* PushNotification */, + 2BA5EAEFFE2AA1A6A16CF6D3 /* Search */, + BCA2DFAF282AC5461CF72C04 /* Setting */, + E2476D845591F375F0AB695C /* Today */, + ); + path = Sources; + sourceTree = ""; + }; + 1BD39CF102FF36BDEDB10517 /* Extension */ = { + isa = PBXGroup; + children = ( + 6056412772C2E91882B9C3D3 /* EnvironmentValues+.swift */, + 6B63ED91862D0C29DCF3ACA6 /* View+.swift */, + ); + path = Extension; + sourceTree = ""; + }; + 25EB81C4CCB2507411589B48 = { + isa = PBXGroup; + children = ( + B94A7F98803FFD2D526DC3F0 /* Products */, + AA9D3556BF0D5914D341DA26 /* Project */, + ); + sourceTree = ""; + }; + 2BA5EAEFFE2AA1A6A16CF6D3 /* Search */ = { + isa = PBXGroup; + children = ( + 2B2F723F05245A13C55118AC /* SearchView.swift */, + ); + path = Search; + sourceTree = ""; + }; + 2C336E1602D5099AF3FA0726 /* Main */ = { + isa = PBXGroup; + children = ( + 1BFDA486635D32624EED7F8D /* MainView.swift */, + ); + path = Main; + sourceTree = ""; + }; + 5582AA34439F4532105E68D4 /* Component */ = { + isa = PBXGroup; + children = ( + 211DFAB11C0329E438E5D600 /* CacheableImage.swift */, + 1CE52F7F2F780808645E2DBF /* CheckBox.swift */, + 00F4FF1816573AFDF15BC06F /* LoadingView.swift */, + CC25959F0F7C16E491AF3A74 /* LoginButton.swift */, + 53E6647B22EA064CB79BABF1 /* RelativeTimeText.swift */, + C70BAD6474F5C8DA82B47AA2 /* Tags.swift */, + AEB00DB87DA750172D839934 /* Toast.swift */, + 529B9370AF656D07E6CF9C75 /* TodoItemRow.swift */, + F0F84711E9735427E326A3D6 /* ToolbarButtons.swift */, + CE8001CAEEA6AF0CD88497E1 /* UIKitTextEditor.swift */, + 99F78B3C0ACB40D516459405 /* WebItemRow.swift */, + ); + path = Component; + sourceTree = ""; + }; + 6883B25D0D519884DF1060AB /* Shared */ = { + isa = PBXGroup; + children = ( + 52512F0A61A001595E088847 /* Version.xcconfig */, + ); + name = Shared; + path = ../Shared; + sourceTree = ""; + }; + 82B843E51D57EFD360E76AA1 /* Login */ = { + isa = PBXGroup; + children = ( + 2C3EC0620703CDC1D064C2F8 /* LoginView.swift */, + ); + path = Login; + sourceTree = ""; + }; + 9BC94E41246B01D219246605 /* Profile */ = { + isa = PBXGroup; + children = ( + E82400E6B2C33B0E139E7C5D /* HeatmapView.swift */, + 80338B4AC83F0BC401B73C81 /* ProfileView.swift */, + ); + path = Profile; + sourceTree = ""; + }; + AA9D3556BF0D5914D341DA26 /* Project */ = { + isa = PBXGroup; + children = ( + E4BC1BD7D39331BC72355666 /* Derived */, + 6883B25D0D519884DF1060AB /* Shared */, + 0F62DEECBFA6B482A490C188 /* Sources */, + ); + name = Project; + sourceTree = ""; + }; + AC7918DF414EAC42574C43C8 /* Common */ = { + isa = PBXGroup; + children = ( + 5582AA34439F4532105E68D4 /* Component */, + 83E8FBE8B634CD815D40C928 /* NavigationBarConfigurator.swift */, + D51ADB239DCB3639943C6EE7 /* TodoDetailContentView.swift */, + 19FC36435309F9D84B455253 /* TodoInfoSheetView.swift */, + 9F3BBD8DF91AE3A8B7FAC599 /* TodoMarkdownContentView.swift */, + 77A2025370CB07568F045A6E /* WebView.swift */, + ); + path = Common; + sourceTree = ""; + }; + B94A7F98803FFD2D526DC3F0 /* Products */ = { + isa = PBXGroup; + children = ( + 6127A27F1781E689752EA11A /* DevLogCore.framework */, + D26E37F394F95F6AA0B184A4 /* DevLogPresentation.framework */, + CA0F9AEA78082777BD930922 /* DevLogUI.framework */, + ); + name = Products; + sourceTree = ""; + }; + BCA2DFAF282AC5461CF72C04 /* Setting */ = { + isa = PBXGroup; + children = ( + 56823A0901F960041578908A /* AccountView.swift */, + C41C9CD1A01AFBD1B64281D2 /* PushNotificationSettingsView.swift */, + E2EB00F69C800B922FF8A48A /* SettingView.swift */, + A44E5A8322D9ADC2D2709471 /* ThemeView.swift */, + ); + path = Setting; + sourceTree = ""; + }; + C47B5B4ADB099CAA5E1E6730 /* Home */ = { + isa = PBXGroup; + children = ( + A8D9416BB473F30DCCD7E859 /* HomeView.swift */, + ); + path = Home; + sourceTree = ""; + }; + CBE61554E7CA53C42DEA3473 /* PushNotification */ = { + isa = PBXGroup; + children = ( + BD11529C090F3011129AC4A3 /* PushNotificationListView.swift */, + ); + path = PushNotification; + sourceTree = ""; + }; + E2476D845591F375F0AB695C /* Today */ = { + isa = PBXGroup; + children = ( + D5F2B7063B902FE6BD1DAD25 /* TodayView.swift */, + ); + path = Today; + sourceTree = ""; + }; + E30D4244433A5F1D744A152C /* Home */ = { + isa = PBXGroup; + children = ( + C47B5B4ADB099CAA5E1E6730 /* Home */, + 9CBD74F72172BA31A47E1961 /* TodoDetailView.swift */, + F6E155E338E408858E973C7C /* TodoEditorView.swift */, + 508B6C083CF1FB1B18A7A7C0 /* TodoListView.swift */, + 5AF9DFAB631D9D4BFE8D5891 /* TodoManageView.swift */, + ); + path = Home; + sourceTree = ""; + }; + E4BC1BD7D39331BC72355666 /* Derived */ = { + isa = PBXGroup; + children = ( + F1D1E807CD22F002CA87CBD0 /* InfoPlists */, + ); + path = Derived; + sourceTree = ""; + }; + F1D1E807CD22F002CA87CBD0 /* InfoPlists */ = { + isa = PBXGroup; + children = ( + 1F083216D01A83A4386A1258 /* DevLogUI-Info.plist */, + ); + path = InfoPlists; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + AE12CA663131269C6EAB64F6 /* DevLogUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = B87B2F8C687DAEE31621C1FD /* Build configuration list for PBXNativeTarget "DevLogUI" */; + buildPhases = ( + 6D6CD97FC849787DAD728B74 /* Sources */, + 4183F2CD6DE0FA9F29BF0EB7 /* Resources */, + FE93059CE51F086DC6395122 /* Frameworks */, + 1E92F96D37C3D41CAC9C29AF /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 3E13A9270FB03C78A0CE8011 /* PBXTargetDependency */, + ); + name = DevLogUI; + packageProductDependencies = ( + 7A26AD882B24E0CD5F4DEC5C /* MarkdownUI */, + CCFA0DE9FF601686A0FFA868 /* OrderedCollections */, + ); + productName = DevLogUI; + productReference = CA0F9AEA78082777BD930922 /* DevLogUI.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4766CAAF698AB49836766AC8 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 2650; + TargetAttributes = { + }; + }; + buildConfigurationList = 4B3A402E29F80157ACDEB612 /* Build configuration list for PBXProject "DevLogUI" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 25EB81C4CCB2507411589B48; + packageReferences = ( + 44402A3549052498AEA6F35E /* XCRemoteSwiftPackageReference "swift-collections" */, + 6FBE97EF929D461E04729183 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */, + 39308EE445B3D9740715053D /* XCRemoteSwiftPackageReference "SwiftLint" */, + ); + productRefGroup = B94A7F98803FFD2D526DC3F0 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + AE12CA663131269C6EAB64F6 /* DevLogUI */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4183F2CD6DE0FA9F29BF0EB7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6D6CD97FC849787DAD728B74 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1F194D9E0C40F9C8D0BBFD81 /* CacheableImage.swift in Sources */, + AD69309B3A8854AC0C659DFF /* CheckBox.swift in Sources */, + E1F211A3100CD23E688CFEAD /* LoadingView.swift in Sources */, + B32465C08D91FC006E5CF3D2 /* LoginButton.swift in Sources */, + 62DB113F0728D6B2A24D4ECA /* RelativeTimeText.swift in Sources */, + EC510917A2262FEE5842FADD /* Tags.swift in Sources */, + E4AFAE6984B3251648B09C49 /* Toast.swift in Sources */, + 3BC362E7B3729B9B766D0E82 /* TodoItemRow.swift in Sources */, + AF285B7139AEDA9D38AF387F /* ToolbarButtons.swift in Sources */, + CD5C10D07CBA3369F4BE94D5 /* UIKitTextEditor.swift in Sources */, + 07DC30D6EE53D3FEB18D4560 /* WebItemRow.swift in Sources */, + EE11A7B71A16E9A205B163D1 /* NavigationBarConfigurator.swift in Sources */, + F0AA0AF4F10C13242BBD4025 /* TodoDetailContentView.swift in Sources */, + 436190F41C9108F6D4936027 /* TodoInfoSheetView.swift in Sources */, + FBAFCE11F1766FBFBB24807C /* TodoMarkdownContentView.swift in Sources */, + B440FE5BC83625806D5137DC /* WebView.swift in Sources */, + 7856BF83CB7D2D0F7B4774CF /* EnvironmentValues+.swift in Sources */, + 4726811A94C0478F5A807A02 /* View+.swift in Sources */, + 76DBAFDBB18F6EC9ABACFC2D /* HomeView.swift in Sources */, + E20A6D6F2126952F83EF28AD /* TodoDetailView.swift in Sources */, + 9FC8F731D0C5F71AE08884ED /* TodoEditorView.swift in Sources */, + 6878F8DBE4009B0462BFCAB4 /* TodoListView.swift in Sources */, + 823A5A6BA41BCA257AD3586D /* TodoManageView.swift in Sources */, + 9E96551C79A75284146EC61D /* LoginView.swift in Sources */, + 534E2A2C85E646406895773D /* MainView.swift in Sources */, + 62A8F2367EF38AD98E4C4539 /* HeatmapView.swift in Sources */, + 69E94B7AD0D2AF37B418A8F4 /* ProfileView.swift in Sources */, + 3CD4C5F7BA6AE10FD96EED2D /* PushNotificationListView.swift in Sources */, + E18A468C5AC5A60C18CF7A99 /* SearchView.swift in Sources */, + 87FF186B7907E7F1B2DE5E60 /* AccountView.swift in Sources */, + 81BC04E40F6CED522AF09D8F /* PushNotificationSettingsView.swift in Sources */, + 7DE3B4F05C4BEEF05B58FCF5 /* SettingView.swift in Sources */, + 76DFE29F22D4336EE4C36B73 /* ThemeView.swift in Sources */, + BF37121E20F7AE5443957E16 /* TodayView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 3E13A9270FB03C78A0CE8011 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = AA737E3F8BA86338D8508844 /* SwiftLintBuildToolPlugin */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 791C4AC0851482878752E469 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 52512F0A61A001595E088847 /* Version.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 4CPC6N38WA; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_KEY_CFBundleShortVersionString = "$(MARKETING_VERSION)"; + INFOPLIST_KEY_CFBundleVersion = "$(CURRENT_PROJECT_VERSION)"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 8462DF0BADC3222ED4E4A89B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 52512F0A61A001595E088847 /* Version.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Derived/InfoPlists/DevLogUI-Info.plist"; + INFOPLIST_KEY_CFBundleShortVersionString = "$(MARKETING_VERSION)"; + INFOPLIST_KEY_CFBundleVersion = "$(CURRENT_PROJECT_VERSION)"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-L$(DT_TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.opfic.DevLog.DevLogUI; + PRODUCT_NAME = DevLogUI; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 84C2AC99DFE2402189CE2500 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 52512F0A61A001595E088847 /* Version.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 4CPC6N38WA; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_KEY_CFBundleShortVersionString = "$(MARKETING_VERSION)"; + INFOPLIST_KEY_CFBundleVersion = "$(CURRENT_PROJECT_VERSION)"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + E6C58351FF08829E9E26F1D2 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 52512F0A61A001595E088847 /* Version.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Derived/InfoPlists/DevLogUI-Info.plist"; + INFOPLIST_KEY_CFBundleShortVersionString = "$(MARKETING_VERSION)"; + INFOPLIST_KEY_CFBundleVersion = "$(CURRENT_PROJECT_VERSION)"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-L$(DT_TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.opfic.DevLog.DevLogUI; + PRODUCT_NAME = DevLogUI; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( + "$(inherited)", + DEBUG, + ); + SWIFT_COMPILATION_MODE = singlefile; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4B3A402E29F80157ACDEB612 /* Build configuration list for PBXProject "DevLogUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 84C2AC99DFE2402189CE2500 /* Debug */, + 791C4AC0851482878752E469 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B87B2F8C687DAEE31621C1FD /* Build configuration list for PBXNativeTarget "DevLogUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E6C58351FF08829E9E26F1D2 /* Debug */, + 8462DF0BADC3222ED4E4A89B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 39308EE445B3D9740715053D /* XCRemoteSwiftPackageReference "SwiftLint" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/realm/SwiftLint"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.62.1; + }; + }; + 44402A3549052498AEA6F35E /* XCRemoteSwiftPackageReference "swift-collections" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-collections.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.3.0; + }; + }; + 6FBE97EF929D461E04729183 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/gonzalezreal/swift-markdown-ui.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.4.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 7A26AD882B24E0CD5F4DEC5C /* MarkdownUI */ = { + isa = XCSwiftPackageProductDependency; + productName = MarkdownUI; + }; + AA737E3F8BA86338D8508844 /* SwiftLintBuildToolPlugin */ = { + isa = XCSwiftPackageProductDependency; + productName = "plugin:SwiftLintBuildToolPlugin"; + }; + CCFA0DE9FF601686A0FFA868 /* OrderedCollections */ = { + isa = XCSwiftPackageProductDependency; + productName = OrderedCollections; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 4766CAAF698AB49836766AC8 /* Project object */; +} diff --git a/Application/DevLogUI/DevLogUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Application/DevLogUI/DevLogUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/Application/DevLogUI/DevLogUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Application/DevLogUI/DevLogUI.xcodeproj/xcshareddata/xcschemes/DevLogUI.xcscheme b/Application/DevLogUI/DevLogUI.xcodeproj/xcshareddata/xcschemes/DevLogUI.xcscheme new file mode 100644 index 00000000..732553b2 --- /dev/null +++ b/Application/DevLogUI/DevLogUI.xcodeproj/xcshareddata/xcschemes/DevLogUI.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Application/DevLogUI/Project.swift b/Application/DevLogUI/Project.swift new file mode 100644 index 00000000..77807d7a --- /dev/null +++ b/Application/DevLogUI/Project.swift @@ -0,0 +1,14 @@ +import ProjectDescription +import ProjectDescriptionHelpers + +let project = Project.devlogFramework( + name: "DevLogUI", + bundleId: "com.opfic.DevLog.DevLogUI", + versionXcconfigPath: "../Shared/Version.xcconfig", + packages: DevLogPackages.presentationPackages, + dependencies: [ + .project(target: "DevLogCore", path: "../DevLogCore"), + .project(target: "DevLogPresentation", path: "../DevLogPresentation"), + ] + DevLogPackages.presentationPackageDependencies, + hasTests: false +) diff --git a/Application/DevLogUI/Sources/.swiftlint.yml b/Application/DevLogUI/Sources/.swiftlint.yml new file mode 100644 index 00000000..1242ffca --- /dev/null +++ b/Application/DevLogUI/Sources/.swiftlint.yml @@ -0,0 +1 @@ +parent_config: ../../../.swiftlint.yml diff --git a/Application/DevLogPresentation/Sources/Common/Component/CacheableImage.swift b/Application/DevLogUI/Sources/Common/Component/CacheableImage.swift similarity index 98% rename from Application/DevLogPresentation/Sources/Common/Component/CacheableImage.swift rename to Application/DevLogUI/Sources/Common/Component/CacheableImage.swift index 0ecb0754..f94901db 100644 --- a/Application/DevLogPresentation/Sources/Common/Component/CacheableImage.swift +++ b/Application/DevLogUI/Sources/Common/Component/CacheableImage.swift @@ -1,12 +1,12 @@ // // CacheableImage.swift -// DevLogPresentation +// DevLogUI // // Created by 최윤진 on 11/30/25. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct CacheableImage: View { @State private var loadedUIImage: UIImage? diff --git a/Application/DevLogPresentation/Sources/Common/Component/CheckBox.swift b/Application/DevLogUI/Sources/Common/Component/CheckBox.swift similarity index 94% rename from Application/DevLogPresentation/Sources/Common/Component/CheckBox.swift rename to Application/DevLogUI/Sources/Common/Component/CheckBox.swift index 1354d636..445f9ae5 100644 --- a/Application/DevLogPresentation/Sources/Common/Component/CheckBox.swift +++ b/Application/DevLogUI/Sources/Common/Component/CheckBox.swift @@ -1,12 +1,12 @@ // // CheckBox.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 6/17/25. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct CheckBox: View { private let isChecked: Bool diff --git a/Application/DevLogPresentation/Sources/Common/Component/LoadingView.swift b/Application/DevLogUI/Sources/Common/Component/LoadingView.swift similarity index 87% rename from Application/DevLogPresentation/Sources/Common/Component/LoadingView.swift rename to Application/DevLogUI/Sources/Common/Component/LoadingView.swift index bd81630a..a304c0bc 100644 --- a/Application/DevLogPresentation/Sources/Common/Component/LoadingView.swift +++ b/Application/DevLogUI/Sources/Common/Component/LoadingView.swift @@ -1,12 +1,12 @@ // // LoadingView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 5/16/25. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct LoadingView: View { var body: some View { diff --git a/Application/DevLogPresentation/Sources/Common/Component/LoginButton.swift b/Application/DevLogUI/Sources/Common/Component/LoginButton.swift similarity index 96% rename from Application/DevLogPresentation/Sources/Common/Component/LoginButton.swift rename to Application/DevLogUI/Sources/Common/Component/LoginButton.swift index 7c9c6fe0..1649584a 100644 --- a/Application/DevLogPresentation/Sources/Common/Component/LoginButton.swift +++ b/Application/DevLogUI/Sources/Common/Component/LoginButton.swift @@ -1,12 +1,12 @@ // // LoginButton.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 4/25/25. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct LoginButton: View { @State private var logo: Image? diff --git a/Application/DevLogPresentation/Sources/Common/Component/RelativeTimeText.swift b/Application/DevLogUI/Sources/Common/Component/RelativeTimeText.swift similarity index 97% rename from Application/DevLogPresentation/Sources/Common/Component/RelativeTimeText.swift rename to Application/DevLogUI/Sources/Common/Component/RelativeTimeText.swift index 083cefee..4e153d3f 100644 --- a/Application/DevLogPresentation/Sources/Common/Component/RelativeTimeText.swift +++ b/Application/DevLogUI/Sources/Common/Component/RelativeTimeText.swift @@ -1,12 +1,12 @@ // // RelativeTimeText.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 3/25/26. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct RelativeTimeText: View { let date: Date diff --git a/Application/DevLogPresentation/Sources/Common/Component/Tags.swift b/Application/DevLogUI/Sources/Common/Component/Tags.swift similarity index 99% rename from Application/DevLogPresentation/Sources/Common/Component/Tags.swift rename to Application/DevLogUI/Sources/Common/Component/Tags.swift index 685e6d6b..7a339f81 100644 --- a/Application/DevLogPresentation/Sources/Common/Component/Tags.swift +++ b/Application/DevLogUI/Sources/Common/Component/Tags.swift @@ -1,12 +1,12 @@ // // Tags.swift -// DevLogPresentation +// DevLogUI // // Created by 최윤진 on 2/6/26. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct Tag: View { @Environment(\.colorScheme) private var colorScheme diff --git a/Application/DevLogPresentation/Sources/Common/Component/Toast.swift b/Application/DevLogUI/Sources/Common/Component/Toast.swift similarity index 99% rename from Application/DevLogPresentation/Sources/Common/Component/Toast.swift rename to Application/DevLogUI/Sources/Common/Component/Toast.swift index 43f8af56..548b3c8c 100644 --- a/Application/DevLogPresentation/Sources/Common/Component/Toast.swift +++ b/Application/DevLogUI/Sources/Common/Component/Toast.swift @@ -1,12 +1,12 @@ // // Toast.swift -// DevLogPresentation +// DevLogUI // // Created by 최윤진 on 2/10/26. // import SwiftUI -import DevLogDomain +import DevLogPresentation extension View { func toast( diff --git a/Application/DevLogPresentation/Sources/Common/Component/TodoItemRow.swift b/Application/DevLogUI/Sources/Common/Component/TodoItemRow.swift similarity index 97% rename from Application/DevLogPresentation/Sources/Common/Component/TodoItemRow.swift rename to Application/DevLogUI/Sources/Common/Component/TodoItemRow.swift index 6aad9508..1ba9792e 100644 --- a/Application/DevLogPresentation/Sources/Common/Component/TodoItemRow.swift +++ b/Application/DevLogUI/Sources/Common/Component/TodoItemRow.swift @@ -1,12 +1,12 @@ // // TodoItemRow.swift -// DevLogPresentation +// DevLogUI // // Created by 최윤진 on 2/21/26. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct TodoItemRow: View { @ScaledMetric(relativeTo: .largeTitle) private var labelWidth = CGFloat(34) diff --git a/Application/DevLogPresentation/Sources/Common/Component/ToolbarButtons.swift b/Application/DevLogUI/Sources/Common/Component/ToolbarButtons.swift similarity index 86% rename from Application/DevLogPresentation/Sources/Common/Component/ToolbarButtons.swift rename to Application/DevLogUI/Sources/Common/Component/ToolbarButtons.swift index 0869baeb..3b443bed 100644 --- a/Application/DevLogPresentation/Sources/Common/Component/ToolbarButtons.swift +++ b/Application/DevLogUI/Sources/Common/Component/ToolbarButtons.swift @@ -1,17 +1,21 @@ // // ToolbarButtons.swift -// DevLogPresentation +// DevLogUI // // Created by 최윤진 on 3/1/26. // import SwiftUI -import DevLogDomain +import DevLogPresentation -struct ToolbarLeadingButton: ToolbarContent { +public struct ToolbarLeadingButton: ToolbarContent { var action: (() -> Void)? - var body: some ToolbarContent { + public init(action: (() -> Void)? = nil) { + self.action = action + } + + public var body: some ToolbarContent { ToolbarItem(placement: .topBarLeading) { if #available(iOS 26.0, *) { Button(role: .cancel) { diff --git a/Application/DevLogPresentation/Sources/Common/Component/UIKitTextEditor.swift b/Application/DevLogUI/Sources/Common/Component/UIKitTextEditor.swift similarity index 99% rename from Application/DevLogPresentation/Sources/Common/Component/UIKitTextEditor.swift rename to Application/DevLogUI/Sources/Common/Component/UIKitTextEditor.swift index 68706d3a..ec06bfb2 100644 --- a/Application/DevLogPresentation/Sources/Common/Component/UIKitTextEditor.swift +++ b/Application/DevLogUI/Sources/Common/Component/UIKitTextEditor.swift @@ -1,13 +1,13 @@ // // UIKitTextEditor.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 3/18/26. // import SwiftUI +import DevLogPresentation import UIKit -import DevLogDomain struct UIKitTextEditor: View { @Binding var text: String diff --git a/Application/DevLogPresentation/Sources/Common/Component/WebItemRow.swift b/Application/DevLogUI/Sources/Common/Component/WebItemRow.swift similarity index 97% rename from Application/DevLogPresentation/Sources/Common/Component/WebItemRow.swift rename to Application/DevLogUI/Sources/Common/Component/WebItemRow.swift index f7abbb95..effc8b9f 100644 --- a/Application/DevLogPresentation/Sources/Common/Component/WebItemRow.swift +++ b/Application/DevLogUI/Sources/Common/Component/WebItemRow.swift @@ -1,12 +1,12 @@ // // WebItemRow.swift -// DevLogPresentation +// DevLogUI // // Created by 최윤진 on 2/24/26. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct WebItemRow: View { @ScaledMetric(relativeTo: .largeTitle) private var labelWidth = CGFloat(34) diff --git a/Application/DevLogPresentation/Sources/Common/NavigationBarConfigurator.swift b/Application/DevLogUI/Sources/Common/NavigationBarConfigurator.swift similarity index 98% rename from Application/DevLogPresentation/Sources/Common/NavigationBarConfigurator.swift rename to Application/DevLogUI/Sources/Common/NavigationBarConfigurator.swift index 3828dcb4..aae1273f 100644 --- a/Application/DevLogPresentation/Sources/Common/NavigationBarConfigurator.swift +++ b/Application/DevLogUI/Sources/Common/NavigationBarConfigurator.swift @@ -1,12 +1,12 @@ // // NavigationBarConfigurator.swift -// DevLogPresentation +// DevLogUI // // Created by 최윤진 on 3/5/26. // import SwiftUI -import DevLogDomain +import DevLogPresentation /// NavigationBar의 배경색을 지정하고 shadowColor를 제거하는 구조체 /// diff --git a/Application/DevLogPresentation/Sources/Common/TodoDetailContentView.swift b/Application/DevLogUI/Sources/Common/TodoDetailContentView.swift similarity index 96% rename from Application/DevLogPresentation/Sources/Common/TodoDetailContentView.swift rename to Application/DevLogUI/Sources/Common/TodoDetailContentView.swift index 2ba8fb53..4e95b080 100644 --- a/Application/DevLogPresentation/Sources/Common/TodoDetailContentView.swift +++ b/Application/DevLogUI/Sources/Common/TodoDetailContentView.swift @@ -1,13 +1,13 @@ // // TodoDetailContentView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 3/2/26. // import SwiftUI +import DevLogPresentation import MarkdownUI -import DevLogDomain struct TodoDetailContentView: View { let title: String diff --git a/Application/DevLogPresentation/Sources/Common/TodoInfoSheetView.swift b/Application/DevLogUI/Sources/Common/TodoInfoSheetView.swift similarity index 98% rename from Application/DevLogPresentation/Sources/Common/TodoInfoSheetView.swift rename to Application/DevLogUI/Sources/Common/TodoInfoSheetView.swift index 6f57efba..fc2ae1fd 100644 --- a/Application/DevLogPresentation/Sources/Common/TodoInfoSheetView.swift +++ b/Application/DevLogUI/Sources/Common/TodoInfoSheetView.swift @@ -1,12 +1,12 @@ // // TodoInfoSheetView.swift -// DevLogPresentation +// DevLogUI // // Created by 최윤진 on 3/2/26. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct TodoInfoSheetView: View { let createdAt: Date diff --git a/Application/DevLogPresentation/Sources/Common/TodoMarkdownContentView.swift b/Application/DevLogUI/Sources/Common/TodoMarkdownContentView.swift similarity index 99% rename from Application/DevLogPresentation/Sources/Common/TodoMarkdownContentView.swift rename to Application/DevLogUI/Sources/Common/TodoMarkdownContentView.swift index cfbdcf65..2c3e883a 100644 --- a/Application/DevLogPresentation/Sources/Common/TodoMarkdownContentView.swift +++ b/Application/DevLogUI/Sources/Common/TodoMarkdownContentView.swift @@ -1,13 +1,13 @@ // // TodoMarkdownContentView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 3/25/26. // import MarkdownUI import SwiftUI -import DevLogDomain +import DevLogPresentation private enum TodoMarkdownSection: Equatable { case markdown(String) diff --git a/Application/DevLogPresentation/Sources/Common/WebView.swift b/Application/DevLogUI/Sources/Common/WebView.swift similarity index 90% rename from Application/DevLogPresentation/Sources/Common/WebView.swift rename to Application/DevLogUI/Sources/Common/WebView.swift index e4657684..17f8b451 100644 --- a/Application/DevLogPresentation/Sources/Common/WebView.swift +++ b/Application/DevLogUI/Sources/Common/WebView.swift @@ -1,13 +1,13 @@ // // WebView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 5/23/25. // import SwiftUI +import DevLogPresentation import WebKit -import DevLogDomain struct WebView: UIViewRepresentable { let url: URL diff --git a/Application/DevLogPresentation/Sources/Extension/EnvironmentValues+.swift b/Application/DevLogUI/Sources/Extension/EnvironmentValues+.swift similarity index 97% rename from Application/DevLogPresentation/Sources/Extension/EnvironmentValues+.swift rename to Application/DevLogUI/Sources/Extension/EnvironmentValues+.swift index 7b8eb91a..34de3489 100644 --- a/Application/DevLogPresentation/Sources/Extension/EnvironmentValues+.swift +++ b/Application/DevLogUI/Sources/Extension/EnvironmentValues+.swift @@ -1,12 +1,12 @@ // // EnvironmentValues+.swift -// DevLogPresentation +// DevLogUI // // Created by 최윤진 on 2/6/26. // import SwiftUI -import DevLogDomain +import DevLogPresentation extension EnvironmentValues { var safeAreaInsets: EdgeInsets { diff --git a/Application/DevLogPresentation/Sources/Extension/View+.swift b/Application/DevLogUI/Sources/Extension/View+.swift similarity index 98% rename from Application/DevLogPresentation/Sources/Extension/View+.swift rename to Application/DevLogUI/Sources/Extension/View+.swift index 96337143..5446f7a9 100644 --- a/Application/DevLogPresentation/Sources/Extension/View+.swift +++ b/Application/DevLogUI/Sources/Extension/View+.swift @@ -1,12 +1,12 @@ // // View+.swift -// DevLogPresentation +// DevLogUI // // Created by 최윤진 on 11/22/25. // import SwiftUI -import DevLogDomain +import DevLogPresentation extension View { @ViewBuilder diff --git a/Application/DevLogPresentation/Sources/Home/Home/HomeView.swift b/Application/DevLogUI/Sources/Home/Home/HomeView.swift similarity index 96% rename from Application/DevLogPresentation/Sources/Home/Home/HomeView.swift rename to Application/DevLogUI/Sources/Home/Home/HomeView.swift index ee3040c0..fe982505 100644 --- a/Application/DevLogPresentation/Sources/Home/Home/HomeView.swift +++ b/Application/DevLogUI/Sources/Home/Home/HomeView.swift @@ -1,12 +1,12 @@ // // HomeView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 5/7/25. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct HomeView: View { @Environment(\.openWindow) private var openWindow @@ -14,6 +14,7 @@ struct HomeView: View { @ScaledMetric(relativeTo: .largeTitle) private var labelWidth = CGFloat(34) let coordinator: HomeViewCoordinator let isCompactLayout: Bool + let todoViewModelFactory: TodoViewModelFactory var body: some View { List { @@ -50,7 +51,8 @@ struct HomeView: View { )) { if let selectedCategory = coordinator.viewModel.state.selectedTodoCategory { TodoEditorView( - viewModel: coordinator.makeTodoEditorViewModel(category: selectedCategory) + viewModel: coordinator.makeTodoEditorViewModel(category: selectedCategory), + todoViewModelFactory: todoViewModelFactory ) } } @@ -58,7 +60,10 @@ struct HomeView: View { get: { coordinator.viewModel.state.showSearchView }, set: { coordinator.viewModel.send(.setPresentation(.searchView, $0)) } )) { - SearchView(viewModel: coordinator.makeSearchViewModel()) + SearchView( + viewModel: coordinator.makeSearchViewModel(), + todoViewModelFactory: todoViewModelFactory + ) } .alert( coordinator.viewModel.state.alertTitle, @@ -317,7 +322,7 @@ struct HomeView: View { ForEach(preferences, id: \.id) { item in Button { DispatchQueue.main.async { - openTodoEditor(for: item.category) + openTodoEditor(for: item) } } label: { labelImage( @@ -387,26 +392,20 @@ struct HomeView: View { .contentShape(.rect) } - private func openTodoEditor(for todoCategory: TodoCategory) { + private func openTodoEditor(for item: TodoCategoryItem) { if isiOSAppOnMac { coordinator.viewModel.send(.setPresentation(.contentPicker, false)) openWindow( id: TodoEditorWindowValue.sceneId, - value: TodoEditorWindowValue(todoCategory: todoCategory, source: .home) + value: TodoEditorWindowValue(todoCategory: item.category, source: .home) ) } else { - coordinator.viewModel.send(.tapTodoCategory(todoCategory)) + coordinator.viewModel.send(.tapTodoCategory(item.category)) } } } -enum HomeRoute: Hashable { - case category(TodoCategoryItem) - case todo(TodoIdItem) - case webPage(WebPageItem) -} - private struct RecentTodoRow: View { @ScaledMetric(relativeTo: .largeTitle) private var labelWidth = CGFloat(34) let todo: RecentTodoItem diff --git a/Application/DevLogPresentation/Sources/Home/TodoDetailView.swift b/Application/DevLogUI/Sources/Home/TodoDetailView.swift similarity index 78% rename from Application/DevLogPresentation/Sources/Home/TodoDetailView.swift rename to Application/DevLogUI/Sources/Home/TodoDetailView.swift index 93d9a62e..d5526880 100644 --- a/Application/DevLogPresentation/Sources/Home/TodoDetailView.swift +++ b/Application/DevLogUI/Sources/Home/TodoDetailView.swift @@ -1,21 +1,28 @@ // // TodoDetailView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 6/12/25. // import SwiftUI -import DevLogCore -import DevLogDomain +import DevLogPresentation -struct TodoDetailView: View { - @Environment(\.diContainer) private var container: DIContainer +public struct TodoDetailView: View { @Environment(\.openWindow) private var openWindow @Environment(\.isiOSAppOnMac) private var isiOSAppOnMac @State var viewModel: TodoDetailViewModel + let todoViewModelFactory: TodoViewModelFactory + + public init( + viewModel: TodoDetailViewModel, + todoViewModelFactory: TodoViewModelFactory + ) { + self.viewModel = viewModel + self.todoViewModelFactory = todoViewModelFactory + } - var body: some View { + public var body: some View { ZStack { Color(.secondarySystemBackground).ignoresSafeArea() if let todo = viewModel.state.todo, let number = todo.number { @@ -43,12 +50,13 @@ struct TodoDetailView: View { set: { viewModel.send(.setSelectedTodoId($0)) } )) { item in NavigationStack { - TodoDetailView(viewModel: TodoDetailViewModel( - fetchTodoUseCase: container.resolve(FetchTodoByIdUseCase.self), - fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self), - todoId: item.id, - showEditButton: false - )) + TodoDetailView( + viewModel: todoViewModelFactory.makeDetailViewModel( + todoId: item.id, + showEditButton: false + ), + todoViewModelFactory: todoViewModelFactory + ) .toolbar { ToolbarLeadingButton { viewModel.send(.setSelectedTodoId(nil)) @@ -64,16 +72,14 @@ struct TodoDetailView: View { )) { if let todo = viewModel.state.todo { TodoEditorView( - viewModel: TodoEditorViewModel( + viewModel: todoViewModelFactory.makeEditorViewModel( todo: todo, - fetchPreferencesUseCase: container.resolve(FetchTodoCategoryPreferencesUseCase.self), - fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self), - upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self), onUpsertSuccess: { todo in viewModel.send(.setShowEditor(false)) viewModel.send(.setTodo(todo)) } - ) + ), + todoViewModelFactory: todoViewModelFactory ) } } @@ -118,7 +124,13 @@ struct TodoDetailView: View { @ViewBuilder private var sheetContent: some View { if let todo = viewModel.state.todo { - TodoDetailInfoSheetView(todo: todo) { + TodoDetailInfoSheetView( + categoryName: TodoCategoryItem(from: todo.category).localizedName, + isCompleted: todo.isCompleted, + isPinned: todo.isPinned, + dueDate: todo.dueDate, + tags: todo.tags + ) { viewModel.send(.setShowInfo(false)) } } @@ -126,7 +138,11 @@ struct TodoDetailView: View { } private struct TodoDetailInfoSheetView: View { - let todo: Todo + let categoryName: String + let isCompleted: Bool + let isPinned: Bool + let dueDate: Date? + let tags: [String] let onClose: () -> Void private let calendar = Calendar.current @@ -137,20 +153,20 @@ private struct TodoDetailInfoSheetView: View { HStack { Text(String(localized: "todo_category")) Spacer() - Text(TodoCategoryItem(from: todo.category).localizedName) + Text(categoryName) .foregroundStyle(.secondary) } statusRow( title: String(localized: "todo_completed"), - systemImage: todo.isCompleted ? "checkmark.circle.fill" : "circle", - color: todo.isCompleted ? .green : .secondary + systemImage: isCompleted ? "checkmark.circle.fill" : "circle", + color: isCompleted ? .green : .secondary ) statusRow( title: String(localized: "todo_pinned"), - systemImage: todo.isPinned ? "star.fill" : "star", - color: todo.isPinned ? .orange : .secondary + systemImage: isPinned ? "star.fill" : "star", + color: isPinned ? .orange : .secondary ) HStack { @@ -158,7 +174,7 @@ private struct TodoDetailInfoSheetView: View { Spacer() - if let dueDate = todo.dueDate { + if let dueDate { Tag(dueDateText(for: dueDate), isEditing: false) .padding(.vertical, -4) } else { @@ -169,12 +185,12 @@ private struct TodoDetailInfoSheetView: View { } Section(String(localized: "todo_tags")) { - if todo.tags.isEmpty { + if tags.isEmpty { Text(String(localized: "todo_no_tags")) .foregroundStyle(.secondary) .padding(.vertical, 4) } else { - TagList(todo.tags) + TagList(tags) } } } diff --git a/Application/DevLogPresentation/Sources/Home/TodoEditorView.swift b/Application/DevLogUI/Sources/Home/TodoEditorView.swift similarity index 94% rename from Application/DevLogPresentation/Sources/Home/TodoEditorView.swift rename to Application/DevLogUI/Sources/Home/TodoEditorView.swift index 2d01eeae..20f330d7 100644 --- a/Application/DevLogPresentation/Sources/Home/TodoEditorView.swift +++ b/Application/DevLogUI/Sources/Home/TodoEditorView.swift @@ -1,6 +1,6 @@ // // TodoEditorView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 5/31/25. // @@ -8,19 +8,28 @@ import MarkdownUI import OrderedCollections import SwiftUI -import DevLogCore -import DevLogDomain +import DevLogPresentation -struct TodoEditorView: View { +public struct TodoEditorView: View { @State var viewModel: TodoEditorViewModel - @Environment(\.diContainer) private var container: DIContainer @Environment(\.dismiss) private var dismiss @Environment(\.isiOSAppOnMac) private var isiOSAppOnMac @FocusState private var field: Field? private let calendar = Calendar.current + let todoViewModelFactory: TodoViewModelFactory var onClose: (() -> Void)? - var body: some View { + public init( + viewModel: TodoEditorViewModel, + todoViewModelFactory: TodoViewModelFactory, + onClose: (() -> Void)? = nil + ) { + self.viewModel = viewModel + self.todoViewModelFactory = todoViewModelFactory + self.onClose = onClose + } + + public var body: some View { NavigationStack { ScrollView { LazyVStack(spacing: 10) { @@ -61,12 +70,13 @@ struct TodoEditorView: View { set: { viewModel.send(.setSelectedTodoId($0)) } )) { item in NavigationStack { - TodoDetailView(viewModel: TodoDetailViewModel( - fetchTodoUseCase: container.resolve(FetchTodoByIdUseCase.self), - fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self), - todoId: item.id, - showEditButton: false - )) + TodoDetailView( + viewModel: todoViewModelFactory.makeDetailViewModel( + todoId: item.id, + showEditButton: false + ), + todoViewModelFactory: todoViewModelFactory + ) .toolbar { ToolbarLeadingButton { viewModel.send(.setSelectedTodoId(nil)) diff --git a/Application/DevLogPresentation/Sources/Home/TodoListView.swift b/Application/DevLogUI/Sources/Home/TodoListView.swift similarity index 94% rename from Application/DevLogPresentation/Sources/Home/TodoListView.swift rename to Application/DevLogUI/Sources/Home/TodoListView.swift index a8c2ab11..b96f2719 100644 --- a/Application/DevLogPresentation/Sources/Home/TodoListView.swift +++ b/Application/DevLogUI/Sources/Home/TodoListView.swift @@ -1,17 +1,15 @@ // // TodoListView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 5/30/25. // import SwiftUI -import DevLogCore -import DevLogDomain +import DevLogPresentation struct TodoListView: View { @Environment(NavigationRouter.self) private var router - @Environment(\.diContainer) var container: DIContainer @Environment(\.colorScheme) private var colorScheme @Environment(\.openWindow) private var openWindow @Environment(\.isiOSAppOnMac) private var isiOSAppOnMac @@ -19,6 +17,7 @@ struct TodoListView: View { @State private var headerOffset: CGFloat = .zero @State private var isScrollTrackingEnabled = false @State var viewModel: TodoListViewModel + let todoViewModelFactory: TodoViewModelFactory var body: some View { Group { @@ -81,17 +80,14 @@ struct TodoListView: View { set: { viewModel.send(.setShowEditor($0)) } )) { TodoEditorView( - viewModel: TodoEditorViewModel( + viewModel: todoViewModelFactory.makeEditorViewModel( category: viewModel.category, - fetchPreferencesUseCase: container.resolve(FetchTodoCategoryPreferencesUseCase.self), - fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self), - upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self), - trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self), onUpsertSuccess: { _ in viewModel.send(.setShowEditor(false)) viewModel.send(.refresh) } - ) + ), + todoViewModelFactory: todoViewModelFactory ) } .toolbar { @@ -350,7 +346,7 @@ struct TodoListView: View { get: { viewModel.state.query.sortTarget }, set: { viewModel.send(.setSortTarget($0)) } )) { - ForEach([TodoQuery.SortTarget.createdAt, .updatedAt], id: \.self) { option in + ForEach([TodoListViewModel.SortTarget.createdAt, .updatedAt], id: \.self) { option in Text(option.title).tag(option) } } label: { @@ -360,7 +356,7 @@ struct TodoListView: View { get: { viewModel.state.query.sortOrder }, set: { viewModel.send(.setSortOrder($0)) } )) { - ForEach([TodoQuery.SortOrder.latest, .oldest], id: \.self) { option in + ForEach([TodoListViewModel.SortOrder.latest, .oldest], id: \.self) { option in Text(option.title).tag(option) } } label: { @@ -396,7 +392,7 @@ struct TodoListView: View { get: { viewModel.state.query.completionFilter }, set: { viewModel.send(.setCompletionFilter($0)) } )) { - ForEach([TodoQuery.CompletionFilter.all, .incomplete, .completed], id: \.self) { option in + ForEach([TodoListViewModel.CompletionFilter.all, .incomplete, .completed], id: \.self) { option in Text(option.title).tag(option) } } label: { diff --git a/Application/DevLogPresentation/Sources/Home/TodoManageView.swift b/Application/DevLogUI/Sources/Home/TodoManageView.swift similarity index 99% rename from Application/DevLogPresentation/Sources/Home/TodoManageView.swift rename to Application/DevLogUI/Sources/Home/TodoManageView.swift index 268c1d52..520c7f04 100644 --- a/Application/DevLogPresentation/Sources/Home/TodoManageView.swift +++ b/Application/DevLogUI/Sources/Home/TodoManageView.swift @@ -1,12 +1,12 @@ // // TodoManageView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 6/16/25. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct TodoManageView: View { @State var viewModel: TodoManageViewModel diff --git a/Application/DevLogPresentation/Sources/Login/LoginView.swift b/Application/DevLogUI/Sources/Login/LoginView.swift similarity index 90% rename from Application/DevLogPresentation/Sources/Login/LoginView.swift rename to Application/DevLogUI/Sources/Login/LoginView.swift index b302eada..f8399a75 100644 --- a/Application/DevLogPresentation/Sources/Login/LoginView.swift +++ b/Application/DevLogUI/Sources/Login/LoginView.swift @@ -1,19 +1,23 @@ // // LoginView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 12/30/24. // import SwiftUI -import DevLogDomain +import DevLogPresentation -struct LoginView: View { +public struct LoginView: View { @Environment(\.colorScheme) var colorScheme @Environment(\.sceneWidth) var sceneWidth @State var viewModel: LoginViewModel - var body: some View { + public init(viewModel: LoginViewModel) { + self.viewModel = viewModel + } + + public var body: some View { ZStack { VStack { Spacer() diff --git a/Application/DevLogPresentation/Sources/Main/MainView.swift b/Application/DevLogUI/Sources/Main/MainView.swift similarity index 87% rename from Application/DevLogPresentation/Sources/Main/MainView.swift rename to Application/DevLogUI/Sources/Main/MainView.swift index 77287698..bd855c7c 100644 --- a/Application/DevLogPresentation/Sources/Main/MainView.swift +++ b/Application/DevLogUI/Sources/Main/MainView.swift @@ -1,15 +1,14 @@ // // MainView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 5/8/25. // import SwiftUI -import DevLogCore -import DevLogDomain +import DevLogPresentation -struct MainView: View { +public struct MainView: View { @Environment(\.horizontalSizeClass) private var horizontalSizeClass @State private var coordinator: MainViewCoordinator @State private var todoWindowCoordinator: TodoWindowCoordinator @@ -18,26 +17,28 @@ struct MainView: View { @State private var pushNotificationListViewCoordinator: PushNotificationListViewCoordinator @State private var profileViewCoordinator: ProfileViewCoordinator @Binding var selectedTab: MainTab? + private let todoViewModelFactory: TodoViewModelFactory private let windowEvent: TodoEditorWindowEvent - init( - container: DIContainer, + public init( + dependencies: MainViewDependencies, windowEvent: TodoEditorWindowEvent, selectedTab: Binding ) { - self._coordinator = State(initialValue: MainViewCoordinator(container: container)) - self._todoWindowCoordinator = State(initialValue: TodoWindowCoordinator(container: container)) - self._homeViewCoordinator = State(initialValue: HomeViewCoordinator(container: container)) - self._todayViewCoordinator = State(initialValue: TodayViewCoordinator(container: container)) + self._coordinator = State(initialValue: dependencies.coordinator) + self._todoWindowCoordinator = State(initialValue: dependencies.todoWindowCoordinator) + self._homeViewCoordinator = State(initialValue: dependencies.homeViewCoordinator) + self._todayViewCoordinator = State(initialValue: dependencies.todayViewCoordinator) self._pushNotificationListViewCoordinator = State( - initialValue: PushNotificationListViewCoordinator(container: container) + initialValue: dependencies.pushNotificationListViewCoordinator ) - self._profileViewCoordinator = State(initialValue: ProfileViewCoordinator(container: container)) + self._profileViewCoordinator = State(initialValue: dependencies.profileViewCoordinator) + self.todoViewModelFactory = dependencies.todoViewModelFactory self.windowEvent = windowEvent self._selectedTab = selectedTab } - var body: some View { + public var body: some View { Group { if let selectedTab { if isCompactLayout { @@ -132,7 +133,8 @@ struct MainView: View { } content: { PushNotificationListView( coordinator: pushNotificationListViewCoordinator, - isCompactLayout: isCompactLayout + isCompactLayout: isCompactLayout, + todoViewModelFactory: todoViewModelFactory ) .navigationSplitViewColumnWidth(min: 350, ideal: 450, max: nil) } detail: { @@ -141,7 +143,8 @@ struct MainView: View { TodoDetailView( viewModel: pushNotificationListViewCoordinator.makeTodoDetailViewModel( todoId: todoId - ) + ), + todoViewModelFactory: todoViewModelFactory ) .id(todoId) } else { @@ -215,7 +218,8 @@ struct MainView: View { private var homeContentView: some View { HomeView( coordinator: homeViewCoordinator, - isCompactLayout: isCompactLayout + isCompactLayout: isCompactLayout, + todoViewModelFactory: todoViewModelFactory ) } @@ -244,11 +248,15 @@ struct MainView: View { switch homeRoute { case .category(let item): TodoListView( - viewModel: todoWindowCoordinator.makeListViewModel(category: item.todoCategory) + viewModel: todoWindowCoordinator.makeListViewModel(category: item.todoCategory), + todoViewModelFactory: todoViewModelFactory ) .id(item.id) case .todo(let item): - TodoDetailView(viewModel: todoWindowCoordinator.makeDetailViewModel(todoId: item.id)) + TodoDetailView( + viewModel: todoWindowCoordinator.makeDetailViewModel(todoId: item.id), + todoViewModelFactory: todoViewModelFactory + ) .id(item.id) case .webPage(let item): WebView(url: item.url) @@ -311,7 +319,10 @@ struct MainView: View { private func todayDestinationView(_ todayRoute: TodayRoute) -> some View { switch todayRoute { case .todo(let item): - TodoDetailView(viewModel: todoWindowCoordinator.makeDetailViewModel(todoId: item.id)) + TodoDetailView( + viewModel: todoWindowCoordinator.makeDetailViewModel(todoId: item.id), + todoViewModelFactory: todoViewModelFactory + ) .id(item.id) } } @@ -319,14 +330,16 @@ struct MainView: View { private var notificationView: some View { PushNotificationListView( coordinator: pushNotificationListViewCoordinator, - isCompactLayout: isCompactLayout + isCompactLayout: isCompactLayout, + todoViewModelFactory: todoViewModelFactory ) } private var profileView: some View { ProfileView( coordinator: profileViewCoordinator, - isCompactLayout: isCompactLayout + isCompactLayout: isCompactLayout, + todoViewModelFactory: todoViewModelFactory ) } @@ -356,7 +369,10 @@ struct MainView: View { private func profileRegularDestinationView(_ route: ProfileRoute) -> some View { switch route { case .activity(let todoId): - TodoDetailView(viewModel: profileViewCoordinator.makeTodoDetailViewModel(todoId: todoId)) + TodoDetailView( + viewModel: profileViewCoordinator.makeTodoDetailViewModel(todoId: todoId), + todoViewModelFactory: todoViewModelFactory + ) .id(todoId) case .settings: SettingView(viewModel: profileViewCoordinator.settingViewModel) diff --git a/Application/DevLogPresentation/Sources/Profile/HeatmapView.swift b/Application/DevLogUI/Sources/Profile/HeatmapView.swift similarity index 91% rename from Application/DevLogPresentation/Sources/Profile/HeatmapView.swift rename to Application/DevLogUI/Sources/Profile/HeatmapView.swift index 40082cae..635afea8 100644 --- a/Application/DevLogPresentation/Sources/Profile/HeatmapView.swift +++ b/Application/DevLogUI/Sources/Profile/HeatmapView.swift @@ -1,17 +1,17 @@ // // HeatmapView.swift -// DevLogPresentation +// DevLogUI // // Created by 최윤진 on 3/2/26. // import SwiftUI -import DevLogCore +import DevLogPresentation struct HeatmapView: View { @State private var availableWidth = CGFloat.zero let quarter: HeatmapQuarter - let selectedActivityKinds: Set + let selectedActivityKindItems: Set let selectedDay: HeatmapDay? let onSelectDay: (HeatmapDay) -> Void @@ -28,7 +28,7 @@ struct HeatmapView: View { month: month, maxCount: maxCount, layout: layout, - selectedActivityKinds: selectedActivityKinds, + selectedActivityKindItems: selectedActivityKindItems, selectedDay: selectedDay, onSelectDay: onSelectDay ) @@ -60,13 +60,13 @@ struct HeatmapView: View { private func dayCount(for day: HeatmapDay) -> Int { var value = 0 - if selectedActivityKinds.contains(.created) { + if selectedActivityKindItems.contains(.created) { value += day.createdCount } - if selectedActivityKinds.contains(.completed) { + if selectedActivityKindItems.contains(.completed) { value += day.completedCount } - if selectedActivityKinds.contains(.deleted) { + if selectedActivityKindItems.contains(.deleted) { value += day.deletedCount } return value @@ -114,7 +114,7 @@ private struct MonthCompactHeatmapView: View { let month: HeatmapMonth let maxCount: Int let layout: HeatmapLayout - let selectedActivityKinds: Set + let selectedActivityKindItems: Set let selectedDay: HeatmapDay? let onSelectDay: (HeatmapDay) -> Void private let orderedWeekdays = Array(1...7) @@ -185,13 +185,13 @@ private struct MonthCompactHeatmapView: View { private func dayCount(for day: HeatmapDay) -> Int { var value = 0 - if selectedActivityKinds.contains(.created) { + if selectedActivityKindItems.contains(.created) { value += day.createdCount } - if selectedActivityKinds.contains(.completed) { + if selectedActivityKindItems.contains(.completed) { value += day.completedCount } - if selectedActivityKinds.contains(.deleted) { + if selectedActivityKindItems.contains(.deleted) { value += day.deletedCount } return value diff --git a/Application/DevLogPresentation/Sources/Profile/ProfileView.swift b/Application/DevLogUI/Sources/Profile/ProfileView.swift similarity index 92% rename from Application/DevLogPresentation/Sources/Profile/ProfileView.swift rename to Application/DevLogUI/Sources/Profile/ProfileView.swift index 25727f13..33f36769 100644 --- a/Application/DevLogPresentation/Sources/Profile/ProfileView.swift +++ b/Application/DevLogUI/Sources/Profile/ProfileView.swift @@ -1,17 +1,17 @@ // // ProfileView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 5/7/25. // import SwiftUI -import DevLogCore -import DevLogDomain +import DevLogPresentation struct ProfileView: View { let coordinator: ProfileViewCoordinator let isCompactLayout: Bool + let todoViewModelFactory: TodoViewModelFactory @FocusState private var focused: Bool var body: some View { @@ -151,7 +151,10 @@ struct ProfileView: View { SettingView(viewModel: coordinator.settingViewModel) .environment(coordinator.router) case .activity(let todoId): - TodoDetailView(viewModel: coordinator.makeTodoDetailViewModel(todoId: todoId)) + TodoDetailView( + viewModel: coordinator.makeTodoDetailViewModel(todoId: todoId), + todoViewModelFactory: todoViewModelFactory + ) case .theme: ThemeView( theme: Binding( @@ -181,7 +184,7 @@ struct ProfileView: View { if let quarter = coordinator.viewModel.state.activityQuarter { HeatmapView( quarter: quarter, - selectedActivityKinds: coordinator.viewModel.state.selectedActivityKinds, + selectedActivityKindItems: coordinator.viewModel.selectedActivityKindItems, selectedDay: coordinator.viewModel.state.selectedDay, onSelectDay: { coordinator.viewModel.send(.selectDay($0)) } ) @@ -223,26 +226,14 @@ struct ProfileView: View { activityKindItem.title, isOn: Binding( get: { - guard let activityKind = ActivityKind(rawValue: activityKindItem.rawValue) else { - return false - } - return coordinator.viewModel.state.selectedActivityKinds.contains(activityKind) + coordinator.viewModel.isSelected(activityKindItem) }, set: { _ in - guard let activityKind = ActivityKind(rawValue: activityKindItem.rawValue) else { - return - } - coordinator.viewModel.send(.toggleActivityKind(activityKind)) + coordinator.viewModel.toggle(activityKindItem) } ) ) - .disabled({ - guard let activityKind = ActivityKind(rawValue: activityKindItem.rawValue) else { - return false - } - return coordinator.viewModel.state.selectedActivityKinds.count == 1 - && coordinator.viewModel.state.selectedActivityKinds.contains(activityKind) - }()) + .disabled(coordinator.viewModel.isOnlySelected(activityKindItem)) } } label: { Image(systemName: "line.3.horizontal.decrease") @@ -431,11 +422,3 @@ struct ProfileView: View { ) } } - -enum ProfileRoute: Hashable { - case settings - case activity(String) - case theme - case pushNotification - case account -} diff --git a/Application/DevLogPresentation/Sources/PushNotification/PushNotificationListView.swift b/Application/DevLogUI/Sources/PushNotification/PushNotificationListView.swift similarity index 97% rename from Application/DevLogPresentation/Sources/PushNotification/PushNotificationListView.swift rename to Application/DevLogUI/Sources/PushNotification/PushNotificationListView.swift index a33d41d9..f61211f4 100644 --- a/Application/DevLogPresentation/Sources/PushNotification/PushNotificationListView.swift +++ b/Application/DevLogUI/Sources/PushNotification/PushNotificationListView.swift @@ -1,13 +1,12 @@ // // PushNotificationListView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 5/14/25. // import SwiftUI -import DevLogCore -import DevLogDomain +import DevLogPresentation struct PushNotificationListView: View { @Environment(\.colorScheme) private var colorScheme @@ -17,6 +16,7 @@ struct PushNotificationListView: View { @State private var isScrollTrackingEnabled = false let coordinator: PushNotificationListViewCoordinator let isCompactLayout: Bool + let todoViewModelFactory: TodoViewModelFactory private var viewModel: PushNotificationListViewModel { coordinator.viewModel @@ -67,7 +67,10 @@ struct PushNotificationListView: View { } )) { item in NavigationStack { - TodoDetailView(viewModel: coordinator.makeTodoDetailViewModel(todoId: item.id)) + TodoDetailView( + viewModel: coordinator.makeTodoDetailViewModel(todoId: item.id), + todoViewModelFactory: todoViewModelFactory + ) .id(item.id) .toolbar { ToolbarLeadingButton { @@ -241,7 +244,7 @@ struct PushNotificationListView: View { get: { viewModel.state.query.timeFilter }, set: { viewModel.send(.setTimeFilter($0)) } )) { - ForEach(PushNotificationQuery.TimeFilter.availableOptions, id: \.self) { option in + ForEach(PushNotificationListViewModel.TimeFilter.availableOptions, id: \.self) { option in Text(option.title).tag(option) } } label: { diff --git a/Application/DevLogPresentation/Sources/Search/SearchView.swift b/Application/DevLogUI/Sources/Search/SearchView.swift similarity index 94% rename from Application/DevLogPresentation/Sources/Search/SearchView.swift rename to Application/DevLogUI/Sources/Search/SearchView.swift index 27616391..717d69ae 100644 --- a/Application/DevLogPresentation/Sources/Search/SearchView.swift +++ b/Application/DevLogUI/Sources/Search/SearchView.swift @@ -1,19 +1,18 @@ // // SearchView.swift -// DevLogPresentation +// DevLogUI // // Created by 최윤진 on 2/12/26. // import SwiftUI -import DevLogCore -import DevLogDomain +import DevLogPresentation struct SearchView: View { @Environment(\.dismiss) private var dismiss - @Environment(\.diContainer) private var container: DIContainer @State private var router = NavigationRouter() @State var viewModel: SearchViewModel + let todoViewModelFactory: TodoViewModelFactory var body: some View { NavigationStack(path: $router.path) { @@ -21,11 +20,10 @@ struct SearchView: View { .navigationDestination(for: Path.self) { path in switch path { case .todo(let todoId): - TodoDetailView(viewModel: TodoDetailViewModel( - fetchTodoUseCase: container.resolve(FetchTodoByIdUseCase.self), - fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self), - todoId: todoId - )) + TodoDetailView( + viewModel: todoViewModelFactory.makeDetailViewModel(todoId: todoId), + todoViewModelFactory: todoViewModelFactory + ) case .web(let page): WebView(url: page.url) .ignoresSafeArea() diff --git a/Application/DevLogPresentation/Sources/Setting/AccountView.swift b/Application/DevLogUI/Sources/Setting/AccountView.swift similarity index 90% rename from Application/DevLogPresentation/Sources/Setting/AccountView.swift rename to Application/DevLogUI/Sources/Setting/AccountView.swift index 87602858..dfbb3159 100644 --- a/Application/DevLogPresentation/Sources/Setting/AccountView.swift +++ b/Application/DevLogUI/Sources/Setting/AccountView.swift @@ -1,12 +1,12 @@ // // AccountView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 5/14/25. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct AccountView: View { @State var viewModel: AccountViewModel @@ -21,7 +21,7 @@ struct AccountView: View { } } Section(String(localized: "account_social_section")) { - let providers = AuthProvider.allCases.filter { $0 != viewModel.state.currentProvider } + let providers = AccountViewModel.Provider.allCases.filter { $0 != viewModel.state.currentProvider } ForEach(providers, id: \.self) { provider in let isConnected = viewModel.state.connectedProviders.contains(provider) HStack { @@ -76,7 +76,7 @@ struct AccountView: View { } } - private func formattedProviderName(_ provider: AuthProvider) -> String { + private func formattedProviderName(_ provider: AccountViewModel.Provider) -> String { let rawValue = provider.rawValue let providerPrefix = rawValue.prefix(1).uppercased() let providerSuffix = rawValue.dropFirst().prefix(while: { $0 != "." }) @@ -84,7 +84,7 @@ struct AccountView: View { } @ViewBuilder - private func providerContent(_ provider: AuthProvider) -> some View { + private func providerContent(_ provider: AccountViewModel.Provider) -> some View { Image(formattedProviderName(provider)) .resizable() .scaledToFit() diff --git a/Application/DevLogPresentation/Sources/Setting/PushNotificationSettingsView.swift b/Application/DevLogUI/Sources/Setting/PushNotificationSettingsView.swift similarity index 99% rename from Application/DevLogPresentation/Sources/Setting/PushNotificationSettingsView.swift rename to Application/DevLogUI/Sources/Setting/PushNotificationSettingsView.swift index 3ec43a07..b99898c6 100644 --- a/Application/DevLogPresentation/Sources/Setting/PushNotificationSettingsView.swift +++ b/Application/DevLogUI/Sources/Setting/PushNotificationSettingsView.swift @@ -1,12 +1,12 @@ // // PushNotificationSettingsView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 5/14/25. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct PushNotificationSettingsView: View { @State var viewModel: PushNotificationSettingsViewModel diff --git a/Application/DevLogPresentation/Sources/Setting/SettingView.swift b/Application/DevLogUI/Sources/Setting/SettingView.swift similarity index 99% rename from Application/DevLogPresentation/Sources/Setting/SettingView.swift rename to Application/DevLogUI/Sources/Setting/SettingView.swift index 91bd74e7..103e1efc 100644 --- a/Application/DevLogPresentation/Sources/Setting/SettingView.swift +++ b/Application/DevLogUI/Sources/Setting/SettingView.swift @@ -1,12 +1,12 @@ // // SettingView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 5/6/25. // import SwiftUI -import DevLogDomain +import DevLogPresentation struct SettingView: View { @Environment(NavigationRouter.self) private var router diff --git a/Application/DevLogPresentation/Sources/Setting/ThemeView.swift b/Application/DevLogUI/Sources/Setting/ThemeView.swift similarity index 82% rename from Application/DevLogPresentation/Sources/Setting/ThemeView.swift rename to Application/DevLogUI/Sources/Setting/ThemeView.swift index a939fdc7..fbb6d0db 100644 --- a/Application/DevLogPresentation/Sources/Setting/ThemeView.swift +++ b/Application/DevLogUI/Sources/Setting/ThemeView.swift @@ -1,16 +1,15 @@ // // ThemeView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 5/6/25. // import SwiftUI -import DevLogCore -import DevLogDomain +import DevLogPresentation struct ThemeView: View { - @Binding var theme: SystemTheme + @Binding var theme: SettingViewModel.Theme var body: some View { List { @@ -18,7 +17,7 @@ struct ThemeView: View { theme = .automatic }) { HStack { - Text(SystemTheme.automatic.localizedName) + Text(SettingViewModel.Theme.automatic.localizedName) .foregroundStyle(Color.primary) Spacer() if theme == .automatic { @@ -30,7 +29,7 @@ struct ThemeView: View { theme = .light }) { HStack { - Text(SystemTheme.light.localizedName) + Text(SettingViewModel.Theme.light.localizedName) .foregroundStyle(Color.primary) Spacer() if theme == .light { @@ -42,7 +41,7 @@ struct ThemeView: View { theme = .dark }) { HStack { - Text(SystemTheme.dark.localizedName) + Text(SettingViewModel.Theme.dark.localizedName) .foregroundStyle(Color.primary) Spacer() if theme == .dark { diff --git a/Application/DevLogPresentation/Sources/Today/TodayView.swift b/Application/DevLogUI/Sources/Today/TodayView.swift similarity index 97% rename from Application/DevLogPresentation/Sources/Today/TodayView.swift rename to Application/DevLogUI/Sources/Today/TodayView.swift index 7db59ef9..f98e68a3 100644 --- a/Application/DevLogPresentation/Sources/Today/TodayView.swift +++ b/Application/DevLogUI/Sources/Today/TodayView.swift @@ -1,13 +1,12 @@ // // TodayView.swift -// DevLogPresentation +// DevLogUI // // Created by opfic on 3/6/26. // import SwiftUI -import DevLogCore -import DevLogDomain +import DevLogPresentation struct TodayView: View { let coordinator: TodayViewCoordinator @@ -85,7 +84,7 @@ struct TodayView: View { set: { coordinator.viewModel.send(.setDueDateVisibility($0)) } ) ) { - ForEach(TodayDisplayOptions.DueDateVisibility.allCases, id: \.self) { option in + ForEach(TodayViewModel.DueDateVisibility.allCases, id: \.self) { option in Text(option.title).tag(option) } } @@ -212,11 +211,7 @@ struct TodayView: View { } } -enum TodayRoute: Hashable { - case todo(TodoIdItem) -} - -private extension TodayDisplayOptions.DueDateVisibility { +private extension TodayViewModel.DueDateVisibility { var title: String { switch self { case .all: diff --git a/DevLog.xcworkspace/contents.xcworkspacedata b/DevLog.xcworkspace/contents.xcworkspacedata index 3f7e3c4b..506c34f3 100644 --- a/DevLog.xcworkspace/contents.xcworkspacedata +++ b/DevLog.xcworkspace/contents.xcworkspacedata @@ -25,6 +25,9 @@ + + - App Architecture + App Architecture @@ -181,7 +181,8 @@ SwiftUI_DevLog/ │ ├── DevLogData/ # Repository 구현, DTO, Mapper, Data 계층 Protocol │ ├── DevLogInfra/ # Firebase, 소셜 로그인, 네트워크, 메타데이터 서비스 구현 │ ├── DevLogPersistence/ # UserDefaults, 이미지 저장소, 위젯 스냅샷 영속성 처리 -│ └── DevLogPresentation/ # SwiftUI 화면, ViewModel, Store, Coordinator +│ ├── DevLogPresentation/ # ViewModel, Store, Coordinator, 화면 상태 +│ └── DevLogUI/ # SwiftUI 화면, 재사용 UI 컴포넌트 ├── Widget/ │ ├── DevLogWidgetCore/ # 위젯 스냅샷 모델, Factory, App Group 상수 │ └── DevLogWidgetExtension/ # WidgetKit UI, Provider, Timeline diff --git a/Workspace.swift b/Workspace.swift index afbf038b..2718b849 100644 --- a/Workspace.swift +++ b/Workspace.swift @@ -10,6 +10,7 @@ let workspace = Workspace( "Application/DevLogInfra", "Application/DevLogPersistence", "Application/DevLogPresentation", + "Application/DevLogUI", "Widget/DevLogWidgetCore", "Widget/DevLogWidgetExtension", ], diff --git a/docs/App.png b/docs/App.png deleted file mode 100644 index 1980a0a2..00000000 Binary files a/docs/App.png and /dev/null differ diff --git a/docs/DevLog.drawio b/docs/DevLog.drawio index ee88c1db..00b9cc1a 100644 --- a/docs/DevLog.drawio +++ b/docs/DevLog.drawio @@ -1,4 +1,4 @@ - + @@ -565,186 +565,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/graph.png b/docs/graph.png new file mode 100644 index 00000000..a71c0bd1 Binary files /dev/null and b/docs/graph.png differ