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 @@ -14,7 +14,6 @@ enum HomeAPI {
}

extension HomeAPI: EndPoint {

var basePath: String {
"/api"
}
Expand Down Expand Up @@ -47,7 +46,7 @@ extension HomeAPI: EndPoint {
}
}

var queryParameters: [String: String]? {
var queryParameters: [String: Any]? {
switch self {
case .fetchDashboard:
return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ enum CalendarMode {

struct CalendarView: View {
@EnvironmentObject private var calendarCoordinator: CalendarCoordinator
@ObservedObject var viewModel: CalendarViewModel
@StateObject var viewModel: CalendarViewModel
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

🧩 Analysis chain

🏁 Script executed:

# Search for CalendarView instantiation patterns
rg -n "CalendarView\(" --type swift -C 3

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 2872


🏁 Script executed:

# Check CalendarView file for init and property declarations
fd "CalendarView.swift" --type f | xargs cat -n | head -100

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 3866


@StateObject 대신 @ObservedObject 사용 필요

CalendarViewModel이 외부의 DIContainer에서 생성되어 의존성 주입으로 전달되고 있습니다 (ViewFactory 라인 53-57, makeCalendarView(viewModel:) 호출). 이 경우 @StateObject 사용은 부적절합니다.

  • @StateObject: 뷰가 객체를 생성하고 소유할 때만 사용
  • @ObservedObject: 외부에서 주입된 객체를 관찰할 때 사용

현재 @StateObject로 선언하면 뷰 재생성 시 예기치 않게 새 인스턴스가 생성될 수 있습니다. @ObservedObject로 변경하세요.

🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`
at line 26, The CalendarView currently declares its injected CalendarViewModel
with `@StateObject` which is incorrect for externally created DI-injected
instances; update the property declaration in CalendarView to use
`@ObservedObject` instead of `@StateObject` so the view observes the externally
created model (refer to CalendarViewModel and the factory call
makeCalendarView(viewModel:) in ViewFactory to locate the injection point).

@State private var topGlobalY: CGFloat = .zero
@State private var initialTopGlobalY: CGFloat? = nil
@State private var bottomOffsetY: CGFloat = .zero
Expand All @@ -32,20 +32,28 @@ struct CalendarView: View {
@State private var buttonState: ButtonState = .active

private let scrollAreaHeight: CGFloat = 184.adjustedH
private let calendarCellWidth: CGFloat = 40.adjustedW
private let calendarCellHeight: CGFloat = 40.adjustedH
private let calendarRowSpacing: CGFloat = 8.adjustedH

let weekdays: [String] = ["일", "월", "화", "수", "목", "금", "토"]
let columns = Array(repeating: GridItem(.fixed(40), spacing: 8), count: 7)
let columns = Array(repeating: GridItem(.fixed(40.adjustedW), spacing: 8), count: 7)
Comment on lines +35 to +40
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

상수 일관성 개선 권장

calendarCellWidth 상수가 정의되었지만 columns에서는 하드코딩된 40.adjustedW를 사용하고 있습니다. 유지보수성을 위해 상수를 사용하는 것이 좋습니다.

♻️ 상수 사용 권장
-    let columns = Array(repeating: GridItem(.fixed(40.adjustedW), spacing: 8), count: 7)
+    let columns = Array(repeating: GridItem(.fixed(calendarCellWidth), spacing: calendarRowSpacing), count: 7)
📝 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
private let calendarCellWidth: CGFloat = 40.adjustedW
private let calendarCellHeight: CGFloat = 40.adjustedH
private let calendarRowSpacing: CGFloat = 8.adjustedH
let weekdays: [String] = ["", "", "", "", "", "", ""]
let columns = Array(repeating: GridItem(.fixed(40), spacing: 8), count: 7)
let columns = Array(repeating: GridItem(.fixed(40.adjustedW), spacing: 8), count: 7)
private let calendarCellWidth: CGFloat = 40.adjustedW
private let calendarCellHeight: CGFloat = 40.adjustedH
private let calendarRowSpacing: CGFloat = 8.adjustedH
let weekdays: [String] = ["", "", "", "", "", "", ""]
let columns = Array(repeating: GridItem(.fixed(calendarCellWidth), spacing: calendarRowSpacing), count: 7)
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`
around lines 35 - 40, The columns GridItem currently hardcodes dimensions;
update its definition to use the existing constants instead: replace the
hardcoded 40.adjustedW with calendarCellWidth and the spacing 8 with
calendarRowSpacing so the columns array references calendarCellWidth and
calendarRowSpacing (symbols: calendarCellWidth, calendarRowSpacing, columns) for
consistent, maintainable sizing.

Copy link
Contributor

Choose a reason for hiding this comment

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

요기 spacing에도 기기대응 해야하나요?


var body: some View {
VStack {
Spacer()
.frame(height: 38.adjustedH)

calendarHeader
dateGridsView
Spacer()

if viewModel.isEmptyProcedureList() {
emptyScheduleView
} else {
scheduleListContainerView
}

Spacer()
}
.task (id: viewModel.currentMonth){
if calendarMode == .none {
Expand All @@ -66,7 +74,7 @@ extension CalendarView {
VStack {
HStack {
Image(.chevronLeft)
.frame(width: 40, height: 40)
.frame(width: 40.adjustedW, height: 40.adjustedH)
.scaledToFit()
.onTapGesture {
viewModel.currentMonth -= 1
Expand All @@ -80,30 +88,32 @@ extension CalendarView {
Spacer()

Image(.chevronRight)
.frame(width: 40, height: 40)
.frame(width: 40.adjustedW, height: 40.adjustedH)
.scaledToFit()
.onTapGesture {
viewModel.currentMonth += 1
viewModel.selectedDate = viewModel.firstDateOfCurrentMonth()
}
}
.padding(.horizontal, 11)
.padding(.top, 38)

.padding(.horizontal, 11.adjustedW)

HStack(spacing: 8) {
ForEach(weekdays, id: \.self) { weekday in
TypographyText(weekday, style: .body1_r_14, color: .gray800)
.frame(width: 40, height: 40)
.frame(width: 40.adjustedW, height: 40.adjustedH)
}
}
.padding(.horizontal, 23)
.padding(.horizontal, 23.adjustedH)
}
}

private var dateGridsView: some View {
LazyVGrid(columns: columns) {
ForEach(viewModel.getDatesArray()) { value in
let dates = viewModel.getDatesArray()
let rowCount = dates.count / 7

return VStack(spacing: 0) {
LazyVGrid(columns: columns, spacing: calendarRowSpacing) {
ForEach(dates) { value in
if value.day != -1 {
CalendarCellView(
value: value,
Expand All @@ -127,11 +137,18 @@ extension CalendarView {
}
}
} else {
Text("").hidden()
Color.clear
.frame(width: calendarCellWidth, height: calendarCellHeight)
}
}
}

if rowCount == 4 {
Spacer()
.frame(height: calendarCellHeight + calendarRowSpacing)
}
}
.padding(.horizontal, 23)
.padding(.horizontal, 23.adjustedW)
}

private var scheduleListContainerView: some View {
Expand All @@ -157,7 +174,7 @@ extension CalendarView {
}
}
.frame(height: 40.adjustedH)
.padding(.horizontal, 20)
.padding(.horizontal, 20.adjustedW)
.padding(.top, 8)

ZStack {
Expand Down Expand Up @@ -203,31 +220,31 @@ extension CalendarView {
}

GradientBox(isTop: true)
.frame(height: 56)
.frame(height: 56.adjustedH)
.allowsHitTesting(false)
.opacity(shouldShowGradientTop ? 1 : 0)
.frame(maxHeight: .infinity, alignment: .top)

GradientBox(isTop: false)
.frame(height: 92)
.frame(height: 92.adjustedH)
.allowsHitTesting(false)
.opacity(shouldShowGradientBottom ? 1 : 0)
.frame(maxHeight: .infinity, alignment: .bottom)
}

.frame(height: scrollAreaHeight.adjustedH)
.padding(.top, 6)
.padding(.horizontal, 19)
.padding(.bottom, 12)
.padding(.top, 6.adjustedW)
.padding(.horizontal, 19.adjustedW)
.padding(.bottom, 12.adjustedH)
}
.background(
RoundedRectangle(cornerRadius: 10)
.fill(.gray0)
.cherrishShadow()
)
.padding(.top, 20)
.padding(.horizontal, 25)
.padding(.bottom, 18)
.padding(.top, 20.adjustedH)
.padding(.horizontal, 25.adjustedW)
.padding(.bottom, 18.adjustedH)
}

private var emptyScheduleView: some View {
Expand All @@ -239,8 +256,8 @@ extension CalendarView {

TypographyText("오늘 예정된 일정이 없어요.", style: .body1_r_14, color: .gray600)
}
.padding(.top, 50)
.padding(.horizontal, 65)
.padding(.top, 50.adjustedH)
.padding(.horizontal, 65.adjustedW)

Spacer()
.frame(height: 38.adjustedH)
Expand All @@ -257,7 +274,7 @@ extension CalendarView {
)
}
)
.padding(.horizontal, 24)
.padding(.horizontal, 24.adjustedW)

Spacer()
.frame(height: 24.adjustedH)
Expand All @@ -268,9 +285,9 @@ extension CalendarView {
.cherrishShadow()
)
.frame(width: 326.adjustedW, height: 264.adjustedH)
.padding(.top, 20)
.padding(.horizontal, 25)
.padding(.bottom, 30)
.padding(.top, 20.adjustedH)
.padding(.horizontal, 25.adjustedW)
.padding(.bottom, 30.adjustedH)
}

private var downTimeRangeIcons: some View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,21 +136,29 @@ extension CalendarViewModel {
}

private func extractDate(currentMonth: Int) -> [DateValue] {
let calendar = Calendar.current
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(identifier: "Asia/Seoul")!

let currentMonth = getCurrentMonth(addingMonth: currentMonth)
var days = currentMonth.getAllDates().compactMap { date -> DateValue in
let day = calendar.component(.day, from: date)
return DateValue(day: day, date: date)
}
let targetMonthDate = getCurrentMonth(addingMonth: currentMonth)
let startOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: targetMonthDate))!
let daysInMonth = calendar.range(of: .day, in: .month, for: startOfMonth)?.count ?? 0

let firstWeekday = calendar.component(.weekday, from: days.first?.date ?? Date())
let firstWeekday = calendar.component(.weekday, from: startOfMonth)
let leading = firstWeekday - 1
let totalCells = leading + daysInMonth
let rows = Int(ceil(Double(totalCells) / 7.0))

for _ in 0 ..< firstWeekday - 1 {
days.insert(DateValue(day: -1, date: Date()), at: 0)
}
let gridStart = calendar.date(byAdding: .day, value: -leading, to: startOfMonth)!

return days
return (0..<(rows * 7)).map { i in
let raw = calendar.date(byAdding: .day, value: i, to: gridStart)!
let date = calendar.startOfDay(for: raw)

let isInTargetMonth = calendar.isDate(date, equalTo: targetMonthDate, toGranularity: .month)
let day = isInTargetMonth ? calendar.component(.day, from: date) : -1

return DateValue(day: day, date: date)
}
}
Comment on lines 138 to 162
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

캘린더 그리드 생성 로직 개선 승인

단일 패스로 캘린더 그리드를 생성하는 깔끔한 리팩토링입니다. 타임존 처리가 일관성 있게 Asia/Seoul로 적용되었습니다.

참고: Line 140, 143, 151, 154에서 force unwrap(!)이 사용되고 있습니다. TimeZone(identifier: "Asia/Seoul")과 캘린더 연산은 일반적으로 안전하지만, 방어적 코딩을 위해 guard let을 고려해 볼 수 있습니다.

♻️ 방어적 코딩 예시 (선택사항)
 private func extractDate(currentMonth: Int) -> [DateValue] {
     var calendar = Calendar(identifier: .gregorian)
-    calendar.timeZone = TimeZone(identifier: "Asia/Seoul")!
+    calendar.timeZone = TimeZone(identifier: "Asia/Seoul") ?? .current
     
     let targetMonthDate = getCurrentMonth(addingMonth: currentMonth)
-    let startOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: targetMonthDate))!
+    guard let startOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: targetMonthDate)) else {
+        return []
+    }
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`
around lines 138 - 162, The method extractDate uses several force-unwraps
(TimeZone(identifier: "Asia/Seoul")!, calendar.date(... )!,
calendar.date(byAdding:...)!, etc.); change these to safe unwrapping: guard-let
the TimeZone and create a Calendar only if the TZ exists, and guard-let the
computed dates (startOfMonth, gridStart, raw dates) where applicable, returning
an empty array or handling the error path if any creation fails; update
references to getCurrentMonth, startOfMonth, and gridStart to use the safely
unwrapped values so no '!' remains in extractDate.


private func mapToDowntimeDays(procedure: ProcedureDowntimeEntity) {
Expand Down
24 changes: 14 additions & 10 deletions Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,17 @@ private struct HeaderLogoView: View {
}

private struct ChallengeCardEmptyView: View {

let challengeBarImageName: String
private let buttonState: ButtonState = .active
@EnvironmentObject var tabBarCoordinator: TabBarCoordinator

var body: some View {
VStack(spacing: 0) {
HStack(spacing: 0) {
TypographyText("챌린지를 시작해봐요!", style: .body1_m_14, color: .gray700)
.padding(.top, 18.adjustedH)

Spacer()
}
.padding(.leading, 18.adjustedW)
Expand All @@ -103,7 +105,7 @@ private struct ChallengeCardEmptyView: View {
leadingIcon: nil,
trailingIcon: nil
) {

tabBarCoordinator.switchTab(tab: .challenge)
}
.padding(.horizontal, 24.adjustedW)
.padding(.top, 10.adjustedH)
Expand Down Expand Up @@ -235,7 +237,7 @@ private struct PlanBoxView: View {
.cherrishShadow()
)
.padding(.horizontal, 24.adjustedW)

}

private var emptyStateView: some View {
Expand All @@ -251,14 +253,15 @@ private struct PlanBoxView: View {
RoundedRectangle(cornerRadius: 10.adjustedW)
.strokeBorder(.gray400, lineWidth: 1.adjustedW)
)
.padding(.top, 8.adjustedH)
.padding(.horizontal, 15.adjustedW)
.padding(.top, 8.adjustedH)
.padding(.horizontal, 15.adjustedW)
}
}

private struct UpcomingBoxView: View {
@ObservedObject var viewModel: HomeViewModel
private let buttonState: ButtonState = .active
@EnvironmentObject var tabBarCoordinator: TabBarCoordinator

private func pinStyle(for index: Int, totalCount: Int) -> (circleColor: Color, lineTopColor: Color, lineBottomColor: Color) {
let red600 = Color.red600
Expand All @@ -275,7 +278,7 @@ private struct UpcomingBoxView: View {
case 1:
let lineBottomColor = totalCount >= 3 ? red300 : gray0
result = (red500, red500, lineBottomColor)
default:
default:
result = (red300, red300, gray0)
}

Expand Down Expand Up @@ -310,9 +313,9 @@ private struct UpcomingBoxView: View {
)
.cherrishShadow()
.padding(.horizontal, 24.adjustedW)

}

private var upcomingListView: some View {
VStack(spacing: 0) {
ForEach(Array(viewModel.upcomingItems.enumerated()), id: \.element.id) { index, item in
Expand All @@ -339,8 +342,9 @@ private struct UpcomingBoxView: View {
.padding(.top, 11.adjustedH)
.padding(.bottom, 12.adjustedH)
}

private var emptyStateView: some View {

VStack(spacing: 0) {
Image("illustration_noschedule")
.padding(.top, 24.adjustedH)
Expand All @@ -354,7 +358,7 @@ private struct UpcomingBoxView: View {
leadingIcon: nil,
trailingIcon: nil
) {

tabBarCoordinator.switchTab(tab: .calendar)
}
.padding(.horizontal, 24.adjustedW)
.padding(.top, 32.adjustedH)
Expand Down