diff --git a/Application/Presentation/NotificationTab/Sources/.swiftlint.yml b/Application/Presentation/NotificationTab/Sources/.swiftlint.yml new file mode 100644 index 00000000..6a9d7380 --- /dev/null +++ b/Application/Presentation/NotificationTab/Sources/.swiftlint.yml @@ -0,0 +1 @@ +parent_config: ../../../../.swiftlint.yml diff --git a/Application/Presentation/Sources/Structure/PushNotificationItem.swift b/Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationItem.swift similarity index 93% rename from Application/Presentation/Sources/Structure/PushNotificationItem.swift rename to Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationItem.swift index 0e163549..ca561d7c 100644 --- a/Application/Presentation/Sources/Structure/PushNotificationItem.swift +++ b/Application/Presentation/NotificationTab/Sources/PushNotification/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/Presentation/Sources/PushNotification/PushNotificationListFeature.swift b/Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationListFeature.swift similarity index 100% rename from Application/Presentation/Sources/PushNotification/PushNotificationListFeature.swift rename to Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationListFeature.swift diff --git a/Application/Presentation/Sources/PushNotification/PushNotificationListFeatureDependencies.swift b/Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationListFeatureDependencies.swift similarity index 100% rename from Application/Presentation/Sources/PushNotification/PushNotificationListFeatureDependencies.swift rename to Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationListFeatureDependencies.swift diff --git a/Application/Presentation/Sources/PushNotification/PushNotificationListView.swift b/Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationListView.swift similarity index 99% rename from Application/Presentation/Sources/PushNotification/PushNotificationListView.swift rename to Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationListView.swift index 31358dc6..cddd6a96 100644 --- a/Application/Presentation/Sources/PushNotification/PushNotificationListView.swift +++ b/Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationListView.swift @@ -9,7 +9,7 @@ import SwiftUI import Core import PresentationShared -struct PushNotificationListView: View { +public struct PushNotificationListView: View { @Environment(\.colorScheme) private var colorScheme @ScaledMetric(relativeTo: .body) private var headerHeight = 41 @ScaledMetric(relativeTo: .largeTitle) private var labelWidth = 34 @@ -19,7 +19,7 @@ struct PushNotificationListView: View { let coordinator: PushNotificationListViewCoordinator let isCompactLayout: Bool - init( + public init( coordinator: PushNotificationListViewCoordinator, isCompactLayout: Bool ) { @@ -28,7 +28,7 @@ struct PushNotificationListView: View { self.store = coordinator.store } - var body: some View { + public var body: some View { NavigationStack { notificationListContent .background(Color(.systemGroupedBackground)) diff --git a/Application/Presentation/Sources/PushNotification/PushNotificationListViewCoordinator.swift b/Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationListViewCoordinator.swift similarity index 87% rename from Application/Presentation/Sources/PushNotification/PushNotificationListViewCoordinator.swift rename to Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationListViewCoordinator.swift index 5372d5f8..38e4027e 100644 --- a/Application/Presentation/Sources/PushNotification/PushNotificationListViewCoordinator.swift +++ b/Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationListViewCoordinator.swift @@ -12,13 +12,13 @@ import PresentationShared @MainActor @Observable -final class PushNotificationListViewCoordinator { +public final class PushNotificationListViewCoordinator { let store: StoreOf private let container: DIContainer @ObservationIgnored private var todoDetailStore: StoreOf? - init(container: DIContainer) { + public init(container: DIContainer) { self.container = container let fetchQueryUseCase = container.resolve(FetchPushNotificationQueryUseCase.self) @@ -37,11 +37,15 @@ final class PushNotificationListViewCoordinator { } } - func fetchData() { + public var selectedTodoId: String? { + store.selectedTodoId?.id + } + + public func fetchData() { store.send(.view(.fetchNotifications)) } - func makeTodoDetailStore(todoId: String) -> StoreOf { + public func makeTodoDetailStore(todoId: String) -> StoreOf { if let todoDetailStore, todoDetailStore.todoId == todoId, !todoDetailStore.showEditButton { diff --git a/Application/Presentation/Sources/Structure/PushNotificationQuery+Presentation.swift b/Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationQuery+Presentation.swift similarity index 91% rename from Application/Presentation/Sources/Structure/PushNotificationQuery+Presentation.swift rename to Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationQuery+Presentation.swift index 3c3f2eae..ec7f418e 100644 --- a/Application/Presentation/Sources/Structure/PushNotificationQuery+Presentation.swift +++ b/Application/Presentation/NotificationTab/Sources/PushNotification/PushNotificationQuery+Presentation.swift @@ -8,7 +8,7 @@ import Core import Foundation -extension PushNotificationQuery.SortOrder { +public extension PushNotificationQuery.SortOrder { var title: String { switch self { case .latest: @@ -19,7 +19,7 @@ extension PushNotificationQuery.SortOrder { } } -extension PushNotificationQuery.TimeFilter { +public extension PushNotificationQuery.TimeFilter { var title: String { switch self { case .none: diff --git a/Application/Presentation/NotificationTab/Tests/.swiftlint.yml b/Application/Presentation/NotificationTab/Tests/.swiftlint.yml new file mode 100644 index 00000000..7bb38c07 --- /dev/null +++ b/Application/Presentation/NotificationTab/Tests/.swiftlint.yml @@ -0,0 +1 @@ +parent_config: ../../../../.swiftlint-tests.yml diff --git a/Application/Presentation/Tests/PushNotification/PushNotificationListFeatureTests.swift b/Application/Presentation/NotificationTab/Tests/PushNotification/PushNotificationListFeatureTests.swift similarity index 99% rename from Application/Presentation/Tests/PushNotification/PushNotificationListFeatureTests.swift rename to Application/Presentation/NotificationTab/Tests/PushNotification/PushNotificationListFeatureTests.swift index 2236c18b..998baccf 100644 --- a/Application/Presentation/Tests/PushNotification/PushNotificationListFeatureTests.swift +++ b/Application/Presentation/NotificationTab/Tests/PushNotification/PushNotificationListFeatureTests.swift @@ -1,13 +1,13 @@ // // PushNotificationListFeatureTests.swift -// PresentationTests +// NotificationTabTests // // Created by opfic on 6/12/26. // import Testing import Domain -@testable import Presentation +@testable import NotificationTab @MainActor struct PushNotificationListFeatureTests { diff --git a/Application/Presentation/Tests/PushNotification/PushNotificationListFixtures.swift b/Application/Presentation/NotificationTab/Tests/PushNotification/PushNotificationListFixtures.swift similarity index 96% rename from Application/Presentation/Tests/PushNotification/PushNotificationListFixtures.swift rename to Application/Presentation/NotificationTab/Tests/PushNotification/PushNotificationListFixtures.swift index a740077f..7e1008e0 100644 --- a/Application/Presentation/Tests/PushNotification/PushNotificationListFixtures.swift +++ b/Application/Presentation/NotificationTab/Tests/PushNotification/PushNotificationListFixtures.swift @@ -1,6 +1,6 @@ // // PushNotificationListFixtures.swift -// PresentationTests +// NotificationTabTests // // Created by opfic on 6/12/26. // diff --git a/Application/Presentation/Tests/PushNotification/PushNotificationListTestAssertions.swift b/Application/Presentation/NotificationTab/Tests/PushNotification/PushNotificationListTestAssertions.swift similarity index 99% rename from Application/Presentation/Tests/PushNotification/PushNotificationListTestAssertions.swift rename to Application/Presentation/NotificationTab/Tests/PushNotification/PushNotificationListTestAssertions.swift index 04af178d..75ff8ac3 100644 --- a/Application/Presentation/Tests/PushNotification/PushNotificationListTestAssertions.swift +++ b/Application/Presentation/NotificationTab/Tests/PushNotification/PushNotificationListTestAssertions.swift @@ -1,6 +1,6 @@ // // PushNotificationListTestAssertions.swift -// PresentationTests +// NotificationTabTests // // Created by opfic on 6/12/26. // @@ -10,7 +10,7 @@ import Core import Domain import PresentationShared import Foundation -@testable import Presentation +@testable import NotificationTab @MainActor func waitUntilMainActor( diff --git a/Application/Presentation/Tests/PushNotification/PushNotificationListTestSupport.swift b/Application/Presentation/NotificationTab/Tests/PushNotification/PushNotificationListTestSupport.swift similarity index 99% rename from Application/Presentation/Tests/PushNotification/PushNotificationListTestSupport.swift rename to Application/Presentation/NotificationTab/Tests/PushNotification/PushNotificationListTestSupport.swift index 0155759d..e1da0f5f 100644 --- a/Application/Presentation/Tests/PushNotification/PushNotificationListTestSupport.swift +++ b/Application/Presentation/NotificationTab/Tests/PushNotification/PushNotificationListTestSupport.swift @@ -1,6 +1,6 @@ // // PushNotificationListTestSupport.swift -// PresentationTests +// NotificationTabTests // // Created by opfic on 6/12/26. // @@ -11,7 +11,7 @@ import Core import Domain import PresentationShared import Foundation -@testable import Presentation +@testable import NotificationTab @MainActor protocol PushNotificationListStateDriving { diff --git a/Application/Presentation/Project.swift b/Application/Presentation/Project.swift index 67963643..ca97b301 100644 --- a/Application/Presentation/Project.swift +++ b/Application/Presentation/Project.swift @@ -141,6 +141,52 @@ let project = Project( ] ) ), + .target( + name: "NotificationTab", + destinations: .iOS, + product: .staticFramework, + bundleId: "com.opfic.DevLog.NotificationTab", + infoPlist: .file(path: frameworkInfoPlistPath), + sources: ["NotificationTab/Sources/**/*.swift"], + scripts: [ + DevLogScripts.swiftLint( + sourcePath: "NotificationTab/Sources", + configPath: "NotificationTab/Sources/.swiftlint.yml" + ) + ], + dependencies: [ + .project(target: "Domain", path: "../Domain"), + .project(target: "Core", path: "../Core"), + .target(name: "PresentationShared") + ], + settings: frameworkBuildSettings + ), + .target( + name: "NotificationTabTests", + destinations: .iOS, + product: .unitTests, + bundleId: "com.opfic.DevLog.NotificationTabTests", + infoPlist: .file(path: testsInfoPlistPath), + sources: ["NotificationTab/Tests/**/*.swift"], + scripts: [ + DevLogScripts.swiftLint( + sourcePath: "NotificationTab/Tests", + configPath: "NotificationTab/Tests/.swiftlint.yml" + ) + ], + dependencies: [ + .project(target: "Domain", path: "../Domain"), + .project(target: "Core", path: "../Core"), + .target(name: "NotificationTab"), + .target(name: "PresentationShared") + ], + settings: .devlog( + base: [ + "ENABLE_USER_SCRIPT_SANDBOXING": "NO", + "TEST_TARGET_NAME": "NotificationTab" + ] + ) + ), .target( name: "Presentation", destinations: .iOS, @@ -159,6 +205,7 @@ let project = Project( .project(target: "Core", path: "../Core"), .target(name: "HomeTab"), .target(name: "TodayTab"), + .target(name: "NotificationTab"), .target(name: "PresentationShared") ], settings: frameworkBuildSettings diff --git a/Application/Presentation/Sources/Main/MainView.swift b/Application/Presentation/Sources/Main/MainView.swift index ca098d4a..102ea7d5 100644 --- a/Application/Presentation/Sources/Main/MainView.swift +++ b/Application/Presentation/Sources/Main/MainView.swift @@ -9,6 +9,7 @@ import SwiftUI import Core import Domain import HomeTab +import NotificationTab import PresentationShared import TodayTab @@ -314,7 +315,7 @@ struct MainView: View { @ViewBuilder private var notificationRegularDetailView: some View { - if let todoId = pushNotificationListViewCoordinator.store.selectedTodoId?.id { + if let todoId = pushNotificationListViewCoordinator.selectedTodoId { TodoDetailView( store: pushNotificationListViewCoordinator.makeTodoDetailStore( todoId: todoId diff --git a/README.md b/README.md index aa5e77ac..6eca3aaf 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Todo, 저장 링크, 오늘 할 일, 받은 알림, 누적 활동을 하나의 ## 아키텍처 `DevLog.xcworkspace` 안에서 Application, Widget 모듈을 분리하고 화면, 상태, 비즈니스 로직, 외부 의존성 경계를 나눈 `Clean Architecture` 기반 구성 -`Presentation`은 tab shell/root composition을 유지하고, 공통 UI 흐름은 `PresentationShared`, 탭 단위 흐름은 `HomeTab`, `TodayTab` target이 소유함 +`Presentation`은 tab shell/root composition을 유지하고, 공통 UI 흐름은 `PresentationShared`, 탭 단위 흐름은 `HomeTab`, `TodayTab`, `NotificationTab` target이 소유함 @@ -242,6 +242,7 @@ DevLog_iOS/ │ │ ├── PresentationShared/ # 공통 UI 흐름, 공통 presentation structure │ │ ├── HomeTab/ # Home 탭 화면, feature, coordinator, 테스트 │ │ ├── TodayTab/ # Today 탭 화면, feature, coordinator, 테스트 +│ │ ├── NotificationTab/ # Notification 탭 화면, feature, coordinator, 테스트 │ │ └── Sources/ # Main, Root, Settings 등 앱 presentation shell │ └── Widget/ # 앱-위젯 브릿지, 위젯 동기화 이벤트, 스냅샷 갱신 ├── Widget/ diff --git a/docs/graph.png b/docs/graph.png index 057b08a5..3232c54d 100644 Binary files a/docs/graph.png and b/docs/graph.png differ