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
4 changes: 2 additions & 2 deletions Cherrish-iOS/Cherrish-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 1.1;
PRODUCT_BUNDLE_IDENTIFIER = "com.nayeon.Cherrish-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down Expand Up @@ -339,7 +339,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 1.1;
PRODUCT_BUNDLE_IDENTIFIER = "com.nayeon.Cherrish-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ struct CalendarCellView: View {
scheduleCircle
}
}
.padding(.bottom, 4.adjustedH)
.padding(.bottom, 6.adjustedH)
.padding(.top, 19.adjustedH)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ struct CalendarView: View {
}
}
}
.onChange(of: homeCalendarFlowState.treatmentDate) { date in
.onChange(of: homeCalendarFlowState.treatmentDate) { _, date in
if let date = date {
viewModel.updateDate(date: date)
homeCalendarFlowState.treatmentDate = nil
Expand Down Expand Up @@ -127,34 +127,34 @@ extension CalendarView {
return VStack(spacing: 0) {
LazyVGrid(columns: columns, spacing: calendarRowSpacing) {
ForEach(dates) { value in
if value.day != -1 {
CalendarCellView(
value: value,
procedureCount: viewModel.getProcedureCount(for: value),
isSelected: viewModel.isSelected(value),
downtimeState: viewModel.getDowntimeState(for: value.date),
isDDay: viewModel.isDDay(for: value.date, selectedProcedureID: selectedProcedureID ?? 0),
calendarMode: $calendarMode
)
.onTapGesture {
viewModel.select(date: value.date)
calendarMode = .none
selectedProcedureID = nil

Task {
do {
try await viewModel.fetchTodayProcedureList()
} catch {
CherrishLogger.error(error)
if value.day != -1 {
CalendarCellView(
value: value,
procedureCount: viewModel.getProcedureCount(for: value),
isSelected: viewModel.isSelected(value),
downtimeState: viewModel.getDowntimeState(for: value.date),
isDDay: viewModel.isDDay(for: value.date, selectedProcedureID: selectedProcedureID ?? 0),
calendarMode: $calendarMode
)
.onTapGesture {
viewModel.select(date: value.date)
calendarMode = .none
selectedProcedureID = nil

Task {
do {
try await viewModel.fetchTodayProcedureList()
} catch {
CherrishLogger.error(error)
}
}
}
} else {
Color.clear
.frame(width: calendarCellWidth, height: calendarCellHeight)
}
} else {
Color.clear
.frame(width: calendarCellWidth, height: calendarCellHeight)
}
}
}

if rowCount == 4 {
Spacer()
Expand Down Expand Up @@ -290,7 +290,7 @@ extension CalendarView {
calendarCoordinator.push(
.selectTreatment
)

}
)
.padding(.horizontal, 24.adjustedW)
Expand Down Expand Up @@ -363,4 +363,4 @@ extension CalendarView {
guard calendarMode == .none, let initial = initialTopGlobalY else { return false }
return topGlobalY < initial - 0.1
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ final class ChallengeProgressViewModel: ObservableObject {
@Published private(set) var challengeData: ProgressChallengeEntity?
@Published private(set) var isLoading: Bool = false
@Published private(set) var errorMessage: String?
@Published private(set) var cherryLevel: CherryLevel = .mong
@Published private(set) var cherryLevel: CherryLevel = .level1
@Published private(set) var remainMissions: Int = 0
@Published private(set) var progressRate = 0
@Published private(set) var currentDay: Int = 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,39 @@ import SwiftUI
import Lottie

struct ChallengeLoadingView: View {
@ObservedObject var viewModel: CreateChallengeViewModel

@ObservedObject var viewModel: CreateChallengeViewModel
var body: some View {
VStack {
VStack(spacing: 4.adjustedH) {
VStack(alignment: .center) {
Spacer()
.frame(height: 94.adjustedH)

VStack(spacing: 2) {
highlight(highlightText: viewModel.selectedRoutine?.description ?? "", normalText: "방향을 바탕으로")
.frame(height: 27.adjustedH)
.padding(.top, 94.adjustedH)
TypographyText("TO-DO 미션을 만들고 있어요.", style: .title1_sb_18, color: .gray800)

LottieView(animationName: "splash", loopMode: .loop)
.frame(width: 130.adjustedW, height: 154.adjustedH)
.padding(.top, 60.adjustedH)

Spacer()
.frame(height: 80.adjustedH)

TypographyText("잠시만 기다려주세요!", style: .title2_m_16, color: .gray800)
.frame(height: 24.adjustedH)
.padding(.top, 17.adjustedH)

Spacer()

TypographyText("AI가 맞춤형 루틴을 제작하고 있어요.", style: .body3_m_12, color: .gray600)
.frame(height: 17.adjustedH)
.padding(.bottom, 30.adjustedH)
.frame(height: 27.adjustedH)
}

Spacer()
.frame(height: 80.adjustedH)

LottieView(animationName: "splash", loopMode: .loop)
.frame(width: 130.adjustedW, height: 154.adjustedH)

Spacer()
.frame(height: 80.adjustedH)

TypographyText("잠시만 기다려주세요!", style: .title2_m_16, color: .gray800)
.frame(height: 24.adjustedH)
.padding(.top, 17.adjustedH)

Spacer()

Comment on lines +36 to +41
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

중복 가능성이 있는 여백 설정

Line 38의 .padding(.top, 17.adjustedH)와 Line 40-41의 Spacer()가 함께 사용되고 있습니다. Spacer()가 남은 공간을 채우므로, 상단 패딩이 의도한 레이아웃인지 확인해 주세요. 만약 고정된 간격이 필요하다면 패딩 대신 Spacer().frame(height:)를 사용하는 것이 더 명확할 수 있습니다.

♻️ 제안된 대안
             TypographyText("잠시만 기다려주세요!", style: .title2_m_16, color: .gray800)
                 .frame(height: 24.adjustedH)
-                .padding(.top, 17.adjustedH)
             
             Spacer()
+                .frame(minHeight: 17.adjustedH)
📝 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
TypographyText("잠시만 기다려주세요!", style: .title2_m_16, color: .gray800)
.frame(height: 24.adjustedH)
.padding(.top, 17.adjustedH)
Spacer()
TypographyText("잠시만 기다려주세요!", style: .title2_m_16, color: .gray800)
.frame(height: 24.adjustedH)
Spacer()
.frame(minHeight: 17.adjustedH)
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/ChallengeLoadingView.swift`
around lines 36 - 41, The top padding on TypographyText(".padding(.top,
17.adjustedH)") and the following Spacer() likely duplicate spacing; pick one
approach and make it explicit in ChallengeLoadingView: either remove the
.padding(.top, 17.adjustedH) and rely on Spacer() to push content, or replace
Spacer() with Spacer().frame(height: 17.adjustedH) (or a fixed Spacer height) to
represent the intended fixed gap; update TypographyText, padding, and Spacer
usage accordingly so only one mechanism controls the vertical space.

TypographyText("AI가 맞춤형 루틴을 제작하고 있어요.", style: .body3_m_12, color: .gray600)
.frame(height: 17.adjustedH)
.padding(.bottom, 30.adjustedH)

}
.frame(maxHeight: .infinity)
.ignoresSafeArea(edges: .bottom)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,6 @@

import SwiftUI

enum CherryLevel: Int {
case mong = 1
case bbo
case pang
case ggu

var levelNumber: Int { rawValue }

static func from(progressRate: Double) -> CherryLevel {
switch progressRate {
case 0.0..<25.0:
return .mong
case 25.0..<50.0:
return .bbo
case 50.0..<75.0:
return .pang
case 75.0...100.0:
return .ggu
default:
return .mong
}
}

var name: String {
switch self {
case .mong: return "몽롱체리"
case .bbo: return "뽀득체리"
case .pang: return "팡팡체리"
case .ggu: return "꾸꾸체리"
}
}

var cherryImage: Image {
Image("cherry\(rawValue)")
}
var progressImage: Image {
Image("challenge_gaugebar_\(levelNumber)")
}
}

struct ChallengeProgressView: View {
@EnvironmentObject private var challengeCoordinator: ChallengeCoordinator
@StateObject var viewModel: ChallengeProgressViewModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ struct HomeView: View {
VStack(spacing: 0) {
HeaderLogoView()
ZStack(alignment: .topTrailing) {
if viewModel.cherryLevel == 0 {
if viewModel.challengeName == nil {
ChallengeCardEmptyView(
challengeBarImageName: viewModel.challengeBarImageName
)
} else {
ChallengeCardView(
challengeName: viewModel.challengeName,
challengeName: viewModel.challengeName ?? "챌린지",
challengeRate: viewModel.challengeRateText,
challengeBarImageName: viewModel.challengeBarImageName
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,21 @@ final class HomeViewModel: ObservableObject {
return String(format: "%.0f%%", rate)
}

var challengeName: String {
dashboardData?.challengeName ?? "챌린지"
var challengeName: String? {
return dashboardData?.challengeName
}

var cherryLevel: Int {
dashboardData?.cherryLevel ?? 0
}

var challengeBarImageName: String {
let level = min(max(cherryLevel, 0), 4)
let level = cherryLevel
return "home_chellenge_bar\(level)"
}
Comment on lines 73 to 76
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

challengeBarImageName에서 cherryLevel 경계값 검증 누락

이전에는 cherryLevel을 클램핑(clamping)하여 사용했을 가능성이 있는데, 현재는 직접 사용하고 있습니다. 서버에서 반환되는 cherryLevel이 0~4 범위를 벗어나는 경우, 존재하지 않는 이미지(home_chellenge_bar5 등)를 참조할 수 있습니다.

cherryLevelImageName(Line 79)에서는 0인 경우를 처리하고 있으나, challengeBarImageName에서는 동일한 보호가 없습니다.

♻️ 경계값 검증 추가 제안
 var challengeBarImageName: String {
-    let level = cherryLevel 
+    let level = min(max(cherryLevel, 0), 4)
     return "home_chellenge_bar\(level)"
 }
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeViewModel.swift`
around lines 73 - 76, challengeBarImageName uses cherryLevel directly which can
be out of bounds and produce missing image names; update challengeBarImageName
to clamp cherryLevel into the valid 0...4 range (same logic used in
cherryLevelImageName) before building the string, e.g., compute a safeLevel =
max(0, min(cherryLevel, 4)) and return "home_chellenge_bar\(safeLevel)"; locate
the challengeBarImageName computed property and apply this clamping so it never
references non-existent images.


var cherryLevelImageName: String {
let level = min(max(cherryLevel, 0), 4)
let level = cherryLevel == 0 ? 1 : cherryLevel
return "home_lv.\(level)"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,7 @@ import Lottie

struct SplashView: View {
@EnvironmentObject private var appCoordinator: AppCoordinator

private let userDefaultService: UserDefaultService

init(userDefaultService: UserDefaultService = DefaultUserDefaultService()) {
self.userDefaultService = userDefaultService
}

var body: some View {
ZStack(alignment: .center) {
LinearGradient(
Expand All @@ -34,20 +28,9 @@ struct SplashView: View {
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
navigateBasedOnUserStatus()
appCoordinator.navigationToOnboarding()
}
}
.ignoresSafeArea()
}

private func navigateBasedOnUserStatus() {
let userID: Int? = userDefaultService.load(key: .userID)
let isOnboardingCompleted: Bool = userDefaultService.load(key: .isOnboardingCompleted) ?? false

if userID != nil && isOnboardingCompleted {
appCoordinator.navigationToTabbar()
} else {
appCoordinator.navigationToOnboarding()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import SwiftUI
struct ScrollTopPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = .zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue())
value = min(value, nextValue())
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// CherryLevel.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/23/26.
//

import Foundation
import SwiftUI

enum CherryLevel: Int {
case level0 = 0
case level1
case level2
case level3
case level4

var levelNumber: Int {
if self == .level0 {
return 1
}
else {
return rawValue
}
}

static func from(progressRate: Double) -> CherryLevel {
switch progressRate {
case 0:
return .level0
case 0.0..<25.0:
return .level1
case 25.0..<50.0:
return .level2
case 50.0..<75.0:
return .level3
case 75.0...100.0:
return .level4
default:
return .level1
}
}
Comment on lines +27 to +42
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

from(progressRate:) 메서드의 케이스 중복 및 경계값 처리 개선 필요

case 0case 0.0..<25.0이 모두 progressRate == 0을 포함합니다. Swift의 switch는 첫 번째 매칭 케이스에서 종료되므로 현재 동작은 의도대로 작동하지만, 코드 가독성과 의도 명확성을 위해 범위를 명시적으로 분리하는 것이 좋습니다.

또한 progressRate가 음수이거나 100을 초과하는 경우 default.level1을 반환하는데, 이것이 의도된 동작인지 확인이 필요합니다.

♻️ 범위를 명확히 분리한 개선안
 static func from(progressRate: Double) -> CherryLevel {
     switch progressRate {
     case 0:
         return .level0
-    case 0.0..<25.0:
+    case 0.0<..<25.0:  // 0 초과 ~ 25 미만 (Swift에서는 이 문법이 지원되지 않음)
         return .level1

Swift에서는 0.0<..<25.0 문법이 지원되지 않으므로, 다음과 같이 수정하는 것을 권장합니다:

 static func from(progressRate: Double) -> CherryLevel {
-    switch progressRate {
-    case 0:
-        return .level0
-    case 0.0..<25.0:
-        return .level1
-    case 25.0..<50.0:
-        return .level2
-    case 50.0..<75.0:
-        return .level3
-    case 75.0...100.0:
-        return .level4
-    default:
-        return .level1
-    }
+    if progressRate == 0 {
+        return .level0
+    } else if progressRate < 25.0 {
+        return .level1
+    } else if progressRate < 50.0 {
+        return .level2
+    } else if progressRate < 75.0 {
+        return .level3
+    } else if progressRate <= 100.0 {
+        return .level4
+    } else {
+        return .level1
+    }
 }
📝 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
static func from(progressRate: Double) -> CherryLevel {
switch progressRate {
case 0:
return .level0
case 0.0..<25.0:
return .level1
case 25.0..<50.0:
return .level2
case 50.0..<75.0:
return .level3
case 75.0...100.0:
return .level4
default:
return .level1
}
}
static func from(progressRate: Double) -> CherryLevel {
if progressRate == 0 {
return .level0
} else if progressRate < 25.0 {
return .level1
} else if progressRate < 50.0 {
return .level2
} else if progressRate < 75.0 {
return .level3
} else if progressRate <= 100.0 {
return .level4
} else {
return .level1
}
}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Enum/CherryLevel.swift` around
lines 27 - 42, The from(progressRate:) switch has overlapping cases and doesn't
handle out-of-range inputs clearly; update CherryLevel.from(progressRate:) to
first clamp progressRate to 0...100 (or explicitly handle negatives and >100),
then use non-overlapping boundaries such as if progressRate <= 0 { return
.level0 } else if progressRate < 25 { return .level1 } else if progressRate < 50
{ return .level2 } else if progressRate < 75 { return .level3 } else /* <=100 */
{ return .level4 }, removing the redundant case 0 and the catch-all default so
ranges are explicit and clear.


var name: String {
switch self {
case .level0, .level1: return "몽롱체리"
case .level2: return "뽀득체리"
case .level3: return "팡팡체리"
case .level4: return "꾸꾸체리"
}
}

var cherryImage: Image {
switch self {
case .level0, .level1: return Image(.cherry1)
case .level2: return Image(.cherry2)
case .level3: return Image(.cherry3)
case .level4: return Image(.cherry4)
}
}


var progressImage: Image {
Image("challenge_gaugebar_\(rawValue)")
}
}
Loading