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
Expand Up @@ -10,4 +10,5 @@ import Foundation
enum UserDefaultsKey: String, CaseIterable {
case userID
case isOnboardingCompleted
case hasProgressChallenge
}
24 changes: 17 additions & 7 deletions Cherrish-iOS/Cherrish-iOS/Data/Repository/DemoRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,27 @@ struct DefaultDemoRepository: DemoInterface {
DemoAPI.fetchChallenges(userID: userID),
decodingType: FetchChallengesResponseDTO.self
)

let _ = userDefaultService.save(true, key: .hasProgressChallenge)
CherrishLogger.debug(userDefaultService.load(key: .hasProgressChallenge) ?? false)
return response.toEntity()
}

func advance() async throws -> ProgressChallengeEntity {
let userID: Int = userDefaultService.load(key: .userID) ?? 1
let response = try await networkService.request(
DemoAPI.advance(userID: userID),
decodingType: FetchChallengesResponseDTO.self
)
return response.toEntity()
do {
let response = try await networkService.request(
DemoAPI.advance(userID: userID),
decodingType: FetchChallengesResponseDTO.self
)
return response.toEntity()

} catch {
if error as! CherrishError == CherrishError.conflict {
let _ = userDefaultService.save(false, key: .hasProgressChallenge)
}
throw error
}
Comment on lines +42 to +47
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Force cast 사용으로 인한 런타임 크래시 위험

error as! CherrishError는 error가 CherrishError 타입이 아닐 경우 앱이 크래시됩니다. 네트워크 에러, 디코딩 에러 등 다른 타입의 에러가 발생할 수 있으므로 안전한 캐스팅을 사용해야 합니다.

🐛 안전한 캐스팅으로 수정
         } catch {
-            if error as! CherrishError == CherrishError.conflict {
+            if let cherrishError = error as? CherrishError, cherrishError == .conflict {
                 let _ = userDefaultService.save(false, key: .hasProgressChallenge)
             }
             throw error
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch {
if error as! CherrishError == CherrishError.conflict {
let _ = userDefaultService.save(false, key: .hasProgressChallenge)
}
throw error
}
} catch {
if let cherrishError = error as? CherrishError, cherrishError == .conflict {
let _ = userDefaultService.save(false, key: .hasProgressChallenge)
}
throw error
}
🧰 Tools
🪛 SwiftLint (0.57.0)

[Error] 43-43: Force casts should be avoided

(force_cast)

🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Repository/DemoRepository.swift` around lines
42 - 47, The catch block in DemoRepository.swift force-casts the thrown error
with `error as! CherrishError`, which can crash for non-CherrishError errors;
change this to a safe cast (e.g., `if let chErr = error as? CherrishError` or
`guard let chErr = error as? CherrishError else { throw error }`) and only call
`userDefaultService.save(false, key: .hasProgressChallenge)` when `chErr ==
.conflict`, otherwise rethrow the original error; update the catch in the
function containing this logic to perform the safe cast and conditional save
before rethrowing.


}

func toggleRoutine(routineID: Int) async throws -> ProgressRoutineEntity {
Expand All @@ -49,7 +59,7 @@ struct DefaultDemoRepository: DemoInterface {

func createChallenge(missionIds: Int, routineNames: [String]) async throws {
let userID: Int = userDefaultService.load(key: .userID) ?? 1
let response: () = try await networkService.request(
let response = try await networkService.request(
DemoAPI.createChallenge(userID: userID, requestDTO:
.init(
homecareRoutineId: missionIds,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ struct DefaultHomeRepository: HomeInterface {
HomeAPI.fetchDashboard(userID: userID),
decodingType: DashboardDTO.self
)

if dto.challengeName == nil {
_ = userDefaultService.save(false, key: .hasProgressChallenge)
}
CherrishLogger.debug(userDefaultService.load(key: .hasProgressChallenge) ?? false)
return dto.toEntity()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import SwiftUI

enum ChallengeRoute: PresentationTypeProtocol {
case root
case startChallenge
case createChallenge
case challengeProgress
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,36 @@ import SwiftUI
struct ChallengeCoordinatorView: View {
@EnvironmentObject private var challengeCoordinator: ChallengeCoordinator
@EnvironmentObject private var tabBarCoordinator: TabBarCoordinator
private let userDefaultService: UserDefaultService = DefaultUserDefaultService()
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

rg -n "UserDefaultService" --type swift -C2

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 10776


🏁 Script executed:

cat -n Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinatorView.swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 2055


🏁 Script executed:

cat -n Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Onboarding/SplashView.swift | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 669


의존성 주입 패턴 일관성 개선 필요

DefaultUserDefaultService()를 직접 인스턴스화하고 있습니다. 리포지토리들(HomeRepository, DemoRepository, ChallengeRepository 등)은 생성자 주입을 통해 UserDefaultService를 받고 있으며, SplashView처럼 같은 계층의 View 컴포넌트도 명시적 init() 메서드에서 기본 매개변수로 주입하는 패턴을 사용하고 있습니다.

다음과 같이 개선하면 테스트 시 mock 서비스 주입이 가능해지고 일관성이 향상됩니다:

init(userDefaultService: UserDefaultService = DefaultUserDefaultService()) {
    self.userDefaultService = userDefaultService
}
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinatorView.swift`
at line 13, Change ChallengeCoordinatorView to accept UserDefaultService via
constructor injection instead of instantiating DefaultUserDefaultService inline:
add an init(userDefaultService: UserDefaultService =
DefaultUserDefaultService()) that assigns to the existing userDefaultService
property, remove the direct DefaultUserDefaultService() initialization, and keep
the property typed as UserDefaultService so tests can inject a mock and callers
can rely on the default.


var body: some View {
NavigationStack(path: $challengeCoordinator.path) {
ViewFactory.shared.makeStartChallengeView()
.navigationDestination(for: ChallengeRoute.self) { route in
Group {
switch route {
case .root:
ViewFactory.shared.makeHomeView()
case .startChallenge:
ViewFactory.shared.makeStartChallengeView()
case .createChallenge:
ViewFactory.shared.makeCreateChallengeView()
.onAppear {
tabBarCoordinator.isTabbarHidden = true
}
case .challengeProgress:
ViewFactory.shared.makeChallengeProgressView()
.onAppear() {
tabBarCoordinator.isTabbarHidden = false
}
}
Group {
if let hasProgress: Bool = userDefaultService.load(key: .hasProgressChallenge), hasProgress {
ViewFactory.shared.makeChallengeProgressView()
} else {
ViewFactory.shared.makeStartChallengeView()
}
}
.navigationDestination(for: ChallengeRoute.self) { route in
Group {
switch route {
case .startChallenge:
ViewFactory.shared.makeStartChallengeView()
case .createChallenge:
ViewFactory.shared.makeCreateChallengeView()
.onAppear {
tabBarCoordinator.isTabbarHidden = true
}
case .challengeProgress:
ViewFactory.shared.makeChallengeProgressView()
.onAppear() {
tabBarCoordinator.isTabbarHidden = false
}
}
.navigationBarBackButtonHidden()
}
.navigationBarBackButtonHidden()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ struct ChallengeLoadingView: View {
var body: some View {
VStack {
VStack(spacing: 4.adjustedH) {
highlight(highlightText: viewModel.selectedRoutine?.description ?? "", normalText: "관리 방향을 바탕으로")
highlight(highlightText: viewModel.selectedRoutine?.description ?? "", normalText: "방향을 바탕으로")
.padding(.top, 113.adjustedH)
TypographyText("TO-DO 미션을 만들고 있어요.", style: .title1_sb_18, color: .gray800)
Image(.loading)
Expand Down