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
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 @@ -70,7 +70,7 @@ extension MyPageView {

private var grayEmptyBar: some View {
Rectangle()
.fill(.gray100)
.fill(.gray200)
.frame(height: 10.adjustedH)
}
}
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