From 1ca9b52d8071d1062001225fdbd12e3866682fa1 Mon Sep 17 00:00:00 2001 From: dlguszoo Date: Thu, 23 Jul 2026 23:07:42 +0900 Subject: [PATCH 01/11] =?UTF-8?q?[#45]=20feat:=20SettingsRepository=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EB=B0=8F=20Onb?= =?UTF-8?q?oardingStatusUseCase=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interface/SettingsRepository.swift | 8 ++++++++ .../OnboardingStatusUseCaseImpl.swift | 20 +++++++++++++++++++ .../Settings/OnboardingStatusUseCase.swift | 8 ++++++++ 3 files changed, 36 insertions(+) create mode 100644 Projects/DVDomain/Sources/Repository/Interface/SettingsRepository.swift create mode 100644 Projects/DVDomain/Sources/UseCase/Impl/Settings/OnboardingStatusUseCaseImpl.swift create mode 100644 Projects/DVDomain/Sources/UseCase/Interface/Settings/OnboardingStatusUseCase.swift diff --git a/Projects/DVDomain/Sources/Repository/Interface/SettingsRepository.swift b/Projects/DVDomain/Sources/Repository/Interface/SettingsRepository.swift new file mode 100644 index 00000000..2f1a6587 --- /dev/null +++ b/Projects/DVDomain/Sources/Repository/Interface/SettingsRepository.swift @@ -0,0 +1,8 @@ +// Copyright © 2026 Devault. All rights reserved + +import Foundation + +public protocol SettingsRepository: Sendable { + func hasCompletedOnboarding() -> Bool + func setOnboardingCompleted() +} diff --git a/Projects/DVDomain/Sources/UseCase/Impl/Settings/OnboardingStatusUseCaseImpl.swift b/Projects/DVDomain/Sources/UseCase/Impl/Settings/OnboardingStatusUseCaseImpl.swift new file mode 100644 index 00000000..8b25acb4 --- /dev/null +++ b/Projects/DVDomain/Sources/UseCase/Impl/Settings/OnboardingStatusUseCaseImpl.swift @@ -0,0 +1,20 @@ +// Copyright © 2026 Devault. All rights reserved + +import Foundation + +public struct OnboardingStatusUseCaseImpl: OnboardingStatusUseCase { + + private let repository: any SettingsRepository + + public init(repository: any SettingsRepository) { + self.repository = repository + } + + public func hasCompleted() -> Bool { + repository.hasCompletedOnboarding() + } + + public func setCompleted() { + repository.setOnboardingCompleted() + } +} diff --git a/Projects/DVDomain/Sources/UseCase/Interface/Settings/OnboardingStatusUseCase.swift b/Projects/DVDomain/Sources/UseCase/Interface/Settings/OnboardingStatusUseCase.swift new file mode 100644 index 00000000..26998174 --- /dev/null +++ b/Projects/DVDomain/Sources/UseCase/Interface/Settings/OnboardingStatusUseCase.swift @@ -0,0 +1,8 @@ +// Copyright © 2026 Devault. All rights reserved + +import Foundation + +public protocol OnboardingStatusUseCase: Sendable { + func hasCompleted() -> Bool + func setCompleted() +} From be6abce410a8c88cf5a2b96df3ea479011eb030c Mon Sep 17 00:00:00 2001 From: dlguszoo Date: Thu, 23 Jul 2026 23:08:14 +0900 Subject: [PATCH 02/11] =?UTF-8?q?[#45]=20feat:=20UserDefaultsKey=20enum=20?= =?UTF-8?q?=EB=B0=8F=20SettingsRepositoryImpl=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Settings/SettingsRepositoryImpl.swift | 22 +++++++++++++++++++ .../Settings/UserDefaultsKey.swift | 17 ++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift create mode 100644 Projects/DVData/Sources/RepositoryImpl/Settings/UserDefaultsKey.swift diff --git a/Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift b/Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift new file mode 100644 index 00000000..57a50ef4 --- /dev/null +++ b/Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift @@ -0,0 +1,22 @@ +// Copyright © 2026 Devault. All rights reserved + +import Foundation + +import DVDomain + +public struct SettingsRepositoryImpl: SettingsRepository, @unchecked Sendable { + + private let defaults: UserDefaults + + public init(defaults: UserDefaults = .standard) { + self.defaults = defaults + } + + public func hasCompletedOnboarding() -> Bool { + defaults.bool(forKey: .hasCompletedOnboarding) + } + + public func setOnboardingCompleted() { + defaults.set(true, forKey: .hasCompletedOnboarding) + } +} diff --git a/Projects/DVData/Sources/RepositoryImpl/Settings/UserDefaultsKey.swift b/Projects/DVData/Sources/RepositoryImpl/Settings/UserDefaultsKey.swift new file mode 100644 index 00000000..8a4907a8 --- /dev/null +++ b/Projects/DVData/Sources/RepositoryImpl/Settings/UserDefaultsKey.swift @@ -0,0 +1,17 @@ +// Copyright © 2026 Devault. All rights reserved + +import Foundation + +enum UserDefaultsKey: String { + case hasCompletedOnboarding +} + +extension UserDefaults { + func bool(forKey key: UserDefaultsKey) -> Bool { + bool(forKey: key.rawValue) + } + + func set(_ value: Bool, forKey key: UserDefaultsKey) { + set(value, forKey: key.rawValue) + } +} From 12c83946d697fe23e7b1c0c99a4bf46b6cded4da Mon Sep 17 00:00:00 2001 From: dlguszoo Date: Fri, 24 Jul 2026 00:09:04 +0900 Subject: [PATCH 03/11] =?UTF-8?q?[#45]=20feat:=20OnboardingStatusClient=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98=20=EB=B0=8F=20liveValue=20=EC=A1=B0=EB=A6=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dependencies/OnboardingStatusClient.swift | 33 +++++++++++++++++++ .../OnboardingStatusClient+Live.swift | 18 ++++++++++ 2 files changed, 51 insertions(+) create mode 100644 Projects/DVPresentation/Sources/Dependencies/OnboardingStatusClient.swift create mode 100644 Projects/Devault/Sources/Composition/Dependencies/OnboardingStatusClient+Live.swift diff --git a/Projects/DVPresentation/Sources/Dependencies/OnboardingStatusClient.swift b/Projects/DVPresentation/Sources/Dependencies/OnboardingStatusClient.swift new file mode 100644 index 00000000..f4a0dfe0 --- /dev/null +++ b/Projects/DVPresentation/Sources/Dependencies/OnboardingStatusClient.swift @@ -0,0 +1,33 @@ +// Copyright © 2026 Devault. All rights reserved + +import Foundation + +import ComposableArchitecture + +// MARK: - OnboardingStatusClient + +@DependencyClient +public struct OnboardingStatusClient { + public var hasCompleted: () -> Bool = { false } + public var setCompleted: () -> Void +} + +// MARK: - DependencyKey + +extension OnboardingStatusClient: TestDependencyKey { + public static let testValue = OnboardingStatusClient() + + public static let previewValue = OnboardingStatusClient( + hasCompleted: { false }, + setCompleted: { } + ) +} + +// MARK: - DependencyValues + +extension DependencyValues { + public var onboardingStatus: OnboardingStatusClient { + get { self[OnboardingStatusClient.self] } + set { self[OnboardingStatusClient.self] = newValue } + } +} diff --git a/Projects/Devault/Sources/Composition/Dependencies/OnboardingStatusClient+Live.swift b/Projects/Devault/Sources/Composition/Dependencies/OnboardingStatusClient+Live.swift new file mode 100644 index 00000000..3dbc8971 --- /dev/null +++ b/Projects/Devault/Sources/Composition/Dependencies/OnboardingStatusClient+Live.swift @@ -0,0 +1,18 @@ +// Copyright © 2026 Devault. All rights reserved + +import ComposableArchitecture +import DVPresentation +import DVDomain +import DVData + +extension OnboardingStatusClient: @retroactive DependencyKey { + public static let liveValue: OnboardingStatusClient = { + let repository: any SettingsRepository = SettingsRepositoryImpl() + let useCase: any OnboardingStatusUseCase = OnboardingStatusUseCaseImpl(repository: repository) + + return OnboardingStatusClient( + hasCompleted: { useCase.hasCompleted() }, + setCompleted: { useCase.setCompleted() } + ) + }() +} From dc600b398e3f62e4b3fafe5ff8631b9a48339787 Mon Sep 17 00:00:00 2001 From: dlguszoo Date: Fri, 24 Jul 2026 00:09:25 +0900 Subject: [PATCH 04/11] =?UTF-8?q?[#45]=20feat:=20OnboardingContainerFeatur?= =?UTF-8?q?e/View=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20DVStepIndicator=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OnboardingView, LockView에서 DVStepIndicator를 제거하고 OnboardingContainerView에서 단일 인스턴스로 공유하여 온보딩→잠금 화면 전환 시 애니메이션 연속성 확보 --- .../Sources/Features/Lock/LockView.swift | 13 +-- .../OnboardingContainerFeature.swift | 85 +++++++++++++++++++ .../Onboarding/OnboardingContainerView.swift | 76 +++++++++++++++++ .../Features/Onboarding/OnboardingView.swift | 11 +-- 4 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerFeature.swift create mode 100644 Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift diff --git a/Projects/DVPresentation/Sources/Features/Lock/LockView.swift b/Projects/DVPresentation/Sources/Features/Lock/LockView.swift index bf522c64..ab9bb143 100644 --- a/Projects/DVPresentation/Sources/Features/Lock/LockView.swift +++ b/Projects/DVPresentation/Sources/Features/Lock/LockView.swift @@ -32,17 +32,8 @@ public struct LockView: View { extension LockView { private var content: some View { - ZStack { - unlockView - if store.isPostOnboarding { - VStack { - Spacer() - DVStepIndicator(totalSteps: 4, currentStep: 3) - .padding(.bottom, 40) - } - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity) + unlockView + .frame(maxWidth: .infinity, maxHeight: .infinity) } private var unlockView: some View { diff --git a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerFeature.swift b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerFeature.swift new file mode 100644 index 00000000..58e7c136 --- /dev/null +++ b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerFeature.swift @@ -0,0 +1,85 @@ +// Copyright © 2026 Devault. All rights reserved + +import Foundation + +import ComposableArchitecture + +// MARK: - OnboardingContainerFeature + +@Reducer +public struct OnboardingContainerFeature { + + // MARK: - State + + @ObservableState + public struct State: Equatable { + var onboarding: OnboardingFeature.State? + var lock: LockFeature.State? + + public init(showingLock: Bool = false) { + if showingLock { + self.lock = .init(isPostOnboarding: true) + } else { + self.onboarding = .init() + } + } + + var currentStepIndex: Int { + if let onboarding { return onboarding.currentStepIndex } + return 3 + } + } + + // MARK: - Action + + public enum Action: Equatable { + + // MARK: - Child + + case onboarding(OnboardingFeature.Action) + case lock(LockFeature.Action) + + // MARK: - Delegate + + case delegate(Delegate) + + public enum Delegate: Equatable { + case completed + } + } + + // MARK: - Init + + public init() {} + + // MARK: - Body + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .onboarding(.delegate(.completed)): + state.onboarding = nil + state.lock = .init(isPostOnboarding: true) + return .none + + case .onboarding: + return .none + + case .lock(.delegate(.unlockCompleted)): + return .send(.delegate(.completed)) + + case .lock: + return .none + + case .delegate: + return .none + } + } + .ifLet(\.onboarding, action: \.onboarding) { + OnboardingFeature() + } + .ifLet(\.lock, action: \.lock) { + LockFeature() + } + } +} diff --git a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift new file mode 100644 index 00000000..bccb8fc9 --- /dev/null +++ b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift @@ -0,0 +1,76 @@ +// Copyright © 2026 Devault. All rights reserved + +import SwiftUI + +import ComposableArchitecture +import DVDesign + +// MARK: - OnboardingContainerView + +public struct OnboardingContainerView: View { + + // MARK: - Properties + + @Bindable public var store: StoreOf + + // MARK: - Init + + public init(store: StoreOf) { + self.store = store + } + + // MARK: - Body + + public var body: some View { + content + .dvScreenBackground() + } +} + +// MARK: - Subviews + +extension OnboardingContainerView { + + private var content: some View { + ZStack { + stepView + VStack { + Spacer() + DVStepIndicator(totalSteps: 4, currentStep: store.currentStepIndex) + .padding(.bottom, 40) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + + @ViewBuilder + private var stepView: some View { + if let store = store.scope(state: \.onboarding, action: \.onboarding) { + OnboardingView(store: store) + } else if let store = store.scope(state: \.lock, action: \.lock) { + LockView(store: store) + } + } +} + +// MARK: - Preview + +#Preview("Onboarding Flow") { + OnboardingContainerView( + store: Store(initialState: OnboardingContainerFeature.State()) { + OnboardingContainerFeature() + } + ) + .frame(width: 540, height: 400) +} + +#Preview("Lock Step") { + OnboardingContainerView( + store: Store( + initialState: OnboardingContainerFeature.State(showingLock: true) + ) { + OnboardingContainerFeature() + } + ) + .frame(width: 540, height: 400) +} diff --git a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingView.swift b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingView.swift index 7492dcd8..49268cc2 100644 --- a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingView.swift +++ b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingView.swift @@ -33,15 +33,8 @@ public struct OnboardingView: View { extension OnboardingView { private var content: some View { - ZStack { - stepContent - VStack { - Spacer() - DVStepIndicator(totalSteps: 4, currentStep: store.currentStepIndex) - .padding(.bottom, 40) - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity) + stepContent + .frame(maxWidth: .infinity, maxHeight: .infinity) } @ViewBuilder From d3fb42a9f407142ec1ae89bb0017dfe223b530cf Mon Sep 17 00:00:00 2001 From: dlguszoo Date: Fri, 24 Jul 2026 00:09:54 +0900 Subject: [PATCH 05/11] =?UTF-8?q?[#45]=20feat:=20AppFeature=20=EC=98=A8?= =?UTF-8?q?=EB=B3=B4=EB=94=A9/=EC=9E=A0=EA=B8=88/=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=B6=84=EA=B8=B0=20=EB=9D=BC=EC=9A=B0=ED=8C=85=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit onboardingStatus에 따라 첫 진입 시 온보딩 또는 잠금 화면으로 분기하고 각 delegate action에 따라 다음 scene으로 전환 --- .../Sources/Features/AppFeature.swift | 46 +++++++++++++++++-- .../Sources/Features/AppView.swift | 12 ++++- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/Projects/DVPresentation/Sources/Features/AppFeature.swift b/Projects/DVPresentation/Sources/Features/AppFeature.swift index 66ad6ebf..eb44978f 100644 --- a/Projects/DVPresentation/Sources/Features/AppFeature.swift +++ b/Projects/DVPresentation/Sources/Features/AppFeature.swift @@ -1,6 +1,6 @@ // Copyright © 2026 Devault. All rights reserved -import SwiftUI +import Foundation import ComposableArchitecture @@ -13,7 +13,9 @@ public struct AppFeature { @ObservableState public struct State: Equatable { - var main: MainFeature.State = .init() + public internal(set) var onboarding: OnboardingContainerFeature.State? + public internal(set) var locked: LockFeature.State? + public internal(set) var main: MainFeature.State? public init() {} } @@ -28,6 +30,8 @@ public struct AppFeature { // MARK: - Child + case onboarding(OnboardingContainerFeature.Action) + case locked(LockFeature.Action) case main(MainFeature.Action) // MARK: - Delegate @@ -37,6 +41,10 @@ public struct AppFeature { public enum Delegate: Equatable {} } + // MARK: - Dependencies + + @Dependency(\.onboardingStatus) var onboardingStatus + // MARK: - Init public init() {} @@ -44,12 +52,31 @@ public struct AppFeature { // MARK: - Body public var body: some ReducerOf { - Scope(state: \.main, action: \.main) { - MainFeature() - } Reduce { state, action in switch action { case .task: + if onboardingStatus.hasCompleted() { + state.locked = .init(isPostOnboarding: false) + } else { + state.onboarding = .init() + } + return .none + + case .onboarding(.delegate(.completed)): + state.onboarding = nil + state.main = .init() + onboardingStatus.setCompleted() + return .none + + case .onboarding: + return .none + + case .locked(.delegate(.unlockCompleted)): + state.locked = nil + state.main = .init() + return .none + + case .locked: return .none case .main: @@ -59,5 +86,14 @@ public struct AppFeature { return .none } } + .ifLet(\.onboarding, action: \.onboarding) { + OnboardingContainerFeature() + } + .ifLet(\.locked, action: \.locked) { + LockFeature() + } + .ifLet(\.main, action: \.main) { + MainFeature() + } } } diff --git a/Projects/DVPresentation/Sources/Features/AppView.swift b/Projects/DVPresentation/Sources/Features/AppView.swift index 41c80426..3940c2a4 100644 --- a/Projects/DVPresentation/Sources/Features/AppView.swift +++ b/Projects/DVPresentation/Sources/Features/AppView.swift @@ -30,8 +30,15 @@ public struct AppView: View { extension AppView { + @ViewBuilder private var content: some View { - MainView(store: store.scope(state: \.main, action: \.main)) + if let store = store.scope(state: \.onboarding, action: \.onboarding) { + OnboardingContainerView(store: store) + } else if let store = store.scope(state: \.locked, action: \.locked) { + LockView(store: store) + } else if let store = store.scope(state: \.main, action: \.main) { + MainView(store: store) + } } } @@ -41,6 +48,9 @@ extension AppView { AppView( store: Store(initialState: AppFeature.State()) { AppFeature() + } withDependencies: { + $0.onboardingStatus.hasCompleted = { false } + $0.onboardingStatus.setCompleted = { } } ) } From e63450ab22a27dfc09094c090760b9bf29a6a5d4 Mon Sep 17 00:00:00 2001 From: dlguszoo Date: Fri, 24 Jul 2026 00:10:13 +0900 Subject: [PATCH 06/11] =?UTF-8?q?[#45]=20feat:=20OnboardingFeature=20iClou?= =?UTF-8?q?d=20sync=203=EC=B4=88=20=ED=83=80=EC=9D=B4=EB=A8=B8=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/Onboarding/OnboardingFeature.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift index a4dbfd57..45b1de72 100644 --- a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift +++ b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift @@ -59,6 +59,10 @@ public struct OnboardingFeature { } } + // MARK: - Dependencies + + @Dependency(\.continuousClock) var clock + // MARK: - Init public init() {} @@ -81,7 +85,11 @@ public struct OnboardingFeature { case .didTapEnableSync: state.step = .syncing - return .none + return .run { send in + // TODO: 실제 iCloud sync 완료 콜백으로 교체 + try await clock.sleep(for: .seconds(3)) + await send(.syncingCompleted) + } case .syncingCompleted: return .send(.delegate(.completed)) From cd4c63d5d8117199a0e2ffa6d9de97780d1455c1 Mon Sep 17 00:00:00 2001 From: dlguszoo Date: Fri, 24 Jul 2026 00:10:23 +0900 Subject: [PATCH 07/11] =?UTF-8?q?[#45]=20feat:=20DevaultApp=20=EC=9C=88?= =?UTF-8?q?=EB=8F=84=EC=9A=B0=20=EC=A0=84=EC=B2=B4=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=B0=8F=20=ED=83=80=EC=9D=B4=ED=8B=80=EB=B0=94=20=EC=88=A8?= =?UTF-8?q?=EA=B9=80=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Devault/Sources/DevaultApp.swift | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Projects/Devault/Sources/DevaultApp.swift b/Projects/Devault/Sources/DevaultApp.swift index 69316e6e..1fba7630 100644 --- a/Projects/Devault/Sources/DevaultApp.swift +++ b/Projects/Devault/Sources/DevaultApp.swift @@ -5,13 +5,17 @@ import DVPresentation @main struct DevaultApp: App { - var body: some Scene { - WindowGroup { - AppView( - store: Store(initialState: AppFeature.State()) { - AppFeature() - } - ) + var body: some Scene { + WindowGroup { + AppView( + store: Store(initialState: AppFeature.State()) { + AppFeature() } + ) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .ignoresSafeArea() } + .windowStyle(.hiddenTitleBar) + .defaultSize(width: 800, height: 600) + } } From 755e9b85b5e44044541f29aacc335683e547f804 Mon Sep 17 00:00:00 2001 From: dlguszoo Date: Fri, 24 Jul 2026 02:00:42 +0900 Subject: [PATCH 08/11] =?UTF-8?q?[#45]=20refactor:=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=20(=ED=95=84=EC=9A=94?= =?UTF-8?q?=EC=97=86=EB=8A=94=20ignoresafearea=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=9E=90=20=EC=A0=9C=EA=B1=B0,=20=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RepositoryImpl/Settings/SettingsRepositoryImpl.swift | 2 +- Projects/DVPresentation/Sources/Features/AppFeature.swift | 1 + Projects/Devault/Sources/DevaultApp.swift | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift b/Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift index 57a50ef4..a0292813 100644 --- a/Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift +++ b/Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift @@ -1,9 +1,9 @@ // Copyright © 2026 Devault. All rights reserved import Foundation - import DVDomain +// UserDefaults는 thread-safe하므로 @unchecked Sendable 허용 public struct SettingsRepositoryImpl: SettingsRepository, @unchecked Sendable { private let defaults: UserDefaults diff --git a/Projects/DVPresentation/Sources/Features/AppFeature.swift b/Projects/DVPresentation/Sources/Features/AppFeature.swift index eb44978f..d6ad78f5 100644 --- a/Projects/DVPresentation/Sources/Features/AppFeature.swift +++ b/Projects/DVPresentation/Sources/Features/AppFeature.swift @@ -13,6 +13,7 @@ public struct AppFeature { @ObservableState public struct State: Equatable { + // 세 프로퍼티 중 항상 하나만 non-nil — reducer 로직으로 불변식 보장 public internal(set) var onboarding: OnboardingContainerFeature.State? public internal(set) var locked: LockFeature.State? public internal(set) var main: MainFeature.State? diff --git a/Projects/Devault/Sources/DevaultApp.swift b/Projects/Devault/Sources/DevaultApp.swift index 1fba7630..8106549b 100644 --- a/Projects/Devault/Sources/DevaultApp.swift +++ b/Projects/Devault/Sources/DevaultApp.swift @@ -13,7 +13,6 @@ struct DevaultApp: App { } ) .frame(maxWidth: .infinity, maxHeight: .infinity) - .ignoresSafeArea() } .windowStyle(.hiddenTitleBar) .defaultSize(width: 800, height: 600) From 75142cae024405fb50645fa2720e0185ec60d7ef Mon Sep 17 00:00:00 2001 From: dlguszoo Date: Sat, 25 Jul 2026 10:52:47 +0900 Subject: [PATCH 09/11] =?UTF-8?q?[#45]=20fix:=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DVDesign/Sources/Foundations/Color/View+DVColor.swift | 8 ++++---- Projects/DVPresentation/Sources/Features/AppFeature.swift | 4 ++++ Projects/DVPresentation/Sources/Features/AppView.swift | 2 +- .../DVPresentation/Sources/Features/Lock/LockView.swift | 2 +- .../Features/Onboarding/OnboardingContainerView.swift | 2 +- .../Sources/Features/Onboarding/OnboardingView.swift | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Projects/DVDesign/Sources/Foundations/Color/View+DVColor.swift b/Projects/DVDesign/Sources/Foundations/Color/View+DVColor.swift index d8f02244..1a255cba 100644 --- a/Projects/DVDesign/Sources/Foundations/Color/View+DVColor.swift +++ b/Projects/DVDesign/Sources/Foundations/Color/View+DVColor.swift @@ -2,16 +2,16 @@ import SwiftUI -public extension View { - func dvForegroundColor(_ token: DVColor) -> some View { +extension View { + public func dvForegroundColor(_ token: DVColor) -> some View { self.foregroundStyle(token.color) } - func dvBackgroundColor(_ token: DVColor) -> some View { + public func dvBackgroundColor(_ token: DVColor) -> some View { self.background(token.color) } - func dvScreenBackground(_ token: DVColor = .gray100) -> some View { + public func dvScreenBackground(_ token: DVColor = .gray100) -> some View { self.background(token.color.ignoresSafeArea()) } } diff --git a/Projects/DVPresentation/Sources/Features/AppFeature.swift b/Projects/DVPresentation/Sources/Features/AppFeature.swift index d6ad78f5..36c2d4bd 100644 --- a/Projects/DVPresentation/Sources/Features/AppFeature.swift +++ b/Projects/DVPresentation/Sources/Features/AppFeature.swift @@ -56,6 +56,10 @@ public struct AppFeature { Reduce { state, action in switch action { case .task: + state.onboarding = nil + state.locked = nil + state.main = nil + if onboardingStatus.hasCompleted() { state.locked = .init(isPostOnboarding: false) } else { diff --git a/Projects/DVPresentation/Sources/Features/AppView.swift b/Projects/DVPresentation/Sources/Features/AppView.swift index 3940c2a4..47093e21 100644 --- a/Projects/DVPresentation/Sources/Features/AppView.swift +++ b/Projects/DVPresentation/Sources/Features/AppView.swift @@ -10,7 +10,7 @@ public struct AppView: View { // MARK: - Properties - @Bindable public var store: StoreOf + @Bindable var store: StoreOf // MARK: - Init diff --git a/Projects/DVPresentation/Sources/Features/Lock/LockView.swift b/Projects/DVPresentation/Sources/Features/Lock/LockView.swift index ab9bb143..86328223 100644 --- a/Projects/DVPresentation/Sources/Features/Lock/LockView.swift +++ b/Projects/DVPresentation/Sources/Features/Lock/LockView.swift @@ -11,7 +11,7 @@ public struct LockView: View { // MARK: - Properties - @Bindable public var store: StoreOf + @Bindable var store: StoreOf // MARK: - Init diff --git a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift index bccb8fc9..8d43c0a4 100644 --- a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift +++ b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift @@ -11,7 +11,7 @@ public struct OnboardingContainerView: View { // MARK: - Properties - @Bindable public var store: StoreOf + @Bindable var store: StoreOf // MARK: - Init diff --git a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingView.swift b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingView.swift index 49268cc2..e60a8cdf 100644 --- a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingView.swift +++ b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingView.swift @@ -12,7 +12,7 @@ public struct OnboardingView: View { // MARK: - Properties - @Bindable public var store: StoreOf + @Bindable var store: StoreOf // MARK: - Init From 471ad5284ad51a58e5ede9e6702d59c2fefba9fd Mon Sep 17 00:00:00 2001 From: dlguszoo Date: Sat, 25 Jul 2026 11:24:02 +0900 Subject: [PATCH 10/11] =?UTF-8?q?[#45]=20fix:=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. hasCompleted, setCompleted 클로저에 @Sendable 추가 2. setCompleted() → .run { _ in onboardingStatus.setCompleted() } 3. Step: CaseIterable 추가, 3 → Step.allCases.count - 1, 4 → Step.allCases.count 4. isPostOnboarding 제거 — DVStepIndicator 표시는 뷰 계층으로 결정 5. public internal(set) → var (internal) --- .../Dependencies/OnboardingStatusClient.swift | 4 ++-- .../Sources/Features/AppFeature.swift | 13 ++++++------- .../Sources/Features/Lock/LockFeature.swift | 6 +----- .../Onboarding/OnboardingContainerFeature.swift | 6 +++--- .../Onboarding/OnboardingContainerView.swift | 2 +- .../Features/Onboarding/OnboardingFeature.swift | 2 +- 6 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Projects/DVPresentation/Sources/Dependencies/OnboardingStatusClient.swift b/Projects/DVPresentation/Sources/Dependencies/OnboardingStatusClient.swift index f4a0dfe0..8435f847 100644 --- a/Projects/DVPresentation/Sources/Dependencies/OnboardingStatusClient.swift +++ b/Projects/DVPresentation/Sources/Dependencies/OnboardingStatusClient.swift @@ -8,8 +8,8 @@ import ComposableArchitecture @DependencyClient public struct OnboardingStatusClient { - public var hasCompleted: () -> Bool = { false } - public var setCompleted: () -> Void + public var hasCompleted: @Sendable () -> Bool = { false } + public var setCompleted: @Sendable () -> Void } // MARK: - DependencyKey diff --git a/Projects/DVPresentation/Sources/Features/AppFeature.swift b/Projects/DVPresentation/Sources/Features/AppFeature.swift index 36c2d4bd..84bba50d 100644 --- a/Projects/DVPresentation/Sources/Features/AppFeature.swift +++ b/Projects/DVPresentation/Sources/Features/AppFeature.swift @@ -14,9 +14,9 @@ public struct AppFeature { @ObservableState public struct State: Equatable { // 세 프로퍼티 중 항상 하나만 non-nil — reducer 로직으로 불변식 보장 - public internal(set) var onboarding: OnboardingContainerFeature.State? - public internal(set) var locked: LockFeature.State? - public internal(set) var main: MainFeature.State? + var onboarding: OnboardingContainerFeature.State? + var locked: LockFeature.State? + var main: MainFeature.State? public init() {} } @@ -59,9 +59,9 @@ public struct AppFeature { state.onboarding = nil state.locked = nil state.main = nil - + if onboardingStatus.hasCompleted() { - state.locked = .init(isPostOnboarding: false) + state.locked = .init() } else { state.onboarding = .init() } @@ -70,8 +70,7 @@ public struct AppFeature { case .onboarding(.delegate(.completed)): state.onboarding = nil state.main = .init() - onboardingStatus.setCompleted() - return .none + return .run { _ in onboardingStatus.setCompleted() } case .onboarding: return .none diff --git a/Projects/DVPresentation/Sources/Features/Lock/LockFeature.swift b/Projects/DVPresentation/Sources/Features/Lock/LockFeature.swift index de89e18f..9c12d1cc 100644 --- a/Projects/DVPresentation/Sources/Features/Lock/LockFeature.swift +++ b/Projects/DVPresentation/Sources/Features/Lock/LockFeature.swift @@ -13,11 +13,7 @@ public struct LockFeature { @ObservableState public struct State: Equatable { - public var isPostOnboarding: Bool - - public init(isPostOnboarding: Bool = false) { - self.isPostOnboarding = isPostOnboarding - } + public init() {} } // MARK: - Action diff --git a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerFeature.swift b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerFeature.swift index 58e7c136..5cfdcf33 100644 --- a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerFeature.swift +++ b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerFeature.swift @@ -18,7 +18,7 @@ public struct OnboardingContainerFeature { public init(showingLock: Bool = false) { if showingLock { - self.lock = .init(isPostOnboarding: true) + self.lock = .init() } else { self.onboarding = .init() } @@ -26,7 +26,7 @@ public struct OnboardingContainerFeature { var currentStepIndex: Int { if let onboarding { return onboarding.currentStepIndex } - return 3 + return OnboardingFeature.Step.allCases.count - 1 } } @@ -59,7 +59,7 @@ public struct OnboardingContainerFeature { switch action { case .onboarding(.delegate(.completed)): state.onboarding = nil - state.lock = .init(isPostOnboarding: true) + state.lock = .init() return .none case .onboarding: diff --git a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift index 8d43c0a4..b9eac57d 100644 --- a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift +++ b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift @@ -36,7 +36,7 @@ extension OnboardingContainerView { stepView VStack { Spacer() - DVStepIndicator(totalSteps: 4, currentStep: store.currentStepIndex) + DVStepIndicator(totalSteps: OnboardingFeature.Step.allCases.count, currentStep: store.currentStepIndex) .padding(.bottom, 40) } } diff --git a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift index 45b1de72..56fa436e 100644 --- a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift +++ b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift @@ -11,7 +11,7 @@ public struct OnboardingFeature { // MARK: - Step - public enum Step: Equatable { + public enum Step: Equatable, CaseIterable { case welcome case security case icloudSync From a977613f63bf1153dff757a0022df70d27536fe1 Mon Sep 17 00:00:00 2001 From: dlguszoo Date: Sat, 25 Jul 2026 11:41:08 +0900 Subject: [PATCH 11/11] =?UTF-8?q?[#45]=20fix:=20lottie=20Presentation=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=EC=97=90=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?isPostOnboarding=20=ED=94=84=EB=A6=AC=EB=B7=B0=EB=8B=A8?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/DVPresentation/Project.swift | 1 + .../Resources/Localizable.xcstrings | 42 ++++++++++++++----- .../Sources/Features/Lock/LockView.swift | 13 +----- .../OnboardingContainerFeature.swift | 8 +--- .../Onboarding/OnboardingContainerView.swift | 10 ----- 5 files changed, 36 insertions(+), 38 deletions(-) diff --git a/Projects/DVPresentation/Project.swift b/Projects/DVPresentation/Project.swift index c0bd94e6..7ef86892 100644 --- a/Projects/DVPresentation/Project.swift +++ b/Projects/DVPresentation/Project.swift @@ -17,6 +17,7 @@ let project = Project.project( // 3rd-party dependency .tca(), + .lottie(), ] ), .tests( diff --git a/Projects/DVPresentation/Resources/Localizable.xcstrings b/Projects/DVPresentation/Resources/Localizable.xcstrings index 966dec69..91840cfb 100644 --- a/Projects/DVPresentation/Resources/Localizable.xcstrings +++ b/Projects/DVPresentation/Resources/Localizable.xcstrings @@ -1,39 +1,59 @@ { "sourceLanguage" : "en", "strings" : { + "Access your secrets on all your\nApple devices, securely encrypted." : { + + }, "Content" : { "comment" : "The title of the main content section in the preview.", "isCommentAutoGenerated" : true + }, + "Create Project" : { + + }, + "De" : { + }, "Detail" : { }, - "Development" : { + "If Touch ID is unavailable,\nsystem password will be used" : { }, - "Enterprise" : { + "LOGO" : { }, - "Failed to load projects" : { + "No expire" : { }, - "Individual" : { + "Placeholder row %@" : { }, - "Production" : { + "Project" : { }, - "Staging" : { + "Project Name" : { }, - "Team" : { + "Save" : { }, - "Sidebar" : { - "comment" : "The text that appears in the sidebar of the main view.", - "isCommentAutoGenerated" : true + "Select Project" : { + + }, + "SSL Required" : { + + }, + "This may take a moment..." : { + + }, + "Vault" : { + + }, + "목록을 불러오지 못했어요" : { + }, - "The project list couldn't be loaded. Please try again later." : { + "시크릿이 없어요" : { } }, diff --git a/Projects/DVPresentation/Sources/Features/Lock/LockView.swift b/Projects/DVPresentation/Sources/Features/Lock/LockView.swift index 86328223..2a7c3e60 100644 --- a/Projects/DVPresentation/Sources/Features/Lock/LockView.swift +++ b/Projects/DVPresentation/Sources/Features/Lock/LockView.swift @@ -61,18 +61,9 @@ extension LockView { // MARK: - Preview -#Preview("Post Onboarding") { +#Preview { LockView( - store: Store(initialState: LockFeature.State(isPostOnboarding: true)) { - LockFeature() - } - ) - .frame(width: 540, height: 400) -} - -#Preview("Re-entry / Locked") { - LockView( - store: Store(initialState: LockFeature.State(isPostOnboarding: false)) { + store: Store(initialState: LockFeature.State()) { LockFeature() } ) diff --git a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerFeature.swift b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerFeature.swift index 5cfdcf33..3f58cf9f 100644 --- a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerFeature.swift +++ b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerFeature.swift @@ -16,12 +16,8 @@ public struct OnboardingContainerFeature { var onboarding: OnboardingFeature.State? var lock: LockFeature.State? - public init(showingLock: Bool = false) { - if showingLock { - self.lock = .init() - } else { - self.onboarding = .init() - } + public init() { + self.onboarding = .init() } var currentStepIndex: Int { diff --git a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift index b9eac57d..33f03d6a 100644 --- a/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift +++ b/Projects/DVPresentation/Sources/Features/Onboarding/OnboardingContainerView.swift @@ -64,13 +64,3 @@ extension OnboardingContainerView { .frame(width: 540, height: 400) } -#Preview("Lock Step") { - OnboardingContainerView( - store: Store( - initialState: OnboardingContainerFeature.State(showingLock: true) - ) { - OnboardingContainerFeature() - } - ) - .frame(width: 540, height: 400) -}