Skip to content

Comments

Style/#49 HomeView 구현#77

Merged
soseoyo12 merged 8 commits intodevelopfrom
style/#49-HomeView
Jan 18, 2026
Merged

Style/#49 HomeView 구현#77
soseoyo12 merged 8 commits intodevelopfrom
style/#49-HomeView

Conversation

@soseoyo12
Copy link
Contributor

구현 내용 iPhone 16 Pro iPhone 13 mini
GIF Screen Recording 2026-01-16 at 9 09 39 PM image
스크린샷 1 image image
스크린샷 2 image image
스크린샷 3 image Screen Recording 2026-01-16 at 9 12 58 PM
스크린샷 4 image image
스크린샷 5 image image
스크린샷 6 image

엠티뷰 추가 (챌린지 레벨이 0일때)

image image image image

작업 내용

  • HomeView 메인 뷰 구현
  • 서브 컴포넌트 4개 추가 (BackgroundGradientView, HeaderLogoView, ChallengeCardView, PlanBoxView, UpcomingBoxView)

설명

offset를 챌린지카드뷰 기준으로 위치조정하여 ZStack으로 올려서 기기대응했습니다.

Image(viewModel.cherryLevelImageName)
                            .resizable()
                            .frame(width: 122.adjustedW, height: 122.adjustedH)
                            .offset(x: -24.adjustedW, y: -67.adjustedH)

3인 maxVisibleCount를 기준으로 더보기 버튼을 관리

private var hasMoreItems: Bool {
        viewModel.allMonthPlanItems.count > maxVisibleCount
    }
