Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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

public init(defaults: UserDefaults = .standard) {
self.defaults = defaults
}

public func hasCompletedOnboarding() -> Bool {
defaults.bool(forKey: .hasCompletedOnboarding)
}

public func setOnboardingCompleted() {
defaults.set(true, forKey: .hasCompletedOnboarding)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright © 2026 Devault. All rights reserved

import Foundation

public protocol SettingsRepository: Sendable {
func hasCompletedOnboarding() -> Bool
func setOnboardingCompleted()
}
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright © 2026 Devault. All rights reserved

import Foundation

public protocol OnboardingStatusUseCase: Sendable {
func hasCompleted() -> Bool
func setCompleted()
}
1 change: 1 addition & 0 deletions Projects/DVPresentation/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ let project = Project.project(

// 3rd-party dependency
.tca(),
.lottie(),
]
),
.tests(
Expand Down
42 changes: 31 additions & 11 deletions Projects/DVPresentation/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
@@ -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." : {
"시크릿이 없어요" : {
Comment on lines +4 to +56

@coderabbitai coderabbitai Bot Jul 25, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file excerpt =="
if [ -f Projects/DVPresentation/Resources/Localizable.xcstrings ]; then
  sed -n '1,90p' Projects/DVPresentation/Resources/Localizable.xcstrings
else
  echo "file not found"
fi

echo
echo "== xcstrings files =="
git ls-files '*xcstrings' || true

echo
echo "== localization metadata search =="
rg -n '"sourceLanguage"|"localizations"|"KO"|"ko"|"en"' -g '*xcstrings' . || true

echo
echo "== localizable key usages =="
rg -n '"목록을 불러오지 못했어요"|"시크릿이 없어요"|"Access your secrets on all your|App Store Review|Content"|"Create Project"|"No expire"|"Placeholder row"|"This may take a moment"|LOGO|Project Name|SSL Required' .

Repository: DevaultProject/Devault-macOS

Length of output: 3662


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json, pathlib
p = pathlib.Path("Projects/DVPresentation/Resources/Localizable.xcstrings")
if not p.exists():
    print("missing")
    raise SystemExit
data = json.loads(p.read_text())
print("sourceLanguage:", data.get("sourceLanguage"))
for key, obj in data.get("strings", {}).items():
    if key in [
        "Access your secrets on all your\nApple devices, securely encrypted.",
        "Content",
        "Create Project",
        "De",
        "Detail",
        "If Touch ID is unavailable,\nsystem password will be used",
        "LOGO",
        "No expire",
        "Placeholder row %@",
        "Project",
        "Project Name",
        "Save",
        "Select Project",
        "SSL Required",
        "This may take a moment...",
        "Vault",
        "목록을 불러오지 못했어요",
        "시크릿이 없어요",
    ]:
        local = obj.get("localizations", {})
        print(key)
        for locale, lodata in local.items():
            print(" ", locale, lodata.get("stringUnit", {}))
PY

Repository: DevaultProject/Devault-macOS

Length of output: 485


로컬라이즈 단위를 채워 주세요.

sourceLanguageen인데 새 영문 키에는 ko 번역이 없고, 목록을 불러오지 못했어요/시크릭이 없어요en 번역 없이 한국어가 소스 키로 남아 있습니다. Localizable.xcstrings가 앱 로케일용인지 확인하고, 지원 대상 로케일에는 en/ko stringUnit을 추가하거나 의도하지 않은 소스 언어 키는 제거해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVPresentation/Resources/Localizable.xcstrings` around lines 4 - 56,
Localizable.xcstrings의 새 로컬라이즈 항목을 완성하세요. sourceLanguage가 en인 각 영문 키에 지원 로케일에 맞는
en 및 ko stringUnit을 추가하고, “목록을 불러오지 못했어요”와 “시크릿이 없어요”는 의도된 한국어 키인지 확인해 en 번역을
추가하거나 소스 언어 키에서 제거하세요.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

client 구현과 함께 다음 이슈에서 처리할 예정

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dlguszoo 알겠습니다. 이번 PR 범위에서는 보류하고, 클라이언트 구현 이슈에서 Localizable.xcstringsen/ko stringUnit과 소스 키 언어를 함께 정리하면 됩니다.

원하시면 해당 후속 GitHub 이슈를 생성해 드릴까요?


}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright © 2026 Devault. All rights reserved

import Foundation

import ComposableArchitecture

// MARK: - OnboardingStatusClient

@DependencyClient
public struct OnboardingStatusClient {
public var hasCompleted: @Sendable () -> Bool = { false }
public var setCompleted: @Sendable () -> 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 }
}
}
50 changes: 45 additions & 5 deletions Projects/DVPresentation/Sources/Features/AppFeature.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright © 2026 Devault. All rights reserved

import SwiftUI
import Foundation

import ComposableArchitecture

Expand All @@ -13,7 +13,10 @@ public struct AppFeature {

@ObservableState
public struct State: Equatable {
var main: MainFeature.State = .init()
// 세 프로퍼티 중 항상 하나만 non-nil — reducer 로직으로 불변식 보장
var onboarding: OnboardingContainerFeature.State?
var locked: LockFeature.State?
var main: MainFeature.State?

public init() {}
}
Expand All @@ -28,6 +31,8 @@ public struct AppFeature {

// MARK: - Child

case onboarding(OnboardingContainerFeature.Action)
case locked(LockFeature.Action)
case main(MainFeature.Action)

// MARK: - Delegate
Expand All @@ -37,19 +42,45 @@ public struct AppFeature {
public enum Delegate: Equatable {}
}

// MARK: - Dependencies

@Dependency(\.onboardingStatus) var onboardingStatus

// MARK: - Init

public init() {}

// MARK: - Body

public var body: some ReducerOf<Self> {
Scope(state: \.main, action: \.main) {
MainFeature()
}
Reduce { state, action in
switch action {
case .task:
state.onboarding = nil
state.locked = nil
state.main = nil

if onboardingStatus.hasCompleted() {
state.locked = .init()
} else {
state.onboarding = .init()
}
return .none

case .onboarding(.delegate(.completed)):
state.onboarding = nil
state.main = .init()
return .run { _ in onboardingStatus.setCompleted() }

case .onboarding:
return .none

case .locked(.delegate(.unlockCompleted)):
state.locked = nil
state.main = .init()
return .none

case .locked:
return .none

case .main:
Expand All @@ -59,5 +90,14 @@ public struct AppFeature {
return .none
}
}
.ifLet(\.onboarding, action: \.onboarding) {
OnboardingContainerFeature()
}
.ifLet(\.locked, action: \.locked) {
LockFeature()
}
.ifLet(\.main, action: \.main) {
MainFeature()
}
}
}
14 changes: 12 additions & 2 deletions Projects/DVPresentation/Sources/Features/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public struct AppView: View {

// MARK: - Properties

@Bindable public var store: StoreOf<AppFeature>
@Bindable var store: StoreOf<AppFeature>

// MARK: - Init

Expand All @@ -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)
}
}
}

Expand All @@ -41,6 +48,9 @@ extension AppView {
AppView(
store: Store(initialState: AppFeature.State()) {
AppFeature()
} withDependencies: {
$0.onboardingStatus.hasCompleted = { false }
$0.onboardingStatus.setCompleted = { }
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading