diff --git a/Application/Presentation/Project.swift b/Application/Presentation/Project.swift index 6aa1d99d..67963643 100644 --- a/Application/Presentation/Project.swift +++ b/Application/Presentation/Project.swift @@ -34,7 +34,7 @@ let project = Project( .target( name: "PresentationShared", destinations: .iOS, - product: .staticFramework, + product: .framework, bundleId: "com.opfic.DevLog.PresentationShared", infoPlist: .file(path: frameworkInfoPlistPath), sources: ["PresentationShared/Sources/**/*.swift"], @@ -97,6 +97,50 @@ let project = Project( ] ) ), + .target( + name: "TodayTab", + destinations: .iOS, + product: .staticFramework, + bundleId: "com.opfic.DevLog.TodayTab", + infoPlist: .file(path: frameworkInfoPlistPath), + sources: ["TodayTab/Sources/**/*.swift"], + scripts: [ + DevLogScripts.swiftLint( + sourcePath: "TodayTab/Sources", + configPath: "TodayTab/Sources/.swiftlint.yml" + ) + ], + dependencies: [ + .project(target: "Domain", path: "../Domain"), + .project(target: "Core", path: "../Core"), + .target(name: "PresentationShared") + ], + settings: frameworkBuildSettings + ), + .target( + name: "TodayTabTests", + destinations: .iOS, + product: .unitTests, + bundleId: "com.opfic.DevLog.TodayTabTests", + infoPlist: .file(path: testsInfoPlistPath), + sources: ["TodayTab/Tests/**/*.swift"], + scripts: [ + DevLogScripts.swiftLint( + sourcePath: "TodayTab/Tests", + configPath: "TodayTab/Tests/.swiftlint.yml" + ) + ], + dependencies: [ + .target(name: "TodayTab"), + .target(name: "PresentationShared") + ], + settings: .devlog( + base: [ + "ENABLE_USER_SCRIPT_SANDBOXING": "NO", + "TEST_TARGET_NAME": "TodayTab" + ] + ) + ), .target( name: "Presentation", destinations: .iOS, @@ -114,6 +158,7 @@ let project = Project( .project(target: "Domain", path: "../Domain"), .project(target: "Core", path: "../Core"), .target(name: "HomeTab"), + .target(name: "TodayTab"), .target(name: "PresentationShared") ], settings: frameworkBuildSettings diff --git a/Application/Presentation/Sources/Main/MainView.swift b/Application/Presentation/Sources/Main/MainView.swift index c6dbc034..ca098d4a 100644 --- a/Application/Presentation/Sources/Main/MainView.swift +++ b/Application/Presentation/Sources/Main/MainView.swift @@ -10,6 +10,7 @@ import Core import Domain import HomeTab import PresentationShared +import TodayTab struct MainView: View { @Environment(\.horizontalSizeClass) private var horizontalSizeClass diff --git a/Application/Presentation/TodayTab/Sources/.swiftlint.yml b/Application/Presentation/TodayTab/Sources/.swiftlint.yml new file mode 100644 index 00000000..6a9d7380 --- /dev/null +++ b/Application/Presentation/TodayTab/Sources/.swiftlint.yml @@ -0,0 +1 @@ +parent_config: ../../../../.swiftlint.yml diff --git a/Application/Presentation/Sources/Today/TodayFeature+State.swift b/Application/Presentation/TodayTab/Sources/Today/TodayFeature+State.swift similarity index 100% rename from Application/Presentation/Sources/Today/TodayFeature+State.swift rename to Application/Presentation/TodayTab/Sources/Today/TodayFeature+State.swift diff --git a/Application/Presentation/Sources/Today/TodayFeature.swift b/Application/Presentation/TodayTab/Sources/Today/TodayFeature.swift similarity index 100% rename from Application/Presentation/Sources/Today/TodayFeature.swift rename to Application/Presentation/TodayTab/Sources/Today/TodayFeature.swift diff --git a/Application/Presentation/TodayTab/Sources/Today/TodayRoute.swift b/Application/Presentation/TodayTab/Sources/Today/TodayRoute.swift new file mode 100644 index 00000000..db9a6447 --- /dev/null +++ b/Application/Presentation/TodayTab/Sources/Today/TodayRoute.swift @@ -0,0 +1,12 @@ +// +// TodayRoute.swift +// Presentation +// +// Created by opfic on 7/5/26. +// + +import PresentationShared + +public enum TodayRoute: Hashable { + case todo(TodoIdItem) +} diff --git a/Application/Presentation/Sources/Structure/Todo/TodayTodoItem.swift b/Application/Presentation/TodayTab/Sources/Today/TodayTodoItem.swift similarity index 100% rename from Application/Presentation/Sources/Structure/Todo/TodayTodoItem.swift rename to Application/Presentation/TodayTab/Sources/Today/TodayTodoItem.swift diff --git a/Application/Presentation/Sources/Today/TodayView.swift b/Application/Presentation/TodayTab/Sources/Today/TodayView.swift similarity index 99% rename from Application/Presentation/Sources/Today/TodayView.swift rename to Application/Presentation/TodayTab/Sources/Today/TodayView.swift index 20389a32..fd914d2d 100644 --- a/Application/Presentation/Sources/Today/TodayView.swift +++ b/Application/Presentation/TodayTab/Sources/Today/TodayView.swift @@ -10,12 +10,12 @@ import Core import Domain import PresentationShared -struct TodayView: View { +public struct TodayView: View { @Bindable var store: StoreOf let coordinator: TodayViewCoordinator let isCompactLayout: Bool - init( + public init( coordinator: TodayViewCoordinator, isCompactLayout: Bool ) { @@ -24,7 +24,7 @@ struct TodayView: View { self.store = coordinator.store } - var body: some View { + public var body: some View { List { summarySection if store.sections.isEmpty, !store.isLoading { @@ -208,10 +208,6 @@ struct TodayView: View { } } -enum TodayRoute: Hashable { - case todo(TodoIdItem) -} - private extension TodayDisplayOptions.DueDateVisibility { var title: String { switch self { diff --git a/Application/Presentation/Sources/Today/TodayViewCoordinator.swift b/Application/Presentation/TodayTab/Sources/Today/TodayViewCoordinator.swift similarity index 86% rename from Application/Presentation/Sources/Today/TodayViewCoordinator.swift rename to Application/Presentation/TodayTab/Sources/Today/TodayViewCoordinator.swift index 8c8d0f28..f966ad09 100644 --- a/Application/Presentation/Sources/Today/TodayViewCoordinator.swift +++ b/Application/Presentation/TodayTab/Sources/Today/TodayViewCoordinator.swift @@ -12,11 +12,11 @@ import PresentationShared @MainActor @Observable -final class TodayViewCoordinator { +public final class TodayViewCoordinator { let store: StoreOf - let router = NavigationRouter() + public let router = NavigationRouter() - init(container: DIContainer) { + public init(container: DIContainer) { let fetchDisplayOptionsUseCase = container.resolve(FetchTodayDisplayOptionsUseCase.self) self.store = Store( initialState: TodayFeature.State( @@ -33,7 +33,7 @@ final class TodayViewCoordinator { } } - func fetchData() { + public func fetchData() { store.send(.fetchData) } } diff --git a/Application/Presentation/TodayTab/Tests/.swiftlint.yml b/Application/Presentation/TodayTab/Tests/.swiftlint.yml new file mode 100644 index 00000000..7bb38c07 --- /dev/null +++ b/Application/Presentation/TodayTab/Tests/.swiftlint.yml @@ -0,0 +1 @@ +parent_config: ../../../../.swiftlint-tests.yml diff --git a/Application/Presentation/Tests/Today/TodayFeatureTestAssertions.swift b/Application/Presentation/TodayTab/Tests/Today/TodayFeatureTestAssertions.swift similarity index 99% rename from Application/Presentation/Tests/Today/TodayFeatureTestAssertions.swift rename to Application/Presentation/TodayTab/Tests/Today/TodayFeatureTestAssertions.swift index 7bd36306..412a16f4 100644 --- a/Application/Presentation/Tests/Today/TodayFeatureTestAssertions.swift +++ b/Application/Presentation/TodayTab/Tests/Today/TodayFeatureTestAssertions.swift @@ -8,7 +8,7 @@ import Testing import Core import Domain -@testable import Presentation +@testable import TodayTab @MainActor private func waitUntilTodayMainActor( diff --git a/Application/Presentation/Tests/Today/TodayFeatureTestDoubles.swift b/Application/Presentation/TodayTab/Tests/Today/TodayFeatureTestDoubles.swift similarity index 99% rename from Application/Presentation/Tests/Today/TodayFeatureTestDoubles.swift rename to Application/Presentation/TodayTab/Tests/Today/TodayFeatureTestDoubles.swift index edb349a1..f7652257 100644 --- a/Application/Presentation/Tests/Today/TodayFeatureTestDoubles.swift +++ b/Application/Presentation/TodayTab/Tests/Today/TodayFeatureTestDoubles.swift @@ -9,7 +9,7 @@ import Foundation import PresentationShared import Core import Domain -@testable import Presentation +@testable import TodayTab enum TodayTestError: Error { case failure diff --git a/Application/Presentation/Tests/Today/TodayFeatureTestSpies.swift b/Application/Presentation/TodayTab/Tests/Today/TodayFeatureTestSpies.swift similarity index 99% rename from Application/Presentation/Tests/Today/TodayFeatureTestSpies.swift rename to Application/Presentation/TodayTab/Tests/Today/TodayFeatureTestSpies.swift index e734c9a2..284f7714 100644 --- a/Application/Presentation/Tests/Today/TodayFeatureTestSpies.swift +++ b/Application/Presentation/TodayTab/Tests/Today/TodayFeatureTestSpies.swift @@ -8,7 +8,7 @@ import Foundation import Core import Domain -@testable import Presentation +@testable import TodayTab final class TodayFetchTodosUseCaseSpy: FetchTodosUseCase { var pagesByFilter: [TodoQuery.DueDateFilter: TodoPage] diff --git a/Application/Presentation/Tests/Today/TodayFeatureTests.swift b/Application/Presentation/TodayTab/Tests/Today/TodayFeatureTests.swift similarity index 99% rename from Application/Presentation/Tests/Today/TodayFeatureTests.swift rename to Application/Presentation/TodayTab/Tests/Today/TodayFeatureTests.swift index 8403e35b..0441087e 100644 --- a/Application/Presentation/Tests/Today/TodayFeatureTests.swift +++ b/Application/Presentation/TodayTab/Tests/Today/TodayFeatureTests.swift @@ -8,7 +8,7 @@ import Testing import Foundation import Core -@testable import Presentation +@testable import TodayTab @MainActor struct TodayFeatureTests { diff --git a/README.md b/README.md index 5e620f71..aa5e77ac 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Todo, 저장 링크, 오늘 할 일, 받은 알림, 누적 활동을 하나의 ## 아키텍처 `DevLog.xcworkspace` 안에서 Application, Widget 모듈을 분리하고 화면, 상태, 비즈니스 로직, 외부 의존성 경계를 나눈 `Clean Architecture` 기반 구성 +`Presentation`은 tab shell/root composition을 유지하고, 공통 UI 흐름은 `PresentationShared`, 탭 단위 흐름은 `HomeTab`, `TodayTab` target이 소유함 @@ -237,7 +238,11 @@ DevLog_iOS/ │ ├── Data/ # Repository 구현, DTO, Mapper, Data 계층 Protocol │ ├── Infra/ # Firebase, 소셜 로그인, 네트워크, 메타데이터 서비스 구현 │ ├── Persistence/ # UserDefaults, 이미지 저장소, 앱 로컬 영속성 처리 -│ ├── Presentation/ # SwiftUI 화면, ViewModel, Store, Coordinator +│ ├── Presentation/ # SwiftUI tab shell, root composition +│ │ ├── PresentationShared/ # 공통 UI 흐름, 공통 presentation structure +│ │ ├── HomeTab/ # Home 탭 화면, feature, coordinator, 테스트 +│ │ ├── TodayTab/ # Today 탭 화면, feature, coordinator, 테스트 +│ │ └── Sources/ # Main, Root, Settings 등 앱 presentation shell │ └── Widget/ # 앱-위젯 브릿지, 위젯 동기화 이벤트, 스냅샷 갱신 ├── Widget/ │ ├── WidgetCore/ # 위젯 스냅샷 모델, Factory, App Group 상수 diff --git a/docs/graph.png b/docs/graph.png index 2b12e9bd..057b08a5 100644 Binary files a/docs/graph.png and b/docs/graph.png differ