if hasMoreItems {
                    Button {
                        withAnimation(.easeInOut(duration: 0.3)) {
                            isExpanded.toggle()
                        }
                    } label: {

아닐때는 3개만

private var visibleItems: [MonthPlanItem] {
        let items = viewModel.allMonthPlanItems
        if isExpanded {
            return items
        } else {
            return Array(items.prefix(maxVisibleCount))
        }
    }

카드수의 따른 그라데이션 변화

private func pinStyle(for index: Int, totalCount: Int) -> (circleColor: Color, lineTopColor: Color, lineBottomColor: Color) {
        let red600 = Color("red_600")
        let red500 = Color("red_500")
        let red300 = Color("red_300")
        let gray0 = Color("gray_0")
        
        let result: (circleColor: Color, lineTopColor: Color, lineBottomColor: Color)
        
        switch index {
        case 0:
            let lineBottomColor = totalCount >= 2 ? red500 : gray0
            result = (red600, red600, lineBottomColor)
        case 1:
            let lineBottomColor = totalCount >= 3 ? red300 : gray0
            result = (red500, red500, lineBottomColor)
        default: 
            result = (red300, red300, gray0)
        }
        
        return result
    }
ForEach(Array(viewModel.upcomingItems.enumerated()), id: \.element.id) { index, item in
                let style = pinStyle(for: index, totalCount: viewModel.upcomingItems.count)
                
                HStack(alignment: .top, spacing: 24.adjustedW) {
                    PinView(
                        circleColor: style.circleColor,
                        lineTopColor: style.lineTopColor,
                        lineBottomColor: style.lineBottomColor
                    )
                    .padding(.leading, 26.adjustedW)
                    .offset(y: -12.adjustedH)
                    
                    OnComingCard(
                        date: item.date,
                        name: item.name,
                        count: item.count,
                        dDay: item.dDay
                    )
                }

@soseoyo12 soseoyo12 requested a review from a team January 16, 2026 14:23
@soseoyo12 soseoyo12 self-assigned this Jan 16, 2026
@soseoyo12 soseoyo12 requested review from sum130, wotjs020708 and y-eonee and removed request for a team January 16, 2026 14:23
@soseoyo12 soseoyo12 linked an issue Jan 16, 2026 that may be closed by this pull request
1 task
@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

📝 Walkthrough

Walkthrough

홈 화면의 대시보드 데이터를 가져오고 표시하는 기능을 구현합니다. 도메인 계층에 HomeInterface 프로토콜, DashboardEntity 모델, FetchDashboardData 유스케이스를 추가하고, 데이터 계층에 DefaultHomeRepository와 MockHomeRepository를 구현합니다. 프레젠테이션 계층에서 HomeView와 HomeViewModel을 새로 작성하여 상태 기반 UI 렌더링을 지원하며, 의존성 주입을 통해 연결합니다. 관련 컴포넌트와 에셋도 함께 업데이트합니다.

Changes

내용 파일 설명
도메인 계층 - 인터페이스 및 모델 Domain/Interface/HomeInterface.swift
Domain/Model/DashboardEntity.swift
Domain/UseCase/FetchDashboardData.swift
HomeInterface 프로토콜 추가, DashboardEntity와 관련 엔티티 구조체 정의, FetchDashboardData 유스케이스 프로토콜 및 DefaultFetchDashboardData 구현 (반환값 누락 이슈 있음)
데이터 계층 - 저장소 Data/Repository/HomeRepository.swift
Data/DataDependencyAssembler.swift
DefaultHomeRepository와 MockHomeRepository 구현, 의존성 주입 설정에 HomeInterface 바인딩 추가
도메인 의존성 주입 Domain/DomainDependencyAssembler.swift FetchDashboardData 의존성 해석 및 등록 로직 추가
프레젠테이션 계층 - 홈 화면 Presentation/Feature/Home/HomeView.swift
Presentation/Feature/Home/HomeViewModel.swift
Presentation/Feature/Home/Coordinator/HomeCoordinatorView.swift
상태 기반 HomeView로 전면 개편, 캐시된 뷰 구성 요소들 추가 (BackgroundGradientView, HeaderLogoView, ChallengeCardView 등), HomeViewModel 구현 (대시보드 로딩, 상태 관리, UI 헬퍼 메서드), navigationDestination 핸들러 추가
프레젠테이션 계층 - 컨포넌트 및 조정자 Presentation/Global/Components/MonthPlan.swift
Presentation/Global/Components/OnComingCard.swift
Presentation/Global/Model/ProcedurePhase.swift
Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinatorView.swift
MonthPlan 태그 배지 너비 제약 제거, OnComingCard에 count 속성 및 계산 속성 추가, ProcedurePhase 열거형 정의 (한글 표시 텍스트 포함), ChallengeCoordinatorView에 onDisappear 수정자 추가
프레젠테이션 의존성 주입 Presentation/PresentationDependencyAssembler.swift FetchDashboardData 의존성 해석 및 HomeViewModel 등록 로직 추가
에셋 카탈로그 Assets.xcassets/home_icon/cherrish_character.imageset/Contents.json
Assets.xcassets/home_icon/home_chellenge_bar0.imageset/Contents.json
Assets.xcassets/home_icon/home_lv.{0,1,2,3,4}.imageset/Contents.json
Assets.xcassets/home_icon/illustration_noschedule.imageset/Contents.json
기존 cherrish_character.imageset 제거, 홈 화면 관련 새 이미지 에셋 6개 추가 (레벨, 챌린지 바, 일정 없음 삽화)

Sequence Diagram

sequenceDiagram
    participant View as HomeView
    participant ViewModel as HomeViewModel
    participant UseCase as FetchDashboardData
    participant Repository as HomeRepository
    participant NetworkService as NetworkService
    
    View->>ViewModel: loadDashboard() [.task]
    activate ViewModel
    ViewModel->>ViewModel: isLoading = true
    ViewModel->>UseCase: execute()
    activate UseCase
    UseCase->>Repository: fetchDashboard()
    activate Repository
    Repository->>NetworkService: GET /dashboard
    activate NetworkService
    NetworkService-->>Repository: DashboardEntity
    deactivate NetworkService
    Repository-->>UseCase: DashboardEntity
    deactivate Repository
    UseCase-->>ViewModel: DashboardEntity
    deactivate UseCase
    ViewModel->>ViewModel: dashboardData = result
    ViewModel->>ViewModel: isLoading = false
    deactivate ViewModel
    View->>View: Render with dashboardData
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45분

Possibly related PRs

  • Setting/#11 네트워크 추상화 #35: HomeRepository가 NetworkService에 의존하고 HomeInterface를 DI에 연결하는데, 해당 PR이 NetworkService 프로토콜과 DefaultNetworkService 구현 및 DI 설정을 제공하므로 직접적인 연관이 있습니다.
  • feat/#50 Month Plan 컴포넌트 추가 #51: 이 PR에서 MonthPlan 컴포넌트의 태그 배지 레이아웃을 수정하는데, 해당 PR이 MonthPlan 컴포넌트를 처음 추가했으므로 관련이 있습니다.
  • Feat/#40 startchallengeview #46: ChallengeCoordinatorView의 selectRoutine 라우트에 onDisappear 핸들링을 추가하는데, 해당 PR이 selectRoutine 라우트와 SelectRoutineView를 도입했으므로 관련이 있습니다.

Suggested reviewers

  • sum130
  • wotjs020708
  • y-eonee
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 HomeView 구현이라는 핵심 변경사항을 명확하게 설명하고 있으며, 제공된 raw_summary의 주요 파일 변경사항(HomeView.swift의 대규모 UI 구현)과 일치합니다.
Description check ✅ Passed PR 설명이 HomeView 구현 내용을 구체적으로 다루고 있으며, 스크린샷, GIF, 구현 상세 설명, 핵심 코드 스니펫을 포함하여 변경사항과 직접적으로 관련되어 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch style/#49-HomeView

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_chellenge_bar0.imageset/Contents.json`:
- Line 4: 파일명 및 폴더에 있는 오타 "chellenge"를 "challenge"로 고치세요: 폴더명
home_chellenge_bar0.imageset → home_challenge_bar0.imageset, 내부 asset 파일
home_chellenge_bar0.svg → home_challenge_bar0.svg, 그리고 Assets.xcassets의
Contents.json의 "filename" 항목을 변경하세요; 변경 후 Xcode에서 해당 에셋을 참조하는 코드(예:
UIImage(named: "home_chellenge_bar0") 또는 Asset Catalog 이름)들을 찾아 모두
"home_challenge_bar0"로 업데이트하고 빌드/캐시 문제 방지를 위해 Clean Build Folder 후 재빌드하세요.

In `@Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift`:
- Around line 22-24: DI registration currently binds HomeInterface to
MockHomeRepository unconditionally; change it so MockHomeRepository is
registered only in debug builds and DefaultHomeRepository is used for
release/production. In the block where DIContainer.shared.register(type:
HomeInterface.self) is called, wrap the MockHomeRepository registration with a
DEBUG conditional compilation check and add an else branch that registers
DefaultHomeRepository(networkService: self.networkService) so production builds
get the real implementation; keep the same constructor args (networkService)
when registering DefaultHomeRepository.

In `@Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift`:
- Around line 28-34: The guard silently returning when
DIContainer.shared.resolve(type: HomeInterface.self) fails hides configuration
errors; update the block that registers FetchDashboardData to surface failures
by adding a fail-fast check and diagnostic logging/assertion: when
DIContainer.shared.resolve(type: HomeInterface.self) returns nil, call
assertionFailure or fatalError in debug (and log an error via your logger in
release) so the missing HomeInterface is immediately obvious; keep the rest of
the registration (DIContainer.shared.register(type: FetchDashboardData.self) {
DefaultFetchDashboardData(repository: homeRepository) }) unchanged, only replace
the silent return with an explicit assert/log/fatal tied to the resolve call.

In `@Cherrish-iOS/Cherrish-iOS/Domain/Model/DashboardEntity.swift`:
- Around line 10-18: DashboardEntity currently stores date and dayOfWeek as
String; change date's type to Date (replace let date: String with let date:
Date) and remove or convert the stored dayOfWeek to a computed property or a
typed enum (e.g., var dayOfWeek: String { /* format from date */ } or DayOfWeek
enum) so domain consumers can perform date operations and comparisons; update
any initializers or mappers that construct DashboardEntity to parse the API
string into a Date and derive dayOfWeek from that Date (ensure
RecentProcedureEntity and UpcomingProcedureEntity usage is unaffected).
- Around line 38-47: Remove the UI presentation property displayText from the
domain model (DashboardEntity/ProcedurePhase) so the Domain layer only contains
business logic, then add a Presentation-layer extension for ProcedurePhase that
reintroduces the displayText computed property (mapping .sensitive, .caution,
.recovery to the Korean strings) in a new file (e.g.,
Presentation/Extensions/ProcedurePhase+Display.swift); update any call sites to
import/use the Presentation module or extension where UI strings are needed.

In `@Cherrish-iOS/Cherrish-iOS/Domain/UseCase/FetchDashboardData.swift`:
- Around line 21-23: The execute() method in FetchDashboardData.swift declares a
return type DashboardEntity but currently does not return a value; update
execute() to await the repository.fetchDashboard() result and return it (e.g.,
capture the result of repository.fetchDashboard() and return that
DashboardEntity) so the function satisfies its declared return type.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/Coordinator/HomeCoordinatorView.swift`:
- Around line 16-21: The switch inside navigationDestination(for:
HomeRoute.self) contains a dead-case for .root because HomeRoute currently
defines only .root and nothing ever calls homeCoordinator.push(.root); either
remove the .root case from the navigationDestination block to eliminate dead
code, or instead expand HomeRoute with real cases (e.g., .detail, .search) and
implement corresponding branches that call ViewFactory.shared.makeHomeView() (or
other view factories) and ensure callers use homeCoordinator.push(...); if you
intend to navigate back to root document that behavior and prefer programmatic
popping, update callers to use homeCoordinator.path.removeLast() (or equivalent)
and add a comment in HomeRoute/navigationDestination explaining intended future
routes.

In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift`:
- Around line 140-151: Replace the hardcoded TypographyText literals in
ChallengeCardView with the view's data properties: use the challengeName
(instead of "웰니스 • 마음챙김") and a formatted challengeRate (instead of "80.9%")
passed into the view; keep the same modifiers (.frame, .padding, .background
with RoundedRectangle and .strokeBorder) on the dynamic TypographyText so
styling/layout remains identical and ensure challengeRate is formatted as a
percentage string (e.g., using String(format:) or NumberFormatter) before
passing to TypographyText.
- Around line 244-257: The background modifier is using .gray0 which is a View
extension (foregroundStyle) defined in View+Color.swift and not a
ShapeStyle/Color, causing a compile error; in HomeView.swift replace the
.background(.gray0) call on the HStack with a proper Color shape style such as
Color("gray_0") (i.e., use Color("gray_0") for background) and keep the existing
overlay/clipShape logic unchanged so the view uses the color resource instead of
the View extension.

In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/OnComingCard.swift`:
- Line 15: The property declaration onTap: (() -> Void)? = nil in OnComingCard
contains a redundant optional nil initializer; remove the "= nil" so the
optional is implicitly nil (i.e., change the declaration of onTap in the
OnComingCard type to just var onTap: (() -> Void)?). This will satisfy SwiftLint
and keep behavior unchanged.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift`:
- Around line 33-39: Currently assemble() silently returns if
DIContainer.shared.resolve(type: FetchDashboardData.self) fails and it captures
fetchDashboardData once into the registration closure; instead, stop the silent
failure and switch to lazy resolution inside the registration closure: in
DIContainer.shared.register(type: HomeViewModel.self) { ... } resolve
FetchDashboardData again (guard let fetch = DIContainer.shared.resolve(type:
FetchDashboardData.self) else { fatalError("Missing FetchDashboardData for
HomeViewModel") }) and then return HomeViewModel(fetchDashboardData: fetch) so
you avoid capturing a single instance and provide an explicit error message that
makes ViewFactory.makeHomeView failures diagnosable; reference
FetchDashboardData, DIContainer.shared.resolve, DIContainer.shared.register,
HomeViewModel, assemble, and ViewFactory.makeHomeView.

In `@Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift`:
- Around line 42-47: The guard in makeHomeView() calls fatalError() without
context; update it to provide a descriptive message (e.g., "Failed to resolve
HomeViewModel from DIContainer") when DIContainer.shared.resolve(type:
HomeViewModel.self) returns nil, and apply the same change to the other related
factory methods (makeOnboardingView(), makeInformationView()) so each fatalError
includes the specific type that failed to resolve.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b13a523 and 64e05f5.

⛔ Files ignored due to path filters (21)
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/cherrish_character.imageset/체리.svg is excluded by !**/*.svg
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/cherrish_logo.imageset/cherrish_logo.svg is excluded by !**/*.svg
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_chellenge_bar0.imageset/home_chellenge_bar0.svg is excluded by !**/*.svg
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.0.imageset/home_lv.0.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.0.imageset/home_lv.0@2x.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.0.imageset/home_lv.0@3x.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.1.imageset/home_lv.1.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.1.imageset/home_lv.1@2x.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.1.imageset/home_lv.1@3x.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.2.imageset/home_lv.2.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.2.imageset/home_lv.2@2x.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.2.imageset/home_lv.2@3x.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.3.imageset/home_lv.3.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.3.imageset/home_lv.3@2x.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.3.imageset/home_lv.3@3x.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.4.imageset/home_lv.4.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.4.imageset/home_lv.4@2x.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.4.imageset/home_lv.4@3x.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/illustration_noschedule.imageset/illustration_noschedule.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/illustration_noschedule.imageset/illustration_noschedule@2x.png is excluded by !**/*.png
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/illustration_noschedule.imageset/illustration_noschedule@3x.png is excluded by !**/*.png
📒 Files selected for processing (22)
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/cherrish_character.imageset/Contents.json
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_chellenge_bar0.imageset/Contents.json
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.0.imageset/Contents.json
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.1.imageset/Contents.json
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.2.imageset/Contents.json
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.3.imageset/Contents.json
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.4.imageset/Contents.json
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/illustration_noschedule.imageset/Contents.json
  • Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift
  • Cherrish-iOS/Cherrish-iOS/Data/Repository/HomeRepository.swift
  • Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift
  • Cherrish-iOS/Cherrish-iOS/Domain/Interface/HomeInterface.swift
  • Cherrish-iOS/Cherrish-iOS/Domain/Model/DashboardEntity.swift
  • Cherrish-iOS/Cherrish-iOS/Domain/UseCase/FetchDashboardData.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinatorView.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/Coordinator/HomeCoordinatorView.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeViewModel.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/MonthPlan.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/OnComingCard.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift
  • Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift
💤 Files with no reviewable changes (1)
  • Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/cherrish_character.imageset/Contents.json
🧰 Additional context used
🧬 Code graph analysis (10)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/Coordinator/HomeCoordinatorView.swift (1)
Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift (1)
  • makeHomeView (42-47)
Cherrish-iOS/Cherrish-iOS/Domain/UseCase/FetchDashboardData.swift (1)
Cherrish-iOS/Cherrish-iOS/Data/Repository/HomeRepository.swift (2)
  • fetchDashboard (17-19)
  • fetchDashboard (78-80)
Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift (1)
Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift (2)
  • resolve (27-34)
  • register (22-25)
Cherrish-iOS/Cherrish-iOS/Domain/Interface/HomeInterface.swift (1)
Cherrish-iOS/Cherrish-iOS/Data/Repository/HomeRepository.swift (2)
  • fetchDashboard (17-19)
  • fetchDashboard (78-80)
Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift (1)
Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift (1)
  • register (22-25)
Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift (1)
Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift (2)
  • resolve (27-34)
  • register (22-25)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeViewModel.swift (1)
Cherrish-iOS/Cherrish-iOS/Domain/UseCase/FetchDashboardData.swift (1)
  • execute (21-24)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/OnComingCard.swift (2)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Shadow.swift (1)
  • body (9-17)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift (3)
  • gray700 (37-39)
  • gray900 (45-47)
  • gray600 (33-35)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift (4)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Shadow.swift (2)
  • body (9-17)
  • cherrishShadow (21-23)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeViewModel.swift (1)
  • loadDashboard (21-33)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift (6)
  • gray700 (37-39)
  • gray0 (9-11)
  • red700 (89-91)
  • red600 (85-87)
  • gray600 (33-35)
  • red500 (81-83)
Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift (1)
  • resolve (27-34)
Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift (1)
Cherrish-iOS/Cherrish-iOS/Core/DIContainer.swift (1)
  • resolve (27-34)
🪛 SwiftLint (0.57.0)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/OnComingCard.swift

[Warning] 15-15: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)

🔇 Additional comments (13)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinatorView.swift (1)

28-30: .selectRoutine에서 다른 화면으로 이동 시 탭 바 깜빡임 가능성 검토 필요

.selectRoutine에서 .loading 또는 .selectMission으로 네비게이션할 경우, onDisappear가 먼저 실행되어 탭 바가 표시된 후, 목적지의 onAppear에서 다시 숨기게 됩니다. 이로 인해 탭 바가 순간적으로 깜빡일 수 있습니다.

또한 .loading.selectMission 라우트에는 onDisappear 핸들러가 없어서 해당 화면에서 뒤로 가기 시 탭 바 복원 로직이 일관되지 않습니다.

다음 시나리오들을 테스트하여 탭 바 상태 전환이 부드럽게 이루어지는지 확인해 주세요:

  1. .selectRoutine.loading 네비게이션 시 깜빡임 여부
  2. .loading 또는 .selectMission에서 뒤로 가기 시 탭 바 복원 여부

필요시 탭 바 상태 관리를 중앙화하거나, 모든 관련 라우트에 일관된 onDisappear 핸들러를 추가하는 것을 고려해 보세요.

Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_chellenge_bar0.imageset/Contents.json (1)

8-15: 2x, 3x 스케일 에셋 파일이 누락되었습니다.

2x와 3x 스케일 항목에 파일명이 지정되지 않았습니다. SVG는 벡터 포맷이므로 Xcode의 "Preserve Vector Data" 옵션을 활성화하면 자동 스케일링이 가능하지만, 해당 설정이 올바르게 되어 있는지 확인이 필요합니다.

다음 사항을 확인해 주세요:

  1. Xcode에서 해당 이미지셋의 "Preserve Vector Data" 옵션이 활성화되어 있는지
  2. 또는 의도적으로 2x/3x 에셋을 생략한 것인지
  3. 고해상도 디바이스(iPhone Pro 등)에서 이미지가 정상적으로 렌더링되는지
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/illustration_noschedule.imageset/Contents.json (1)

1-23: 에셋 카탈로그 구성이 올바릅니다.

1x/2x/3x 스케일과 universal idiom, info 메타데이터 구성이 표준 형식에 맞습니다.

Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.0.imageset/Contents.json (1)

1-23: 에셋 카탈로그 구성이 올바릅니다.

1x/2x/3x 스케일과 universal idiom, info 메타데이터 구성이 표준 형식에 맞습니다.

Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.4.imageset/Contents.json (1)

1-23: 에셋 카탈로그 구성이 올바릅니다.

1x/2x/3x 스케일과 universal idiom, info 메타데이터 구성이 표준 형식에 맞습니다.

Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.2.imageset/Contents.json (1)

1-23: 에셋 카탈로그 구성이 올바릅니다.

1x/2x/3x 스케일과 universal idiom, info 메타데이터 구성이 표준 형식에 맞습니다.

Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.1.imageset/Contents.json (1)

1-23: 에셋 카탈로그 구성이 올바릅니다.

1x/2x/3x 스케일과 universal idiom, info 메타데이터 구성이 표준 형식에 맞습니다.

Cherrish-iOS/Cherrish-iOS/Assets.xcassets/home_icon/home_lv.3.imageset/Contents.json (1)

1-22: 에셋 카탈로그 설정 확인 완료.
스케일/idiom 구성이 정상적입니다.

Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/OnComingCard.swift (1)

13-23: D-Day 표기 개선과 패딩 적용 좋습니다.
가독성 및 UI 일관성 측면에서 변경이 명확합니다.

Also applies to: 31-38, 48-48

Cherrish-iOS/Cherrish-iOS/Domain/Interface/HomeInterface.swift (1)

1-12: HomeInterface 정의 명확합니다.
새 use-case/DI 흐름과 잘 맞습니다.

Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/MonthPlan.swift (1)

25-32: LGTM - 태그 너비 동적 처리 개선

고정 너비(width: 32.adjustedW) 제거로 태그 텍스트 길이에 따라 자연스럽게 크기가 조정됩니다. 다양한 태그 텍스트 길이를 지원하기 위한 적절한 변경입니다.

Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeViewModel.swift (1)

21-33: 로딩 상태 업데이트 흐름이 명확합니다

MainActor에서 로딩/에러 상태를 관리하는 흐름이 잘 정리되어 있습니다.

Cherrish-iOS/Cherrish-iOS/Data/Repository/HomeRepository.swift (1)

17-19: fetchDashboard() 구현이 인터페이스 계약을 잘 지킵니다

두 구현 모두 DashboardEntity를 반환하도록 되어 있어 사용처와 잘 맞습니다.

Also applies to: 78-80

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +10 to +18
struct DashboardEntity {
let date: String
let dayOfWeek: String
let challengeName: String
let cherryLevel: Int
let challengeRate: Double
let recentProcedures: [RecentProcedureEntity]
let upcomingProcedures: [UpcomingProcedureEntity]
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

datedayOfWeek의 타입 검토

Domain 모델에서 날짜를 String으로 저장하면 날짜 연산이나 비교가 어려워집니다. API 응답을 그대로 전달하는 DTO가 아닌 Domain Entity라면, Date 타입 사용을 권장합니다.

만약 현재 구조가 의도된 것이라면 무시해주세요.

🔧 제안하는 수정
 struct DashboardEntity {
-    let date: String
-    let dayOfWeek: String
+    let date: Date
     let challengeName: String
     let cherryLevel: Int
     let challengeRate: Double
     let recentProcedures: [RecentProcedureEntity]
     let upcomingProcedures: [UpcomingProcedureEntity]
+    
+    var formattedDate: String {
+        // Presentation layer에서 포맷팅
+    }
+    
+    var formattedDayOfWeek: String {
+        // Date에서 요일 계산
+    }
 }
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Domain/Model/DashboardEntity.swift` around lines 10
- 18, DashboardEntity currently stores date and dayOfWeek as String; change
date's type to Date (replace let date: String with let date: Date) and remove or
convert the stored dayOfWeek to a computed property or a typed enum (e.g., var
dayOfWeek: String { /* format from date */ } or DayOfWeek enum) so domain
consumers can perform date operations and comparisons; update any initializers
or mappers that construct DashboardEntity to parse the API string into a Date
and derive dayOfWeek from that Date (ensure RecentProcedureEntity and
UpcomingProcedureEntity usage is unaffected).

Comment on lines 38 to 47
var displayText: String {
switch self {
case .sensitive:
return "민감기"
case .caution:
return "주의기"
case .recovery:
return "회복기"
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

displayText는 Presentation 레이어에 적합

displayText computed property는 한국어 UI 문자열을 반환하므로 Presentation 로직입니다. Domain 레이어는 비즈니스 로직만 포함하고, UI 표시 문자열은 View 또는 ViewModel에서 처리하는 것이 Clean Architecture 원칙에 부합합니다.

현재 구조가 팀의 컨벤션이라면 무시해도 됩니다.

🔧 제안하는 수정 (Presentation 레이어로 이동)

Domain 모델에서 displayText 제거:

 enum ProcedurePhase {
     case sensitive
     case caution
     case recovery
-    
-    var displayText: String {
-        switch self {
-        case .sensitive:
-            return "민감기"
-        case .caution:
-            return "주의기"
-        case .recovery:
-            return "회복기"
-        }
-    }
 }

Presentation 레이어에 extension 추가:

// Presentation/Extensions/ProcedurePhase+Display.swift
extension ProcedurePhase {
    var displayText: String {
        switch self {
        case .sensitive: return "민감기"
        case .caution: return "주의기"
        case .recovery: return "회복기"
        }
    }
}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Domain/Model/DashboardEntity.swift` around lines 38
- 47, Remove the UI presentation property displayText from the domain model
(DashboardEntity/ProcedurePhase) so the Domain layer only contains business
logic, then add a Presentation-layer extension for ProcedurePhase that
reintroduces the displayText computed property (mapping .sensitive, .caution,
.recovery to the Korean strings) in a new file (e.g.,
Presentation/Extensions/ProcedurePhase+Display.swift); update any call sites to
import/use the Presentation module or extension where UI strings are needed.

Comment on lines +140 to +151
HStack(spacing: 0) {
TypographyText("웰니스 • 마음챙김", style: .title2_m_16, color: .gray900)

TypographyText("80.9%", style: .body3_m_12, color: .red700)
.frame(height: 19.adjustedH)
.padding(.horizontal, 7.adjustedW)
.background(
RoundedRectangle(cornerRadius: 30.adjustedW)
.strokeBorder(.red600, lineWidth: 1.adjustedW)
)
.padding(.leading, 6.adjustedW)

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

ChallengeCardView가 전달받은 데이터 대신 하드코딩된 값을 표시함

challengeName/challengeRate가 실제 UI에 반영되지 않아 데이터 표시가 깨집니다.

🛠️ 수정 제안
-            HStack(spacing: 0) {
-                TypographyText("웰니스 • 마음챙김", style: .title2_m_16, color: .gray900)
-                
-                TypographyText("80.9%", style: .body3_m_12, color: .red700)
+            HStack(spacing: 0) {
+                TypographyText(challengeName, style: .title2_m_16, color: .gray900)
+                
+                TypographyText(challengeRate, style: .body3_m_12, color: .red700)
                     .frame(height: 19.adjustedH)
                     .padding(.horizontal, 7.adjustedW)
                     .background(
                         RoundedRectangle(cornerRadius: 30.adjustedW)
                             .strokeBorder(.red600, lineWidth: 1.adjustedW)
                     )
                     .padding(.leading, 6.adjustedW)
📝 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
HStack(spacing: 0) {
TypographyText("웰니스 • 마음챙김", style: .title2_m_16, color: .gray900)
TypographyText("80.9%", style: .body3_m_12, color: .red700)
.frame(height: 19.adjustedH)
.padding(.horizontal, 7.adjustedW)
.background(
RoundedRectangle(cornerRadius: 30.adjustedW)
.strokeBorder(.red600, lineWidth: 1.adjustedW)
)
.padding(.leading, 6.adjustedW)
HStack(spacing: 0) {
TypographyText(challengeName, style: .title2_m_16, color: .gray900)
TypographyText(challengeRate, style: .body3_m_12, color: .red700)
.frame(height: 19.adjustedH)
.padding(.horizontal, 7.adjustedW)
.background(
RoundedRectangle(cornerRadius: 30.adjustedW)
.strokeBorder(.red600, lineWidth: 1.adjustedW)
)
.padding(.leading, 6.adjustedW)
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift` around
lines 140 - 151, Replace the hardcoded TypographyText literals in
ChallengeCardView with the view's data properties: use the challengeName
(instead of "웰니스 • 마음챙김") and a formatted challengeRate (instead of "80.9%")
passed into the view; keep the same modifiers (.frame, .padding, .background
with RoundedRectangle and .strokeBorder) on the dynamic TypographyText so
styling/layout remains identical and ensure challengeRate is formatted as a
percentage string (e.g., using String(format:) or NumberFormatter) before
passing to TypographyText.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

api 연결할때 연결하겠습니다 ~

let name: String
let count: Int
let dDay: Int
var onTap: (() -> Void)? = nil
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

옵셔널 초기값 nil은 중복입니다.
SwiftLint 경고를 피하려면 초기값을 제거하세요.

✅ 수정 제안
-    var onTap: (() -> Void)? = nil
+    var onTap: (() -> Void)?
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 15-15: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)

🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/OnComingCard.swift`
at line 15, The property declaration onTap: (() -> Void)? = nil in OnComingCard
contains a redundant optional nil initializer; remove the "= nil" so the
optional is implicitly nil (i.e., change the declaration of onTap in the
OnComingCard type to just var onTap: (() -> Void)?). This will satisfy SwiftLint
and keep behavior unchanged.

Comment on lines 33 to 39
guard let fetchDashboardData = DIContainer.shared.resolve(type: FetchDashboardData.self) else {
return
}

DIContainer.shared.register(type: HomeViewModel.self) {
return HomeViewModel(fetchDashboardData: fetchDashboardData)
}
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

Silent failure와 클로저 캡처 이슈 검토 필요

두 가지 잠재적 문제가 있습니다:

  1. Silent failure: FetchDashboardData resolve 실패 시 조용히 return하여 HomeViewModel이 등록되지 않습니다. 이후 ViewFactory.makeHomeView()에서 fatalError가 발생하면 근본 원인 파악이 어렵습니다.

  2. 클로저 캡처: fetchDashboardDataassemble() 시점에 한 번 resolve되어 클로저에 캡처됩니다. 만약 use-case가 상태를 가지거나, DI 컨테이너가 재구성되는 경우 예상치 못한 동작이 발생할 수 있습니다.

🔧 제안하는 수정
-        guard let fetchDashboardData = DIContainer.shared.resolve(type: FetchDashboardData.self) else {
-            return
+        guard let fetchDashboardData = DIContainer.shared.resolve(type: FetchDashboardData.self) else {
+            assertionFailure("FetchDashboardData를 resolve할 수 없습니다. DomainDependencyAssembler 등록 여부를 확인하세요.")
+            return
         }

또는 lazy resolution 방식으로 변경:

         DIContainer.shared.register(type: HomeViewModel.self) {
-            return HomeViewModel(fetchDashboardData: fetchDashboardData)
+            guard let fetchDashboardData = DIContainer.shared.resolve(type: FetchDashboardData.self) else {
+                fatalError("FetchDashboardData를 resolve할 수 없습니다.")
+            }
+            return HomeViewModel(fetchDashboardData: fetchDashboardData)
         }
📝 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
guard let fetchDashboardData = DIContainer.shared.resolve(type: FetchDashboardData.self) else {
return
}
DIContainer.shared.register(type: HomeViewModel.self) {
return HomeViewModel(fetchDashboardData: fetchDashboardData)
}
guard let fetchDashboardData = DIContainer.shared.resolve(type: FetchDashboardData.self) else {
assertionFailure("FetchDashboardData를 resolve할 수 없습니다. DomainDependencyAssembler 등록 여부를 확인하세요.")
return
}
DIContainer.shared.register(type: HomeViewModel.self) {
return HomeViewModel(fetchDashboardData: fetchDashboardData)
}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift`
around lines 33 - 39, Currently assemble() silently returns if
DIContainer.shared.resolve(type: FetchDashboardData.self) fails and it captures
fetchDashboardData once into the registration closure; instead, stop the silent
failure and switch to lazy resolution inside the registration closure: in
DIContainer.shared.register(type: HomeViewModel.self) { ... } resolve
FetchDashboardData again (guard let fetch = DIContainer.shared.resolve(type:
FetchDashboardData.self) else { fatalError("Missing FetchDashboardData for
HomeViewModel") }) and then return HomeViewModel(fetchDashboardData: fetch) so
you avoid capturing a single instance and provide an explicit error message that
makes ViewFactory.makeHomeView failures diagnosable; reference
FetchDashboardData, DIContainer.shared.resolve, DIContainer.shared.register,
HomeViewModel, assemble, and ViewFactory.makeHomeView.

Copy link
Contributor

@y-eonee y-eonee left a comment

Choose a reason for hiding this comment

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

복잡했을텐데 고생했다 ~ ~
IMG_6534

Comment on lines 17 to 21
func fetchDashboard() async throws -> DashboardEntity {
return createMockDashboard()
}

private func createMockDashboard() -> DashboardEntity {
Copy link
Contributor

Choose a reason for hiding this comment

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

밑에 목 만들었으니까 여기는 지웁시다

Comment on lines 33 to 47
enum ProcedurePhase {
case sensitive
case caution
case recovery

var displayText: String {
switch self {
case .sensitive:
return "민감기"
case .caution:
return "주의기"
case .recovery:
return "회복기"
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

이것은 도메인에 있을게 아닌것 같아요~~ 프레젠테이션으로 옮겨주세요 !!


init(viewModel: HomeViewModel) {
_viewModel = StateObject(wrappedValue: viewModel)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

뷰모델 여기서 init 하지 않아도 DIContainer 프레젠테이션 레이어에서 주입해주고 있어서 괜찮슴

)
.frame(height: 270.adjustedH)

Color("home_gradient2")
Copy link
Contributor

Choose a reason for hiding this comment

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

이거 . 쓰는걸로 바꿉시다 위에도요!!


}

private var contentView: some View {
Copy link
Contributor

Choose a reason for hiding this comment

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

네이밍 고쳐주세염

Comment on lines 376 to 390
VStack(spacing: 0) {
Circle()
.fill(circleColor)
.frame(width: circleSize, height: circleSize)

Rectangle()
.fill(
LinearGradient(
colors: [lineTopColor, lineBottomColor],
startPoint: .top,
endPoint: .bottom
)
)
.frame(width: lineWidth, height: lineLength)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

오 ~~

@Published var isLoading: Bool = false
@Published var errorMessage: String?

private let fetchDashboardData: FetchDashboardData
Copy link
Contributor

Choose a reason for hiding this comment

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

뒤에 useCase 붙이면 좋을 것 같아요~

Comment on lines +46 to +49
var challengeRateText: String {
guard let rate = dashboardData?.challengeRate else { return "0%" }
return String(format: "%.0f%%", rate)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

뜨거운 감자 소수점

Comment on lines 96 to 110
struct MonthPlanItem: Identifiable {
let id: String
let name: String
let dayCount: Int
let tag: String
let isRecent: Bool
}

struct UpcomingItem: Identifiable {
let id: String
let date: String
let name: String
let count: Int
let dDay: Int
}
Copy link
Contributor

Choose a reason for hiding this comment

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

프레젠테이션에 있는 것보단 도메인으로 옮기는게 좋을 것 같습니다 ~

name: procedure.name,
dayCount: procedure.daysSince,
tag: procedure.currentPhase.displayText,
isRecent: true
Copy link
Contributor

Choose a reason for hiding this comment

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

이것이것뭐에요

Copy link
Contributor

@wotjs020708 wotjs020708 left a comment

Choose a reason for hiding this comment

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

고생하셨습니당 어푸푸푸ㅜㅍ
KakaoTalk_Photo_2026-01-09-22-59-50 019

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Repository/HomeRepository.swift`:
- Around line 23-28: MockHomeRepository currently stores a private
networkService in its initializer but never uses it in fetchDashboard(), so
either remove the unused dependency or explicitly document/forward it to
maintain API parity with DefaultHomeRepository; update MockHomeRepository by
deleting the networkService property and removing it from the init signature if
the mock doesn't need networking, or if you want parity keep the property but
mark it unused with a comment and/or use it in a no-op way in fetchDashboard()
to make intent clear (referencing MockHomeRepository, networkService, and
fetchDashboard to locate the change).
- Around line 62-79: The RecentProcedureEntity entries in HomeRepository.swift
have stray trailing commas on their own lines; tidy the mock array by moving
each comma to the end of the previous element line (or removing duplicate extra
commas) so each RecentProcedureEntity initializer is followed directly by a
comma on the same line, ensuring consistent array element formatting for the
RecentProcedureEntity(...) items.

In `@Cherrish-iOS/Cherrish-iOS/Domain/Model/DashboardEntity.swift`:
- Around line 20-24: RecentProcedureEntity in the Domain layer depends on
Presentation's ProcedurePhase enum; move the core ProcedurePhase enum definition
into the Domain module (keep only domain values and logic, remove any
UI/displayText properties), update RecentProcedureEntity to reference the new
Domain ProcedurePhase type, and then in the Presentation layer add an extension
on ProcedurePhase to provide UI strings (displayText) and any presentation-only
helpers; finally update imports/usages across the codebase so Presentation
consumes the Domain enum via the extension and Domain has no Presentation
dependencies.

In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift`:
- Around line 363-387: PinView is declared as a public struct while other
subviews are private; change its declaration to private struct PinView: View to
match the file's access control and limit scope, unless PinView is intentionally
used from other files—if it is used externally, keep it non-private and instead
make the other subviews public as needed; update the declaration of PinView
accordingly and verify all usages within the file still compile.
- Line 11: Replace the force-unwrap in HomeView by resolving the HomeViewModel
safely: in HomeView's initializer call DIContainer.shared.resolve(type:
HomeViewModel.self) and handle the optional result (e.g., guard let vm = ...
else { log an explicit error including context about
PresentationDependencyAssembler and either create a sensible fallback
HomeViewModel or call fatalError with a clear message }). Then assign the
resolved vm to the StateObject via _viewModel = StateObject(wrappedValue: vm)
instead of using DIContainer.shared.resolve(...)! directly; reference HomeView,
DIContainer.shared.resolve(type:), and HomeViewModel to locate the change.
- Around line 261-281: The pinStyle(for:totalCount:) function references
non-existent static colors (Color.gray0, Color.red600, Color.red500,
Color.red300) causing a compile error; fix by replacing those static references
with asset-based initializers (e.g. let gray0 = Color("gray_0"), let red600 =
Color("red600"), let red500 = Color("red500"), let red300 = Color("red300"))
inside pinStyle or alternatively create a Color extension (e.g. extension Color
{ static let gray0 = Color("gray_0") ... }) in View+Color.swift and use those
static properties from pinStyle.

In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeViewModel.swift`:
- Around line 26-30: The catch block only sets errorMessage with
error.localizedDescription but doesn't log the error; update the catch to call
CherrishLogger.error(...) with a clear context (e.g., "Failed to fetch dashboard
data") and include the caught error (and stack if available) alongside keeping
errorMessage = error.localizedDescription; target the catch after
fetchDashboardDataUseCase.execute() in HomeViewModel where dashboardData and
errorMessage are set.
- Around line 35-44: The computed property formattedDate recreates DateFormatter
on every access; cache formatters to avoid the allocation cost by adding
reusable formatters (e.g., private static let inputDateFormatter with dateFormat
"yyyy-MM-dd" and private static let outputDateFormatter with locale
Locale(identifier: "ko_KR") and dateFormat "yyyy년 M월 d일 (E)") in HomeViewModel,
then replace the local formatter usage in formattedDate to parse with
inputDateFormatter and format with outputDateFormatter; keep names
formattedDate, inputDateFormatter, and outputDateFormatter to locate the
changes.
♻️ Duplicate comments (6)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeViewModel.swift (1)

46-49: 소수점 포맷팅 확인 필요

"%.0f%%"는 소수점을 버립니다. 예: 80.9"81%". 기획 의도에 따라 "%.1f%%" (80.9%) 사용을 검토해주세요.

Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift (1)

33-39: Silent failure 및 클로저 캡처 이슈

이전 리뷰에서 지적된 문제가 아직 해결되지 않았습니다:

  1. FetchDashboardData resolve 실패 시 assertionFailure 없이 조용히 return하여 디버깅이 어렵습니다.
  2. fetchDashboardDataassemble() 시점에 한 번 resolve되어 클로저에 캡처됩니다.
권장 수정: lazy resolution 방식
-        guard let fetchDashboardData = DIContainer.shared.resolve(type: FetchDashboardData.self) else {
-            return
-        }
-        
         DIContainer.shared.register(type: HomeViewModel.self) {
-            return HomeViewModel(fetchDashboardDataUseCase: fetchDashboardData)
+            guard let fetchDashboardData = DIContainer.shared.resolve(type: FetchDashboardData.self) else {
+                fatalError("FetchDashboardData를 resolve할 수 없습니다.")
+            }
+            return HomeViewModel(fetchDashboardDataUseCase: fetchDashboardData)
         }
Cherrish-iOS/Cherrish-iOS/Domain/Model/DashboardEntity.swift (1)

10-18: datedayOfWeek의 타입 검토

이전 리뷰에서 지적된 사항입니다. Domain Entity에서 날짜를 String으로 저장하면 날짜 연산이나 비교가 어렵습니다. Date 타입 사용을 권장합니다.

Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift (2)

136-147: ChallengeCardView가 전달받은 데이터 대신 하드코딩된 값을 표시

challengeNamechallengeRate가 파라미터로 전달되지만 실제 UI에서는 하드코딩된 "웰니스 • 마음챙김""80.9%"를 표시합니다.

수정 제안
             HStack(spacing: 0) {
-                TypographyText("웰니스 • 마음챙김", style: .title2_m_16, color: .gray900)
+                TypographyText(challengeName, style: .title2_m_16, color: .gray900)
                 
-                TypographyText("80.9%", style: .body3_m_12, color: .red700)
+                TypographyText(challengeRate, style: .body3_m_12, color: .red700)

246-246: .background(.gray0) 컴파일 오류 가능성

View+Color.swift에서 gray0()foregroundStyle을 설정하는 View extension 함수입니다. background modifier에 ShapeStyle로 사용할 수 없습니다.

수정 제안
-        .background(.gray0)
+        .background(Color("gray_0"))
Cherrish-iOS/Cherrish-iOS/Data/Repository/HomeRepository.swift (1)

17-19: fatalError로 인한 프로덕션 크래시 위험

DefaultHomeRepository.fetchDashboard()fatalError("Not implemented")를 던집니다. DI 설정 오류로 MockHomeRepository 대신 DefaultHomeRepository가 주입되면 앱이 크래시됩니다.

API 구현 전까지 임시로 mock 데이터를 반환하거나, 커스텀 에러를 throw하는 것이 안전합니다.

권장 수정
     func fetchDashboard() async throws -> DashboardEntity {
-        fatalError("Not implemented")
+        throw HomeRepositoryError.notImplemented
     }
+}
+
+enum HomeRepositoryError: Error {
+    case notImplemented
 }

Comment on lines +23 to +28
struct MockHomeRepository: HomeInterface {
private let networkService: NetworkService

init(networkService: NetworkService) {
self.networkService = networkService
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

MockHomeRepository의 미사용 의존성

networkService가 주입되지만 fetchDashboard()에서 사용되지 않습니다. Mock 구현에서는 네트워크가 필요 없으므로 제거하거나, DefaultHomeRepository와의 API 일관성을 위해 유지하는 것인지 명확히 해주세요.

제거 시 수정 제안
 struct MockHomeRepository: HomeInterface {
-    private let networkService: NetworkService
-    
-    init(networkService: NetworkService) {
-        self.networkService = networkService
-    }
+    init() {}
📝 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
struct MockHomeRepository: HomeInterface {
private let networkService: NetworkService
init(networkService: NetworkService) {
self.networkService = networkService
}
struct MockHomeRepository: HomeInterface {
init() {}
}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Repository/HomeRepository.swift` around lines
23 - 28, MockHomeRepository currently stores a private networkService in its
initializer but never uses it in fetchDashboard(), so either remove the unused
dependency or explicitly document/forward it to maintain API parity with
DefaultHomeRepository; update MockHomeRepository by deleting the networkService
property and removing it from the init signature if the mock doesn't need
networking, or if you want parity keep the property but mark it unused with a
comment and/or use it in a no-op way in fetchDashboard() to make intent clear
(referencing MockHomeRepository, networkService, and fetchDashboard to locate
the change).

Comment on lines +62 to +79
,
RecentProcedureEntity(
name: "필러",
daysSince: 8,
currentPhase: .recovery
)
,
RecentProcedureEntity(
name: "필러",
daysSince: 8,
currentPhase: .recovery
)
,
RecentProcedureEntity(
name: "필러",
daysSince: 8,
currentPhase: .recovery
)
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Mock 데이터 포맷팅 정리

trailing comma가 라인 끝에 별도로 위치해 있어 코드가 깔끔하지 않습니다.

포맷팅 수정 제안
                 RecentProcedureEntity(
                     name: "필러",
                     daysSince: 8,
                     currentPhase: .recovery
-                )
-                ,
+                ),
                 RecentProcedureEntity(
📝 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
,
RecentProcedureEntity(
name: "필러",
daysSince: 8,
currentPhase: .recovery
)
,
RecentProcedureEntity(
name: "필러",
daysSince: 8,
currentPhase: .recovery
)
,
RecentProcedureEntity(
name: "필러",
daysSince: 8,
currentPhase: .recovery
)
,
RecentProcedureEntity(
name: "필러",
daysSince: 8,
currentPhase: .recovery
),
RecentProcedureEntity(
name: "필러",
daysSince: 8,
currentPhase: .recovery
),
RecentProcedureEntity(
name: "필러",
daysSince: 8,
currentPhase: .recovery
)
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Repository/HomeRepository.swift` around lines
62 - 79, The RecentProcedureEntity entries in HomeRepository.swift have stray
trailing commas on their own lines; tidy the mock array by moving each comma to
the end of the previous element line (or removing duplicate extra commas) so
each RecentProcedureEntity initializer is followed directly by a comma on the
same line, ensuring consistent array element formatting for the
RecentProcedureEntity(...) items.

Comment on lines +20 to +24
struct RecentProcedureEntity {
let name: String
let daysSince: Int
let currentPhase: ProcedurePhase
}
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find ProcedurePhase file locations
echo "=== ProcedurePhase 파일 위치 ==="
find . -name "*ProcedurePhase*" -type f 2>/dev/null | grep -v ".git" | head -20

echo -e "\n=== Domain 모델에서 ProcedurePhase 사용 확인 ==="
rg -n "ProcedurePhase" --type=swift | head -30

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 514


🏁 Script executed:

#!/bin/bash
# Examine DashboardEntity.swift content
echo "=== DashboardEntity.swift 전체 내용 ==="
fd "DashboardEntity.swift" --type f -x cat -n {} 2>/dev/null

echo -e "\n=== Domain 디렉토리 구조 ==="
find . -path "*/Domain/*" -type f -name "*.swift" 2>/dev/null | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1448


🏁 Script executed:

#!/bin/bash
# Check import statements in DashboardEntity.swift
echo "=== DashboardEntity.swift imports 확인 ==="
fd "DashboardEntity.swift" --type f -x head -20 {} 2>/dev/null

echo -e "\n=== RecentProcedureEntity 정의 확인 ==="
rg -A 5 "struct RecentProcedureEntity" --type=swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1026


🏁 Script executed:

#!/bin/bash
# Check for all imports in DashboardEntity.swift
echo "=== DashboardEntity.swift 전체 import 확인 ==="
rg "^import" --type=swift Cherrish-iOS/Cherrish-iOS/Domain/Model/DashboardEntity.swift

echo -e "\n=== ProcedurePhase 전체 정의 ==="
cat Cherrish-iOS/Cherrish-iOS/Presentation/Global/Model/ProcedurePhase.swift

echo -e "\n=== 다른 Domain 모델들에서 Presentation 타입 사용 여부 ==="
rg "currentPhase\|ProcedurePhase" --type=swift Cherrish-iOS/Cherrish-iOS/Domain/

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 603


ProcedurePhase의 Domain 레이어 이동 필요

RecentProcedureEntityProcedurePhase 타입을 사용하지만, 현재 ProcedurePhasePresentation/Global/Model/에 정의되어 있습니다. Domain 레이어가 Presentation 레이어의 타입에 의존하게 되어 Clean Architecture 원칙을 위반합니다.

권장 해결책:

  1. ProcedurePhase enum을 Domain 레이어로 이동 (displayText 제외)
  2. Presentation 레이어에서 extension으로 UI 문자열 추가

이를 통해 Domain이 순수하게 유지되고, Presentation에서만 UI 관련 로직을 갖게 됩니다.

🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Domain/Model/DashboardEntity.swift` around lines 20
- 24, RecentProcedureEntity in the Domain layer depends on Presentation's
ProcedurePhase enum; move the core ProcedurePhase enum definition into the
Domain module (keep only domain values and logic, remove any UI/displayText
properties), update RecentProcedureEntity to reference the new Domain
ProcedurePhase type, and then in the Presentation layer add an extension on
ProcedurePhase to provide UI strings (displayText) and any presentation-only
helpers; finally update imports/usages across the codebase so Presentation
consumes the Domain enum via the extension and Domain has no Presentation
dependencies.

import SwiftUI

struct HomeView: View {
@StateObject private var viewModel = DIContainer.shared.resolve(type: HomeViewModel.self)!
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

Force unwrap으로 인한 런타임 크래시 위험

DIContainer.shared.resolve(type: HomeViewModel.self)!에서 force unwrap 사용 시, resolve 실패 시 앱이 크래시됩니다. PresentationDependencyAssembler에서 silent return 문제가 해결되지 않으면 이 크래시의 원인 파악이 어렵습니다.

권장 수정: 명시적 에러 처리
-    `@StateObject` private var viewModel = DIContainer.shared.resolve(type: HomeViewModel.self)!
+    `@StateObject` private var viewModel: HomeViewModel = {
+        guard let vm = DIContainer.shared.resolve(type: HomeViewModel.self) else {
+            fatalError("HomeViewModel을 resolve할 수 없습니다. DI 등록을 확인하세요.")
+        }
+        return vm
+    }()
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift` at line
11, Replace the force-unwrap in HomeView by resolving the HomeViewModel safely:
in HomeView's initializer call DIContainer.shared.resolve(type:
HomeViewModel.self) and handle the optional result (e.g., guard let vm = ...
else { log an explicit error including context about
PresentationDependencyAssembler and either create a sensible fallback
HomeViewModel or call fatalError with a clear message }). Then assign the
resolved vm to the StateObject via _viewModel = StateObject(wrappedValue: vm)
instead of using DIContainer.shared.resolve(...)! directly; reference HomeView,
DIContainer.shared.resolve(type:), and HomeViewModel to locate the change.

Copy link
Contributor

Choose a reason for hiding this comment

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

이거 계속 뜨긴하던데 로거찍는 건 어떨까요?!

Comment on lines +261 to +281
private func pinStyle(for index: Int, totalCount: Int) -> (circleColor: Color, lineTopColor: Color, lineBottomColor: Color) {
let red600 = Color.red600
let red500 = Color.red500
let red300 = Color.red300
let gray0 = Color.gray0

let result: (circleColor: Color, lineTopColor: Color, lineBottomColor: Color)

switch index {
case 0:
let lineBottomColor = totalCount >= 2 ? red500 : gray0
result = (red600, red600, lineBottomColor)
case 1:
let lineBottomColor = totalCount >= 3 ? red300 : gray0
result = (red500, red500, lineBottomColor)
default:
result = (red300, red300, gray0)
}

return result
}
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:

#!/bin/bash
# Color.gray0 static 정의 확인
echo "=== Color extension에서 gray0 관련 정의 확인 ==="
rg -n "gray0" --type=swift -B 2 -A 2

echo -e "\n=== Color extension에서 static var 정의 확인 ==="
rg -n "static\s+var\s+gray0" --type=swift

echo -e "\n=== Color("gray_0") 사용 확인 ==="
rg -n 'Color\("gray_0"\)' --type=swift | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 7545


🏁 Script executed:

#!/bin/bash
# Color extension 전체 파일 찾기
echo "=== Color extension 파일 찾기 ==="
find . -type f -name "*.swift" | xargs grep -l "extension Color" | head -10

echo -e "\n=== Color extension에서 red600, red500, red300, gray0 정의 확인 ==="
rg -n "extension Color" --type=swift -A 100 | grep -E "(static\s+var|var\s+)(red600|red500|red300|gray0)" | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 162


🏁 Script executed:

#!/bin/bash
# red600, red500 등의 정의 찾기
echo "=== red600 정의 찾기 ==="
rg -n "red600" --type=swift -B 5 -A 2 | head -40

echo -e "\n=== Color extension 파일 전체 목록 ==="
rg -l "extension Color" --type=swift

echo -e "\n=== Assets.xcassets 구조 확인 ==="
find . -type d -name "*.xcassets" | head -5

echo -e "\n=== ColorSet 파일 확인 ==="
find . -name "*.colorset" | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 5731


🏁 Script executed:

#!/bin/bash
# View+Color.swift 전체 내용 확인
echo "=== View+Color.swift 전체 내용 ==="
cat -n Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift

echo -e "\n=== red_300, red_500, red_600 colorset 확인 ==="
find . -name "red_*.colorset" -type d

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 3452


🏁 Script executed:

#!/bin/bash
# Color를 확장하는 모든 파일 찾기
echo "=== Color 확장 정의 찾기 ==="
rg -n "extension Color" --type=swift -A 50 2>/dev/null

echo -e "\n=== static var 패턴으로 Color 속성 찾기 ==="
rg -n "static.*var.*(red|gray)" --type=swift | head -20

echo -e "\n=== Color 직접 사용 패턴 (Color.red600 등) 찾기 ==="
rg -n "Color\.(red|gray)[0-9]" --type=swift | head -30

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1015


🏁 Script executed:

#!/bin/bash
# 모든 Swift 파일에서 extension Color 검색
echo "=== 전체 extension Color 검색 ==="
find . -name "*.swift" -type f | xargs grep -n "extension Color" 2>/dev/null

echo -e "\n=== Color 관련 파일 모두 확인 ==="
find . -name "*Color*.swift" -type f | head -20

echo -e "\n=== Xcode Build Phase에서 생성된 Color 파일 확인 ==="
find . -name "*.swift" -type f | xargs grep -l "red600\|red500\|red300\|gray0" | head -10

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 944


🏁 Script executed:

#!/bin/bash
# Derived Data나 빌드 생성 파일 확인
echo "=== 프로젝트 구조에서 Color 정의 파일 찾기 ==="
find . -name "*.swift" -type f -exec grep -l "^extension Color" {} \;

echo -e "\n=== HomeView.swift 주변 코드 확인 ==="
sed -n '260,280p' Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift

echo -e "\n=== Color 속성 정의 방식 검색 (shorthand syntax) ==="
rg -n "@color|ColorResource|Color\(" --type=swift Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/ -A 2 -B 2 | head -50

echo -e "\n=== 프로젝트 설정 파일 확인 ==="
find . -name "*.pbxproj" -o -name "*.xcconfig" | head -5

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 5936


🏁 Script executed:

#!/bin/bash
# Swift Package 확인
echo "=== Package.swift 확인 ==="
find . -name "Package.swift" | xargs cat 2>/dev/null

echo -e "\n=== build 폴더 확인 ==="
find . -path "*/build/*" -name "*.swift" 2>/dev/null | head -20

echo -e "\n=== DerivedData 확인 ==="
find . -path "*DerivedData*" -name "*.swift" 2>/dev/null | head -20

echo -e "\n=== 프로젝트 내 모든 Swift 파일 수 ==="
find . -name "*.swift" -type f | wc -l

echo -e "\n=== Color 관련 모든 Swift 파일 목록 ==="
find . -name "*.swift" -type f | xargs grep -l "Color\." | sort

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 835


🏁 Script executed:

#!/bin/bash
# 모든 Swift 파일 전체 검색 - Color static var 정의
echo "=== 모든 Swift 파일에서 static 정의 검색 ==="
find . -name "*.swift" -type f -exec grep -l "static" {} \; | while read f; do
  echo "File: $f"
  grep -n "static.*var\|static.*let" "$f" | grep -i "color\|red\|gray" | head -5
done

echo -e "\n=== 특정 Color 확장 파일 내용 전체 ==="
find . -path "*Global*" -name "*.swift" | grep -i color

echo -e "\n=== HomeView import 확인 ==="
head -20 Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1447


🏁 Script executed:

#!/bin/bash
# 전체 Swift 파일에서 extension Color 검색
echo "=== 전체 Swift 파일 목록 ==="
find . -name "*.swift" -type f

echo -e "\n=== 각 파일에서 Color 관련 static 정의 검색 ==="
find . -name "*.swift" -type f -exec bash -c '
  if grep -q "extension Color\|Color\." "$1"; then
    echo "File: $1"
    grep -n "extension Color\|static.*var.*red\|static.*var.*gray" "$1" | head -5
  fi
' _ {} \;

echo -e "\n=== HomeView.swift에서 Color 사용 부분 ==="
grep -n "let red\|let gray" Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 7106


🏁 Script executed:

#!/bin/bash
# 모든 Swift 파일 내용에서 Color 정의 찾기
echo "=== Color 직접 정의 있는지 검색 ==="
find . -name "*.swift" -type f -exec bash -c '
  content=$(cat "$1")
  if echo "$content" | grep -q "\.red600\|\.red500\|\.red300\|\.gray0"; then
    echo "=== Found Color usage in: $1 ==="
    echo "$content" | grep -n "extension Color\|static.*var\|var.*red600\|var.*gray0" | head -10
  fi
' _ {} \;

echo -e "\n=== Color extension을 정의하는 파일 찾기 ==="
find . -name "*.swift" -type f | xargs grep -l "extension Color"

echo -e "\n=== 모든 .swift 파일에서 Color extension 패턴 찾기 ==="
find . -name "*.swift" -exec grep -H "extension Color" {} \;

echo -e "\n=== 혹시 generated 파일이나 다른 폴더에 Color 정의가 있는지 확인 ==="
find . -type f \( -name "*Color*.swift" -o -name "*color*.swift" \) 2>/dev/null

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1168


🏁 Script executed:

#!/bin/bash
# Macro 관련 파일이나 특수한 설정이 있는지 확인
echo "=== Macro 폴더 확인 ==="
find . -type d -name "*Macro*" -o -name "*Generated*"

echo -e "\n=== Build Phases 확인 ==="
cat ./Cherrish-iOS/Cherrish-iOS.xcodeproj/project.pbxproj | grep -A 5 "Color\|red600\|gray0" | head -30

echo -e "\n=== 혹시 Color extension이 조건부로 정의되었는지 확인 ==="
find . -name "*.swift" -exec grep -B 5 -A 20 "Color.red600\|Color.gray0" {} \; | head -50

echo -e "\n=== XCCodeGen이나 다른 생성 도구 확인 ==="
find . -name "*.yml" -o -name "*.yaml" | head -10

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 2218


Color.gray0 사용 수정 필요

Color.gray0, Color.red600, Color.red500, Color.red300 등의 static 속성이 Color extension에 정의되어 있지 않습니다. 현재 코드는 컴파일되지 않습니다.

Assets.xcassets에 ColorSet 파일이 존재하므로 다음과 같이 수정하세요:

let gray0 = Color("gray_0")
let red600 = Color("red600")
let red500 = Color("red500")
let red300 = Color("red300")

또는 View+Color.swift에 Color extension을 추가하여 static 속성을 정의하는 방식도 가능합니다.

🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift` around
lines 261 - 281, The pinStyle(for:totalCount:) function references non-existent
static colors (Color.gray0, Color.red600, Color.red500, Color.red300) causing a
compile error; fix by replacing those static references with asset-based
initializers (e.g. let gray0 = Color("gray_0"), let red600 = Color("red600"),
let red500 = Color("red500"), let red300 = Color("red300")) inside pinStyle or
alternatively create a Color extension (e.g. extension Color { static let gray0
= Color("gray_0") ... }) in View+Color.swift and use those static properties
from pinStyle.

Comment on lines +363 to 387
struct PinView: View {
var circleColor: Color
var lineTopColor: Color
var lineBottomColor: Color
var circleSize: CGFloat = 10.adjustedW
var lineLength: CGFloat = 65.adjustedH
var lineWidth: CGFloat = 2.adjustedW

var body: some View {
VStack(spacing: 0) {
Circle()
.fill(circleColor)
.frame(width: circleSize, height: circleSize)

Rectangle()
.fill(
LinearGradient(
colors: [lineTopColor, lineBottomColor],
startPoint: .top,
endPoint: .bottom
)
)
.frame(width: lineWidth, height: lineLength)
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

PinView 접근 제어자 확인 필요

다른 subview들은 private struct로 선언되어 있는데, PinViewstruct로 선언되어 있습니다. 다른 화면에서 재사용할 계획이 아니라면 private으로 통일하는 것이 좋습니다.

수정 제안
-struct PinView: View {
+private struct PinView: View {
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift` around
lines 363 - 387, PinView is declared as a public struct while other subviews are
private; change its declaration to private struct PinView: View to match the
file's access control and limit scope, unless PinView is intentionally used from
other files—if it is used externally, keep it non-private and instead make the
other subviews public as needed; update the declaration of PinView accordingly
and verify all usages within the file still compile.

Comment on lines +26 to +30
do {
dashboardData = try await fetchDashboardDataUseCase.execute()
} catch {
errorMessage = error.localizedDescription
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

에러 로깅 추가 권장

error.localizedDescription만 저장하고 있어 디버깅 시 상세 정보가 부족할 수 있습니다. CherrishLogger.error()를 활용하여 에러를 기록하면 좋겠습니다.

권장 수정
         do {
             dashboardData = try await fetchDashboardDataUseCase.execute()
         } catch {
+            CherrishLogger.error(error)
             errorMessage = error.localizedDescription
         }
📝 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
do {
dashboardData = try await fetchDashboardDataUseCase.execute()
} catch {
errorMessage = error.localizedDescription
}
do {
dashboardData = try await fetchDashboardDataUseCase.execute()
} catch {
CherrishLogger.error(error)
errorMessage = error.localizedDescription
}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeViewModel.swift`
around lines 26 - 30, The catch block only sets errorMessage with
error.localizedDescription but doesn't log the error; update the catch to call
CherrishLogger.error(...) with a clear context (e.g., "Failed to fetch dashboard
data") and include the caught error (and stack if available) alongside keeping
errorMessage = error.localizedDescription; target the catch after
fetchDashboardDataUseCase.execute() in HomeViewModel where dashboardData and
errorMessage are set.

Comment on lines +35 to +44
var formattedDate: String {
guard let date = dashboardData?.date else { return "" }
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
guard let parsedDate = formatter.date(from: date) else { return date }

formatter.locale = Locale(identifier: "ko_KR")
formatter.dateFormat = "yyyy년 M월 d일 (E)"
return formatter.string(from: parsedDate)
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

DateFormatter 생성 최적화

formattedDate가 호출될 때마다 DateFormatter가 새로 생성됩니다. DateFormatter는 생성 비용이 높으므로 캐싱하는 것이 좋습니다.

권장 수정
+    private static let inputFormatter: DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "yyyy-MM-dd"
+        return formatter
+    }()
+    
+    private static let outputFormatter: DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.locale = Locale(identifier: "ko_KR")
+        formatter.dateFormat = "yyyy년 M월 d일 (E)"
+        return formatter
+    }()
+    
     var formattedDate: String {
         guard let date = dashboardData?.date else { return "" }
-        let formatter = DateFormatter()
-        formatter.dateFormat = "yyyy-MM-dd"
-        guard let parsedDate = formatter.date(from: date) else { return date }
-        
-        formatter.locale = Locale(identifier: "ko_KR")
-        formatter.dateFormat = "yyyy년 M월 d일 (E)"
-        return formatter.string(from: parsedDate)
+        guard let parsedDate = Self.inputFormatter.date(from: date) else { return date }
+        return Self.outputFormatter.string(from: parsedDate)
     }
📝 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
var formattedDate: String {
guard let date = dashboardData?.date else { return "" }
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
guard let parsedDate = formatter.date(from: date) else { return date }
formatter.locale = Locale(identifier: "ko_KR")
formatter.dateFormat = "yyyy년 M월 d일 (E)"
return formatter.string(from: parsedDate)
}
private static let inputFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
private static let outputFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ko_KR")
formatter.dateFormat = "yyyy년 M월 d일 (E)"
return formatter
}()
var formattedDate: String {
guard let date = dashboardData?.date else { return "" }
guard let parsedDate = Self.inputFormatter.date(from: date) else { return date }
return Self.outputFormatter.string(from: parsedDate)
}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeViewModel.swift`
around lines 35 - 44, The computed property formattedDate recreates
DateFormatter on every access; cache formatters to avoid the allocation cost by
adding reusable formatters (e.g., private static let inputDateFormatter with
dateFormat "yyyy-MM-dd" and private static let outputDateFormatter with locale
Locale(identifier: "ko_KR") and dateFormat "yyyy년 M월 d일 (E)") in HomeViewModel,
then replace the local formatter usage in formattedDate to parse with
inputDateFormatter and format with outputDateFormatter; keep names
formattedDate, inputDateFormatter, and outputDateFormatter to locate the
changes.

Copy link
Contributor

@sum130 sum130 left a comment

Choose a reason for hiding this comment

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

이미 많이 수정하셨군뇨! 수고했어요~~ 굿!!

import SwiftUI

struct HomeView: View {
@StateObject private var viewModel = DIContainer.shared.resolve(type: HomeViewModel.self)!
Copy link
Contributor

Choose a reason for hiding this comment

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

이거 계속 뜨긴하던데 로거찍는 건 어떨까요?!

@soseoyo12 soseoyo12 merged commit e0b17e2 into develop Jan 18, 2026
1 check passed
@soseoyo12 soseoyo12 deleted the style/#49-HomeView branch January 18, 2026 11:58
Kimgyuilli pushed a commit that referenced this pull request Jan 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Style] 홈 뷰

4 participants