Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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 @@ -15,7 +15,7 @@ final class ChallengeProgressViewModel: ObservableObject {
@Published private(set) var remainMissions: Int = 0
@Published private(set) var progressRate = 0
@Published private(set) var currentDay: Int = 1
@Published private(set) var challengeTitle: String = "์ฑŒ๋ฆฐ์ง€"
@Published private(set) var challengeTitle: String = ""
@Published private(set) var todayRoutines: [ProgressRoutineEntity] = []

private let fetchChallengeUseCase: FetchChallengeUseCase
Expand Down Expand Up @@ -94,14 +94,18 @@ final class ChallengeProgressViewModel: ObservableObject {
}

extension ChallengeProgressViewModel {
var isChallengeCompleted: Bool {
currentDay == 7
}
Comment on lines +97 to +99
Copy link

Choose a reason for hiding this comment

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

๐Ÿงน Nitpick | ๐Ÿ”ต Trivial

์™„๋ฃŒ ๊ธฐ์ค€(7์ผ) ํ•˜๋“œ์ฝ”๋”ฉ ํ™•์ธ ํ•„์š”.
์ฑŒ๋ฆฐ์ง€ ๊ธฐ๊ฐ„์ด ๊ณ ์ • 7์ผ์ด ์•„๋‹ˆ๋ผ๋ฉด ๋ฐ์ดํ„ฐ/์ƒ์ˆ˜๋กœ ๋ถ„๋ฆฌํ•ด ๊ธฐ์ค€์„ ๋งž์ถ”๋Š” ํŽธ์ด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿค– Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ViewModel/ChallengeProgressViewModel.swift`
around lines 97 - 99, The isChallengeCompleted property currently hardcodes the
7-day completion check; change it to use a configurable duration value (e.g., a
Challenge.duration / totalDays constant or a value from the Challenge
model/repository) instead of the literal 7. Update ChallengeProgressViewModel to
reference that configurable property (for example challengeDuration or
challenge.totalDays) when evaluating currentDay in isChallengeCompleted so
different challenge lengths are supported. Ensure the new source is injected or
accessible in the ViewModel constructor or via its existing challenge/currentDay
data so you don't introduce globals.


@MainActor
private func updateInfo() {
guard let challengeData else { return }
cherryLevel = CherryLevel.from(progressRate: Double(challengeData.progressPercentage))
remainMissions = challengeData.remainingRoutinesToNextLevel
progressRate = challengeData.progressPercentage
currentDay = challengeData.currentDay
challengeTitle = challengeData.title
challengeTitle = challengeData.title + " ์ฑŒ๋ฆฐ์ง€"
todayRoutines = challengeData.todayRoutines
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct ChallengeLoadingView: View {
TypographyText("TO-DO ๋ฏธ์…˜์„ ๋งŒ๋“ค๊ณ  ์žˆ์–ด์š”.", style: .title1_sb_18, color: .gray800)
Image(.loading)
.padding(.top, 17.adjustedH)
TypographyText("์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”!", style: .title2_sb_16, color: .gray800)
TypographyText("์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”!", style: .title2_m_16, color: .gray800)
.padding(.top, 17.adjustedH)
Spacer()
TypographyText("AI๊ฐ€ ๋งž์ถคํ˜• ๋ฃจํ‹ด์„ ์ œ์ž‘ํ•˜๊ณ  ์žˆ์–ด์š”.", style: .body3_m_12, color: .gray600)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@ enum CherryLevel: Int {
}

struct ChallengeProgressView: View {
@EnvironmentObject private var challengeCoordinator: ChallengeCoordinator
@StateObject var viewModel: ChallengeProgressViewModel

Comment on lines +51 to 53
Copy link

Choose a reason for hiding this comment

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

๐Ÿงน Nitpick | ๐Ÿ”ต Trivial

EnvironmentObject ์ฃผ์ž… ๋ณด์žฅ ๋ฐ @StateObject ๊ฐ€์‹œ์„ฑ ์ •๋ฆฌ ํ•„์š”

์ƒˆ๋กœ ์ถ”๊ฐ€๋œ EnvironmentObject๊ฐ€ ์ƒ์œ„์—์„œ ์ฃผ์ž…๋˜์ง€ ์•Š์œผ๋ฉด ๋Ÿฐํƒ€์ž„ ํฌ๋ž˜์‹œ๊ฐ€ ๋‚ฉ๋‹ˆ๋‹ค. ์ƒ์„ฑ ์œ„์น˜์—์„œ ์ฃผ์ž… ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ด ์ฃผ์„ธ์š”. ๋˜ํ•œ @StateObject๋Š” ๋ณดํ†ต private๋กœ ๋‘๊ณ  init์—์„œ ์ฃผ์ž…ํ•˜๋Š” ํŒจํ„ด์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค(SwiftLint ๊ฒฝ๊ณ ).

โ™ป๏ธ `@StateObject` ์ ‘๊ทผ์„ฑ ์ •๋ฆฌ ์ œ์•ˆ
-    `@StateObject` var viewModel: ChallengeProgressViewModel
+    `@StateObject` private var viewModel: ChallengeProgressViewModel
+
+    init(viewModel: ChallengeProgressViewModel) {
+        _viewModel = StateObject(wrappedValue: viewModel)
+    }
#!/bin/bash
# ChallengeProgressView ์ƒ์„ฑ ์œ„์น˜์™€ EnvironmentObject ์ฃผ์ž… ์—ฌ๋ถ€ ํ™•์ธ
rg -n --type=swift -C3 '\bChallengeProgressView\b'
rg -n --type=swift -C3 '\.environmentObject\([^)]*ChallengeCoordinator'
๐Ÿงฐ Tools
๐Ÿช› SwiftLint (0.57.0)

[Warning] 52-52: SwiftUI state properties should be private

(private_swiftui_state)

๐Ÿค– Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/ChallengeProgressView.swift`
around lines 51 - 53, The ChallengeProgressView currently declares
`@EnvironmentObject` private var challengeCoordinator: ChallengeCoordinator which
can crash if not injected and declares `@StateObject` var viewModel:
ChallengeProgressViewModel with non-private visibility; update the view to
ensure safe EnvironmentObject usage by either (1) asserting or providing a
fallback when ChallengeCoordinator is missing at creation sites (verify all
ChallengeProgressView initializers call
.environmentObject(ChallengeCoordinator()) or wrap uses in a parent that injects
it) or (2) change the property to `@EnvironmentObject` private var
challengeCoordinator: ChallengeCoordinator? and guard/handle the nil case to
avoid runtime crashes; also make viewModel private (change to `@StateObject`
private var viewModel) and adopt the init-injection pattern for
ChallengeProgressView by adding an init(_ viewModel: ChallengeProgressViewModel)
that assigns _viewModel = StateObject(wrappedValue: viewModel) to satisfy
SwiftLint and visibility expectations, and then update all call sites to pass
the viewModel and ensure .environmentObject(ChallengeCoordinator) is applied
where the view is created.

let buttonState: ButtonState = .active

var body: some View {
ScrollView {
VStack {
Expand Down Expand Up @@ -82,22 +83,30 @@ struct ChallengeProgressView: View {

extension ChallengeProgressView {
private var CherryGrowthView: some View {
VStack {
VStack {
VStack(spacing: 0) {
VStack(spacing: 0) {
HStack {
TypographyText("Lv.\(viewModel.cherryLevel.levelNumber) \(viewModel.cherryLevel.name)", style: .body1_m_14, color: .gray900)
TypographyText("Lv.\(viewModel.cherryLevel.levelNumber)", style: .body1_m_14, color: .gray900)
TypographyText("\(viewModel.cherryLevel.name)", style: .body1_m_14, color: .gray900)
.padding(.leading, 6.adjustedW)
Spacer()
}
viewModel.cherryLevel.cherryImage
.padding(.top, 14.adjustedH)
TypographyText("์ฒด๋ฆฌ๊ฐ€ ํฌ๋ ค๋ฉด \(viewModel.remainMissions)๊ฐœ์˜ ๋ฏธ์…˜์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ด์š”!", style: .body2_r_13, color: .gray800)
.padding(.top, 14.adjustedH)
.frame(width: 154.adjustedW, height: 154.adjustedW)
.padding(.bottom, 5.adjustedH)
if viewModel.cherryLevel.levelNumber == 4 {
TypographyText("์ฑŒ๋ฆฐ์ง€ ์™„๋ฃŒ๊นŒ์ง€ \(viewModel.remainMissions)๊ฐœ์˜ ๋ฏธ์…˜์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ด์š”!", style: .body2_r_13, color: .gray800)
.padding(.bottom, 14.adjustedH)
}else {
TypographyText("์ฒด๋ฆฌ๊ฐ€ ํฌ๋ ค๋ฉด \(viewModel.remainMissions)๊ฐœ์˜ ๋ฏธ์…˜์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ด์š”!", style: .body2_r_13, color: .gray800)
.padding(.bottom, 14.adjustedH)
}
}
.padding(.horizontal, 25.adjustedW)
Rectangle()
.fill(.gray300)
.frame(height: 1)
.padding(.vertical, 14.adjustedH)
.padding(.bottom, 14.adjustedH)
VStack {
HStack {
TypographyText("์ฑŒ๋ฆฐ์ง€ ๋‹ฌ์„ฑ๋ฅ  \(viewModel.progressRate)%", style: .body1_m_14, color: .gray900)
Expand Down Expand Up @@ -149,14 +158,21 @@ extension ChallengeProgressView {
}

CherrishButton(
title: "์˜ค๋Š˜ ๋ฏธ์…˜ ์ข…๋ฃŒํ•˜๊ธฐ",
title: viewModel.isChallengeCompleted ? "์ฑŒ๋ฆฐ์ง€ ์ข…๋ฃŒํ•˜๊ธฐ":"์˜ค๋Š˜ ๋ฏธ์…˜ ์ข…๋ฃŒํ•˜๊ธฐ",
type: .small,
state: .constant(buttonState),
leadingIcon: nil,
trailingIcon: nil
) {
Task {
await viewModel.advanceDay()
if viewModel.isChallengeCompleted {
Task {
await viewModel.advanceDay()
challengeCoordinator.push(.startChallenge)
}
}else {
Task {
await viewModel.advanceDay()
}
Comment on lines 168 to +183
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

์™„๋ฃŒ ๋ถ„๊ธฐ์—์„œ advanceDay ์‹คํŒจ ์‹œ์—๋„ ๋„ค๋น„๊ฒŒ์ด์…˜๋ฉ๋‹ˆ๋‹ค.
advanceDay()๊ฐ€ ์‹คํŒจํ•ด๋„ ๊ณง๋ฐ”๋กœ ์ƒˆ ์ฑŒ๋ฆฐ์ง€๋กœ ์ด๋™ํ•˜๋ฉด ์„œ๋ฒ„ ์ƒํƒœ์™€ UI๊ฐ€ ์–ด๊ธ‹๋‚  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์„ฑ๊ณต ํ™•์ธ ํ›„ ์ด๋™ํ•˜๋„๋ก ๊ฐ€๋“œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

โœ… ์ˆ˜์ • ์ œ์•ˆ
-                if viewModel.isChallengeCompleted {
-                    Task {
-                        await viewModel.advanceDay()
-                        challengeCoordinator.push(.startChallenge)
-                    }
-                }else {
-                    Task {
-                        await viewModel.advanceDay()
-                    }
-                }
+                if viewModel.isChallengeCompleted {
+                    Task {
+                        await viewModel.advanceDay()
+                        guard viewModel.errorMessage == nil else { return }
+                        challengeCoordinator.push(.startChallenge)
+                    }
+                } else {
+                    Task {
+                        await viewModel.advanceDay()
+                    }
+                }
๐Ÿค– Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/ChallengeProgressView.swift`
around lines 160 - 175, The completion branch calls
challengeCoordinator.push(.startChallenge) regardless of whether
viewModel.advanceDay() succeeded; modify the branch handling in the
CherrishButton action so you await and verify success before navigating: call
viewModel.advanceDay() and check its result (or catch errors if it throws) and
only call challengeCoordinator.push(.startChallenge) when advanceDay reports
success; update viewModel.advanceDay() to return a Bool or throw if it currently
doesn't so you can gate navigation in the completion path (symbols:
CherrishButton, viewModel.isChallengeCompleted, viewModel.advanceDay(),
challengeCoordinator.push(.startChallenge)).

}
}
.padding(.top, 10.adjustedH)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ struct ChallengeSelectMissionView: View {
VStack {
HStack {
VStack(alignment: .leading) {
TypographyText("์ฑŒ๋ฆฐ์ง€ ๊ธฐ๊ฐ„ ๋™์•ˆ",
style: .title1_sb_18,
color: .gray1000
)
TypographyText("์ง„ํ–‰ํ•  ๋ฏธ์…˜์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”.",
TypographyText("์ฑŒ๋ฆฐ์ง€ ๊ธฐ๊ฐ„ ๋™์•ˆ\n์ง„ํ–‰ํ•  ๋ฏธ์…˜์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”.",
style: .title1_sb_18,
color: .gray1000
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct ChallengeSelectRoutineView: View {
var body: some View {
VStack {
HStack{
VStack(alignment: .leading){
VStack(alignment: .leading, spacing: 0){
TypographyText("์ง€๊ธˆ ๋‚˜์—๊ฒŒ ๊ฐ€์žฅ ํ•„์š”ํ•œ\n๊ด€๋ฆฌ ๋ฃจํ‹ด์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”.",
style: .title1_sb_18,
color: .gray1000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ struct ChallengeStartChallengeView: View {
.padding(.top, 10.adjustedH)
.padding(.horizontal, 24.adjustedW)

HStack(spacing: 12) {
HStack(spacing: 0) {
Image("info")
TypographyText("์ด ์ฑŒ๋ฆฐ์ง€๋Š” ์„ค์ • ์‹œ์ ๋ถ€ํ„ฐ 7์ผ๊ฐ„ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.", style: .body3_m_12, color: .gray600)
}
.padding(.bottom, 12.adjustedH)
.padding(.top, 20.adjustedH)

CherrishButton(
title: "์ฑŒ๋ฆฐ์ง€ ์‹œ์ž‘ํ•˜๊ธฐ",
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
{
"images" : [
{
"filename" : "challenge_gaugebar_1.svg",
"idiom" : "universal"
"filename" : "challenge_gaugebar_1.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "challenge_gaugebar_1@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "challenge_gaugebar_1@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

This file was deleted.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
{
"images" : [
{
"filename" : "challenge_gaugebar_2.svg",
"idiom" : "universal"
"filename" : "challenge_gaugebar_2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "challenge_gaugebar_2@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "challenge_gaugebar_2@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading