Conversation
|
""" Walkthrough이 변경 사항은 추천 루틴(RecommendedRoutine) 기능의 전체적인 도메인, 데이터, 프레젠테이션 계층을 아우르는 신규 기능 도입 및 구조 개선에 집중되어 있습니다. 주요 내용은 추천 루틴 관련 엔티티, DTO, 저장소, 유스케이스, 뷰모델, UI 컴포넌트, DI 등록, Enum 확장 및 리소스 추가 등입니다. 또한, 기존 코드의 일부 리팩토링과 불필요한 import 제거, UI 개선이 포함됩니다. Changes
Sequence Diagram(s)sequenceDiagram
participant View as RecommendedRoutineView
participant ViewModel as RecommendedRoutineViewModel
participant UseCase as RecommendedRoutineUseCase
participant Repo as RecommendedRoutineRepository
participant Network as NetworkService
View->>ViewModel: fetchRecommendedRoutines()
ViewModel->>UseCase: fetchRecommendedRoutines()
UseCase->>Repo: fetchRecommendedRoutines()
Repo->>Network: request(RecommendedRoutineEndpoint.fetchRecommendedRoutines)
Network-->>Repo: RecommendedRoutineDictionaryResponseDTO
Repo-->>UseCase: [RecommendedRoutineEntity]
UseCase-->>ViewModel: [RecommendedRoutineEntity]
ViewModel-->>View: 추천 루틴 데이터 갱신
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40분 Possibly related PRs
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (6)
Projects/DataSource/Sources/Repository/RecommendedRoutineRepository.swift (1)
10-12: Repository 구현이 적절하지만 싱글톤 사용에 대한 고려가 필요합니다.
NetworkService.shared싱글톤 사용은 기존 코드베이스와 일관성을 유지하지만, 테스트 가능성 측면에서는 의존성 주입이 더 나을 수 있습니다.선택적 개선사항: 향후 테스트 용이성을 위해 생성자를 통한 의존성 주입 고려:
final class RecommendedRoutineRepository: RecommendedRoutineRepositoryProtocol { private let networkService: NetworkServiceProtocol init(networkService: NetworkServiceProtocol = NetworkService.shared) { self.networkService = networkService } }Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift (1)
17-29: SwiftLint 경고를 해결해주세요.현재 이니셜라이저는 Swift가 자동으로 생성하는 memberwise initializer와 동일합니다. 명시적인 이니셜라이저가 필요한 특별한 이유가 없다면 제거하는 것을 권장합니다.
다음과 같이 이니셜라이저를 제거할 수 있습니다:
- init( - id: Int, - mainTitle: String, - subTitle: String?, - routineCategory: RoutineCategoryType, - routineLevel: RoutineLevelType - ) { - self.id = id - self.mainTitle = mainTitle - self.subTitle = subTitle - self.routineCategory = routineCategory - self.routineLevel = routineLevel - }Projects/Presentation/Sources/RecommendedRoutine/Model/RoutineCategoryType.swift (1)
27-27: 타이틀 네이밍 개선 고려"나가봐요_제보" 타이틀은 개발/디버깅용으로 보입니다. 실제 사용자에게 노출되지 않는다면 괜찮지만, 더 의미있는 이름으로 변경하는 것을 고려해보세요.
Projects/Presentation/Sources/Common/Component/FloatingButton.swift (1)
18-19: 상태 관리 개선 제안
isToggled프로퍼티를 private(set)으로 변경하여 외부에서 읽기는 가능하지만 수정은 toggle() 메서드를 통해서만 가능하도록 하는 것을 고려해보세요.- private var isToggled: Bool = false + private(set) var isToggled: Bool = falseProjects/Presentation/Sources/Common/Component/FloatingMenuView.swift (1)
2-2: 파일명 불일치주석의 파일명이 실제 파일명과 일치하지 않습니다.
-// FloatingMenu.swift +// FloatingMenuView.swiftProjects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift (1)
65-67: TODO 주석 처리 필요에러 토스트 메시지 표시를 위한 TODO가 있습니다. 이전 학습 내용에 따르면 CustomAlertView를 만들어 에러 처리를 할 계획이 있으신 것으로 알고 있습니다.
에러 처리를 위한 토스트 메시지 구현이나 CustomAlertView 작성을 도와드릴까요? 아니면 이 작업을 추적하기 위한 새 이슈를 생성할까요?
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
Projects/Presentation/Resources/Images.xcassets/add_routine_icon.imageset/add_routine_icon.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/add_routine_icon.imageset/add_routine_icon@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/add_routine_icon.imageset/add_routine_icon@3x.pngis excluded by!**/*.png
📒 Files selected for processing (32)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift(1 hunks)Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift(1 hunks)Projects/DataSource/Sources/DTO/RecommendedRoutineDictionaryResponseDTO.swift(1 hunks)Projects/DataSource/Sources/DTO/RecommendedRoutineListResponseDTO.swift(1 hunks)Projects/DataSource/Sources/Endpoint/AuthEndpoint.swift(0 hunks)Projects/DataSource/Sources/Endpoint/OnboardingEndpoint.swift(0 hunks)Projects/DataSource/Sources/Endpoint/RecommendedRoutineEndpoint.swift(1 hunks)Projects/DataSource/Sources/Persistence/KeychainStorage.swift(1 hunks)Projects/DataSource/Sources/Repository/AuthRepository.swift(1 hunks)Projects/DataSource/Sources/Repository/RecommendedRoutineRepository.swift(1 hunks)Projects/Domain/Sources/DomainDependencyAssembler.swift(1 hunks)Projects/Domain/Sources/Entity/Enum/RoutineCategoryType.swift(1 hunks)Projects/Domain/Sources/Entity/Enum/RoutineLevelType.swift(1 hunks)Projects/Domain/Sources/Entity/RecommendedRoutineEntity.swift(1 hunks)Projects/Domain/Sources/Protocol/Repository/RecommendedRoutineRepositoryProtocol.swift(1 hunks)Projects/Domain/Sources/Protocol/UseCase/RecommendedRoutineUseCaseProtocol.swift(1 hunks)Projects/Domain/Sources/UseCase/RecommendedRoutine/RecommendedRoutineUseCase.swift(1 hunks)Projects/Presentation/Resources/Images.xcassets/add_routine_icon.imageset/Contents.json(1 hunks)Projects/Presentation/Sources/Common/Component/FloatingButton.swift(1 hunks)Projects/Presentation/Sources/Common/Component/FloatingMenuView.swift(1 hunks)Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift(1 hunks)Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift(2 hunks)Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift(2 hunks)Projects/Presentation/Sources/RecommendedRoutine/Model/RoutineCategoryType.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/Model/RoutineLevelType.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RecommendedRoutineCardView.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RegisterEmotionButton.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryButton.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryView.swift(4 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelButton.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift(10 hunks)Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift(3 hunks)
💤 Files with no reviewable changes (2)
- Projects/DataSource/Sources/Endpoint/AuthEndpoint.swift
- Projects/DataSource/Sources/Endpoint/OnboardingEndpoint.swift
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Domain/Sources/DomainDependencyAssembler.swift (1)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (3)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift (1)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
🧬 Code Graph Analysis (10)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
register(14-16)resolve(18-25)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
register(14-16)
Projects/Domain/Sources/Protocol/UseCase/RecommendedRoutineUseCaseProtocol.swift (1)
Projects/Domain/Sources/UseCase/RecommendedRoutine/RecommendedRoutineUseCase.swift (1)
fetchRecommendedRoutines(15-18)
Projects/Domain/Sources/DomainDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
register(14-16)resolve(18-25)
Projects/Domain/Sources/Protocol/Repository/RecommendedRoutineRepositoryProtocol.swift (1)
Projects/Domain/Sources/UseCase/RecommendedRoutine/RecommendedRoutineUseCase.swift (1)
fetchRecommendedRoutines(15-18)
Projects/Presentation/Sources/Common/Component/FloatingButton.swift (1)
Projects/Presentation/Sources/Common/Component/FloatingMenuView.swift (2)
configureAttribute(41-56)configureLayout(58-86)
Projects/DataSource/Sources/Repository/RecommendedRoutineRepository.swift (2)
Projects/DataSource/Sources/NetworkService/NetworkService.swift (1)
request(17-47)Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift (1)
toRecommendedRoutineEntity(25-42)
Projects/DataSource/Sources/Endpoint/RecommendedRoutineEndpoint.swift (1)
Projects/DataSource/Sources/Repository/RecommendedRoutineRepository.swift (1)
fetchRecommendedRoutines(13-25)
Projects/Presentation/Sources/Common/Component/FloatingMenuView.swift (2)
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (1)
floatingMenuDidTapRegisterRoutineButton(271-278)Projects/Presentation/Sources/Common/Component/FloatingButton.swift (2)
configureAttribute(31-38)configureLayout(40-47)
Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift (3)
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (1)
fetchRecommendedRoutines(189-209)Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift (1)
toRecommendedRoutine(33-41)Projects/Shared/Sources/Logger/Logger.swift (1)
log(13-22)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift
[Warning] 17-17: This memberwise initializer would be synthesized automatically - you do not need to define it
(unneeded_synthesized_initializer)
Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift
[Warning] 65-65: TODOs should be resolved (에러 토스트 메시지 보여주기)
(todo)
🔇 Additional comments (41)
Projects/DataSource/Sources/Persistence/KeychainStorage.swift (1)
63-70: 불변성 확보를 위한let전환 👍
baseQuery(for:)내부에서 생성된 딕셔너리를 수정하지 않으므로var→let전환이 타당합니다. 행위(호출 측의 가변 사본 생성)에는 영향이 없고, 함수 내부에서의 불필요한 가변성을 제거해 코드 안정성과 의도를 명확히 했습니다. 추가 조치 없이 그대로 진행하셔도 됩니다.Projects/Presentation/Sources/RecommendedRoutine/View/Component/RegisterEmotionButton.swift (1)
16-16: UI 개선이 적절합니다.플러스 아이콘 크기를 8에서 10으로 증가시켜 가시성을 향상시킨 변경사항입니다. 사용자 경험 개선에 도움이 될 것으로 보입니다.
Projects/Domain/Sources/Protocol/UseCase/RecommendedRoutineUseCaseProtocol.swift (1)
8-12: 깔끔한 프로토콜 설계입니다.클린 아키텍처 원칙을 잘 따르고 있으며, async throws를 사용한 비동기 에러 처리와 명확한 한국어 문서화가 잘 되어 있습니다. 도메인 계층에서의 적절한 추상화입니다.
Projects/Presentation/Resources/Images.xcassets/add_routine_icon.imageset/Contents.json (1)
1-24: 표준 이미지 애셋 구성이 올바릅니다.1x, 2x, 3x 스케일 변형을 포함한 표준 iOS 이미지 애셋 카탈로그 구조가 적절히 구성되어 있습니다. 파일명 규칙과 JSON 구조가 iOS 표준을 따르고 있습니다.
Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift (1)
26-26: 일관된 아이콘 추가입니다.기존 아이콘들과 동일한 패턴을 따르며, 네이밍과 구현 방식이 일관성 있게 작성되었습니다. 적절한 위치에 배치되어 있습니다.
Projects/DataSource/Sources/DTO/RecommendedRoutineDictionaryResponseDTO.swift (1)
8-10: 적절한 DTO 구조 설계입니다.서버 응답을 파싱하기 위한 깔끔한 DTO 구조로, Decodable 프로토콜을 활용한 자동 JSON 파싱이 가능합니다. 카테고리별로 분류된 추천 루틴 데이터를 효과적으로 표현하는 구조입니다.
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryButton.swift (1)
8-8: 도메인 계층 분리에 따른 적절한 import 추가
RoutineCategoryType이 Domain 모듈로 이동됨에 따라 필요한 import를 추가한 것이 적절합니다. 클린 아키텍처 원칙에 따라 Presentation 계층이 Domain 계층에 의존하는 구조로 개선되었습니다.Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelButton.swift (1)
8-8: 도메인 타입 사용을 위한 일관된 import 추가
RoutineLevelType을 사용하기 위해 Domain 모듈을 import한 것이 적절합니다. 다른 컴포넌트들과 일관된 아키텍처 개선 방향입니다.Projects/DataSource/Sources/Repository/AuthRepository.swift (1)
9-12: import 문 정리import 순서 정리가 코드 일관성을 위해 적절합니다. 기능상 변경사항은 없으며 다른 DataSource 파일들과 동일한 정리 작업으로 보입니다.
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RecommendedRoutineCardView.swift (1)
87-87: 라벨과 버튼 간 레이아웃 충돌 방지
labelStackView의 trailing 제약조건을plusButton의 leading에 연결하여 두 UI 요소 간의 겹침을 방지한 것이 적절합니다. 카드 뷰 내에서 라벨과 버튼의 공간 분리가 명확해졌습니다.Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (2)
39-44: 추천 루틴 뷰모델의 의존성 주입 개선
RecommendedRoutineViewModel이RecommendedRoutineUseCaseProtocol에 의존하도록 수정된 것이 적절합니다. API 연동을 위한 UseCase 계층 도입에 맞춰 올바른 의존성 구조를 구성했습니다.
53-55: 루틴 생성 뷰모델 등록 추가FloatingButton에서 루틴 생성 화면으로의 네비게이션을 위해
RoutineCreationViewModel을 등록한 것이 PR 목표와 일치합니다. 현재 의존성이 없는 단순한 등록이 적절합니다.Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1)
28-30: 추천 루틴 리포지토리 의존성 등록이 올바르게 구현되었습니다.기존의 다른 리포지토리 등록 패턴과 일관성 있게 구현되어 있으며, DIContainer를 통한 의존성 주입이 적절히 설정되었습니다.
Projects/Domain/Sources/Entity/Enum/RoutineLevelType.swift (1)
8-12: 루틴 난이도 타입 열거형이 잘 설계되었습니다.의미적으로 명확한 케이스 이름(easy, normal, hard)과 서버 API 형식에 맞는 원시값(LEVEL1-3)을 적절히 매핑하였고, String과 CaseIterable 프로토콜 준수로 활용성이 높습니다.
Projects/DataSource/Sources/DTO/RecommendedRoutineListResponseDTO.swift (1)
8-10: 추천 루틴 응답 DTO가 적절히 구현되었습니다.JSON 응답을 파싱하기 위한 Decodable 프로토콜 준수와 단순한 구조로 목적에 맞게 설계되었습니다.
Projects/Domain/Sources/DomainDependencyAssembler.swift (1)
42-47: 추천 루틴 유스케이스 의존성 등록이 올바르게 구현되었습니다.리포지토리 의존성을 적절히 해결하고, 실패 시 명확한 한국어 오류 메시지와 함께 fatalError를 발생시키는 방식이 기존 OnboardingUseCase 등록 패턴과 일관성 있게 구현되었습니다.
Projects/Domain/Sources/Protocol/Repository/RecommendedRoutineRepositoryProtocol.swift (1)
8-13: 추천 루틴 리포지토리 프로토콜이 잘 설계되었습니다.단일 책임 원칙을 준수하여 추천 루틴 데이터 가져오기만을 담당하며, async/throws를 통한 비동기 처리와 에러 핸들링이 적절합니다. 도메인 엔티티를 반환하여 계층 간 분리도 잘 이루어졌습니다.
Projects/Domain/Sources/UseCase/RecommendedRoutine/RecommendedRoutineUseCase.swift (1)
8-19: 깔끔한 Use Case 구현이지만 현재는 단순한 pass-through 패턴입니다.현재 구현은 Repository를 단순히 호출하는 형태로, Use Case 계층의 역할이 제한적입니다. 향후 비즈니스 로직 추가 시 확장 가능한 구조이지만, 현재로서는 얇은 추상화 계층입니다.
향후 고려사항:
- 데이터 필터링, 정렬, 변환 로직
- 캐싱 전략
- 에러 핸들링 및 로깅
Projects/Domain/Sources/Entity/Enum/RoutineCategoryType.swift (1)
8-16: 잘 구조화된 도메인 enum입니다.서버 API 응답과 일치하는 raw value를 가지고 있고,
CaseIterable프로토콜 준수로 UI에서 활용하기 좋은 구조입니다. 도메인 계층에 위치하여 여러 계층에서 공통으로 사용할 수 있는 좋은 설계입니다.장점:
- 명확한 raw value 매핑
- 타입 안전성 보장
- 여러 계층에서 재사용 가능
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryView.swift (3)
8-8: 도메인 모듈 import 추가가 적절합니다.
RoutineCategoryType이 도메인 계층으로 이동하면서 필요한 import입니다. 프레젠테이션 계층에서 도메인 엔티티를 참조하는 클린 아키텍처 패턴을 잘 따르고 있습니다.
27-27: 중복 코드 제거와 필터링 로직 개선이 좋습니다.
routineCategories상수를 통해 반복되는 필터링과 정렬 로직을 한 곳에 모았습니다..outdoorReport카테고리를 제외하는 비즈니스 요구사항도 명확하게 구현되었습니다.장점:
- 코드 중복 제거
- 성능 개선 (한 번만 계산)
- 유지보수성 향상
46-54: 리팩토링된 코드가 더 깔끔하고 효율적입니다.이전에 반복되던
RoutineCategoryType.allCases.sorted(by: { $0.id < $1.id })로직을routineCategories상수로 대체하여 성능과 가독성이 모두 개선되었습니다.Also applies to: 65-72
Projects/Presentation/Sources/RecommendedRoutine/Model/RoutineLevelType.swift (2)
8-8: 도메인 계층 통합을 위한 적절한 import입니다.
RoutineLevelType이 도메인 계층으로 이동하면서 필요한 변경사항입니다. 이제 여러 계층에서 일관된 타입을 사용할 수 있습니다.
10-26: UI 친화적인 extension 구현이 우수합니다.
SelectableItem프로토콜 준수를 통해 UI에서 필요한id와title속성을 제공합니다. 한국어 제목이 사용자 친화적이고, id 매핑도 순차적으로 잘 구성되었습니다.장점:
- 명확한 id 매핑 (1, 2, 3)
- 직관적인 한국어 제목
- UI 계층과 도메인 계층의 적절한 분리
Projects/DataSource/Sources/Repository/RecommendedRoutineRepository.swift (1)
13-26: 깔끔한 데이터 변환 로직과 적절한 에러 처리입니다.사전 형태의 응답을 순회하면서 카테고리 정보와 함께 DTO를 엔티티로 변환하는 로직이 잘 구현되었습니다. nil 응답에 대한 빈 배열 반환도 적절합니다.
장점:
- 명확한 데이터 변환 플로우
- 카테고리 정보 전달을 통한 컨텍스트 보존
compactMap사용으로 안전한 변환- 적절한 nil 처리
Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift (2)
15-15: 새로운 routineLevel 프로퍼티 추가 잘 되었습니다!도메인 레이어의
RoutineLevelType추가와 일치하며, 추천 루틴의 난이도 정보를 표현할 수 있게 되었습니다.
38-40: 기본값 처리가 적절합니다.
category와level이 nil일 때 안전한 기본값(.recommendation,.easy)을 제공하여 앱이 안정적으로 동작할 수 있도록 했습니다.Projects/Presentation/Sources/RecommendedRoutine/Model/RoutineCategoryType.swift (2)
8-10: Domain-driven design 접근 방식이 훌륭합니다!enum을 Domain 레이어로 이동하고 Presentation에서 UI 관련 프로퍼티만 확장하는 구조가 깔끔합니다. 코드의 모듈화와 재사용성이 향상되었습니다.
15-19:.outdoorReportUI 필터링 확인 완료
RoutineCategoryView.swift에서private let routineCategories = RoutineCategoryType .allCases .filter({ $0 != .outdoorReport }) .sorted(by: { $0.id < $1.id })로
.outdoorReport를 제외하도록 구현되어 있으므로, 별도 수정이 필요 없습니다.Projects/Domain/Sources/Entity/RecommendedRoutineEntity.swift (3)
12-14: 도메인 엔티티 확장이 잘 되었습니다!
category,level,subRoutines프로퍼티 추가로 추천 루틴의 더 풍부한 정보를 표현할 수 있게 되었습니다. Optional 타입 사용으로 서버 데이터의 불완전성도 적절히 고려했습니다.
33-41: SubRoutineEntity 구조체가 적절합니다.간단하고 명확한 구조로 하위 루틴 정보를 표현하며, public 접근 제어자도 올바르게 적용되었습니다.
16-31: 이니셜라이저 업데이트가 완벽합니다.새로운 프로퍼티들이 모두 이니셜라이저에 포함되고 적절히 할당되었습니다. 파라미터 순서와 타입도 일관성 있게 유지되었습니다.
Projects/Presentation/Sources/Common/Component/FloatingButton.swift (2)
11-57: FloatingButton 구현이 훌륭합니다!UI 구성, 애니메이션, 상태 관리가 모두 잘 구현되었습니다. 디자인 시스템을 일관성 있게 사용하고, 부드러운 회전 애니메이션도 사용자 경험을 향상시킵니다.
52-55: 애니메이션 구현이 매끄럽습니다.45도 회전(-.pi/4)과 0.3초 duration으로 자연스러운 플러스 → X 전환 효과를 구현했습니다.
curveEaseInOut옵션으로 부드러운 애니메이션을 제공합니다.Projects/DataSource/Sources/Endpoint/RecommendedRoutineEndpoint.swift (4)
8-10: Endpoint enum 구조가 좋습니다.단일 케이스지만 향후 확장 가능한 구조로 설계되었습니다. 추천 루틴 관련 다른 API가 추가될 때 쉽게 확장할 수 있습니다.
23-25: HTTP 메서드가 적절합니다.추천 루틴을 가져오는 작업에 GET 메서드를 사용한 것이 RESTful API 설계 원칙에 맞습니다.
43-45: 인증 설정이 적절합니다.추천 루틴은 사용자별 맞춤 데이터이므로 인증이 필요한 것이 맞습니다.
17-21: path 프로퍼티 구현 일관성 확인 필요
- OnboardingEndpoint(.registerOnboarding/.registerRecommendedRoutine), AuthEndpoint(.login/.logout 등) 모두
baseURL을 포함한 전체 URL을 반환하고 있습니다.- RecommendedRoutineEndpoint도 같은 패턴으로 의도된 설계인지 검토 부탁드립니다.
- 만약 상대 경로(ex.
"/routines")만 반환하도록 변경할 계획이라면, 모든 Endpoint 구현체를 통일해 리팩토링이 필요합니다.Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift (3)
14-15: 속성 추가 및 타입 변경이 적절합니다.
routineLevel을 옵셔널로 추가하고subRoutines의 타입을[SubRoutineDTO]로 변경한 것이 올바릅니다. 서버 응답에서routineLevel이 없을 수 있는 경우를 고려한 설계입니다.
20-22: 코딩 키 매핑이 정확합니다.서버 응답의 필드명과 Swift 프로퍼티명을 적절히 매핑하고 있습니다. 네이밍 컨벤션을 따라 camelCase로 변환된 것도 좋습니다.
45-57: SubRoutineDTO 구조체와 변환 메서드가 잘 구현되었습니다.구조체명 변경과 변환 메서드 추가가 적절합니다. 간단하고 명확한 구현입니다.
| func toRecommendedRoutineEntity(category: String? = nil) -> RecommendedRoutineEntity { | ||
| var routineCategory: RoutineCategoryType? | ||
| if let category { | ||
| routineCategory = RoutineCategoryType(rawValue: category) | ||
| } | ||
|
|
||
| var level: RoutineLevelType? | ||
| if let routineLevel { | ||
| level = RoutineLevelType(rawValue: routineLevel) | ||
| } | ||
| return RecommendedRoutineEntity( | ||
| id: id, | ||
| title: routineName, | ||
| description: routineDescription) | ||
| description: routineDescription, | ||
| category: routineCategory, | ||
| level: level, | ||
| subRoutines: subRoutines.compactMap({ $0.toSubRoutineEntity() })) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
엔티티 변환 로직 개선을 검토해주세요.
변환 메서드가 향상되었지만 몇 가지 개선점이 있습니다:
RoutineCategoryType(rawValue:)초기화가 실패할 수 있으나 이에 대한 처리가 없습니다.compactMap을 사용하고 있지만SubRoutineDTO.toSubRoutineEntity()가 실패하지 않는 것 같습니다.
다음과 같이 개선을 제안합니다:
func toRecommendedRoutineEntity(category: String? = nil) -> RecommendedRoutineEntity {
var routineCategory: RoutineCategoryType?
if let category {
- routineCategory = RoutineCategoryType(rawValue: category)
+ routineCategory = RoutineCategoryType(rawValue: category)
+ // 로깅 추가: 유효하지 않은 카테고리인 경우
+ if routineCategory == nil {
+ print("Warning: Invalid category value: \(category)")
+ }
}
var level: RoutineLevelType?
if let routineLevel {
- level = RoutineLevelType(rawValue: routineLevel)
+ level = RoutineLevelType(rawValue: routineLevel)
+ // 로깅 추가: 유효하지 않은 레벨인 경우
+ if level == nil {
+ print("Warning: Invalid level value: \(routineLevel)")
+ }
}
return RecommendedRoutineEntity(
id: id,
title: routineName,
description: routineDescription,
category: routineCategory,
level: level,
- subRoutines: subRoutines.compactMap({ $0.toSubRoutineEntity() }))
+ subRoutines: subRoutines.map({ $0.toSubRoutineEntity() }))
}📝 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.
| func toRecommendedRoutineEntity(category: String? = nil) -> RecommendedRoutineEntity { | |
| var routineCategory: RoutineCategoryType? | |
| if let category { | |
| routineCategory = RoutineCategoryType(rawValue: category) | |
| } | |
| var level: RoutineLevelType? | |
| if let routineLevel { | |
| level = RoutineLevelType(rawValue: routineLevel) | |
| } | |
| return RecommendedRoutineEntity( | |
| id: id, | |
| title: routineName, | |
| description: routineDescription) | |
| description: routineDescription, | |
| category: routineCategory, | |
| level: level, | |
| subRoutines: subRoutines.compactMap({ $0.toSubRoutineEntity() })) | |
| } | |
| func toRecommendedRoutineEntity(category: String? = nil) -> RecommendedRoutineEntity { | |
| var routineCategory: RoutineCategoryType? | |
| if let category { | |
| routineCategory = RoutineCategoryType(rawValue: category) | |
| // 로깅 추가: 유효하지 않은 카테고리인 경우 | |
| if routineCategory == nil { | |
| print("Warning: Invalid category value: \(category)") | |
| } | |
| } | |
| var level: RoutineLevelType? | |
| if let routineLevel { | |
| level = RoutineLevelType(rawValue: routineLevel) | |
| // 로깅 추가: 유효하지 않은 레벨인 경우 | |
| if level == nil { | |
| print("Warning: Invalid level value: \(routineLevel)") | |
| } | |
| } | |
| return RecommendedRoutineEntity( | |
| id: id, | |
| title: routineName, | |
| description: routineDescription, | |
| category: routineCategory, | |
| level: level, | |
| subRoutines: subRoutines.map({ $0.toSubRoutineEntity() })) | |
| } |
🤖 Prompt for AI Agents
In Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift around lines 25
to 42, the toRecommendedRoutineEntity method does not handle the case where
RoutineCategoryType(rawValue:) initialization might fail, and compactMap is used
unnecessarily if toSubRoutineEntity() cannot fail. Fix this by safely unwrapping
the RoutineCategoryType initialization and handling failure (e.g., defaulting or
returning nil), and replace compactMap with map if toSubRoutineEntity() always
succeeds.
| guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) else { | ||
| fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.") | ||
| } |
There was a problem hiding this comment.
프로덕션에서 안전하지 않은 fatalError 사용
의존성 주입 실패 시 fatalError를 사용하면 프로덕션에서 앱이 크래시될 수 있습니다. 사용자에게 더 나은 경험을 제공하기 위해 에러를 우아하게 처리하는 것이 좋습니다.
-guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) else {
- fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.")
-}
+guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) else {
+ // TODO: 에러 토스트 메시지 표시
+ BitnagilLogger.log(logType: .error, message: "routineCreationViewModel 의존성이 등록되지 않았습니다.")
+ return
+}📝 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.
| guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) else { | |
| fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.") | |
| } | |
| guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) else { | |
| // TODO: 에러 토스트 메시지 표시 | |
| BitnagilLogger.log(logType: .error, message: "routineCreationViewModel 의존성이 등록되지 않았습니다.") | |
| return | |
| } |
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift
around lines 273 to 275, replace the fatalError call used when dependency
injection for RoutineCreationViewModel fails with a safer error handling
approach. Instead of crashing, handle the failure gracefully by providing
fallback behavior or showing an error message to the user, ensuring the app does
not crash in production.
taipaise
left a comment
There was a problem hiding this comment.
리뷰 노트 확인했습니다!
- 일단은 빠른 개발이 우선이라 생각합니다~! 데스노트에 올리고 나중에 처리하기~
- 의존성 조립 감사합니다~! 의존성 등록을 깜빡했네욥,, 앞으로 바로바로 등록하겠습니다!
- 3, 4 확인했습니다~
추천 루틴에서 "맞춤 추천"인 경우 .. 항상 감정 등록하기 버튼을 보여주어야 합니다.
하지만 제가 맞춤 추천인 경우를 어떻게 판단하냐면요 ..
맞춤 추천에 관련해서는 한 가지 질문이 있습니다!
카테고리를 선택하면, ViewModel에서는 아래와 같은 함수가 실행되는 것으로 보입니다.
private func selectCategory(selectedCategory: RoutineCategoryType) {
let currentCategory = selectedCategorySubject.value
if currentCategory != selectedCategory {
selectedCategorySubject.send(selectedCategory)
let currentLevel = selectedRoutineLevelSubject.value
filterRecommendedRoutines(category: selectedCategory, level: currentLevel)
}
}그럼 selectedCategorySubject는 erasePublisher로 Subject의 정보를 지우고 selectedCategoryPublisher 형태로 View에게 선택한 카테고리 정보를 전달합니다. (ReccommendedRoutineView의 174행 bind 함수)
이때 전달하는 정보인 RoutineCategoryType은 아래와 같이 정의되어 있는데요,
public enum RoutineCategoryType: String, CaseIterable {
case recommendation = "PERSONALIZED"
case outdoor = "OUTING"
case outdoorReport = "OUTING_REPORT"
case wakeup = "WAKE_UP"
case connection = "CONNECT"
case rest = "REST"
case growth = "GROW"
}View가 전달 받은 type이 case recommendation 일 때만 floating button을 표시하고, 이외의 경우에는 hidden 시키는 방식으로 해결은 힘들까요?
- 이 외에는 크리티컬한 이슈 없이 깔끔합니다👍 빠른 반영을 위해 어푸루브 하겠습니다~!
- RecommendedRoutineDTO, RecommendedRoutineDictionaryResponseDTO, RecommendedRoutineListResponseDTO 타입 정의 - RecommendedRoutineEndpoint 생성 - RecommendedRoutineRepositoryProtocol, RecommendedRoutineRepository 정의 및 구현
- RecommendedRoutineUseCaseProtocol, RecommendedRoutineUseCase 정의 및 구현 - RecommendedRoutineEntity 수정 (카테고리, 난이도 값 추가) - RoutineCategoryType, RoutineLevelType 위치 변경 (기존 Presentation -> Domain)
- 감정 등록 버튼이 추천 루틴 목록들보다 상단에 뜨도록 UI 수정 - RecommendedRoutineCardView에서 Label trailing Layout 추가
…ation 모듈 수정 - Presentation 모듈에 있는 RoutineLevelType, RoutineCategoryType가 Domain을 extension해서 필요한 값을 추가하여 사용할 수 있도록 수정 - 그에 따른 import Domain 추가
…-121) - 전체 추천 루틴 받아오는 로직 추가 + 더미 데이터 삭제 - 카테고리 및 난이도에 대해 받아온 루틴 필터링하는 로직 추가 - 의존성 조립 (DataSource, Domain, Presentation)
- RecommendedRoutineView에 FloatingButton, FloatingMenu, dimmedView 추가 - 루틴 등록 화면으로 이동해야 하므로 RoutineCreationViewModel 의존성 등록
187d0e3 to
a579154
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift (1)
55-69: 비동기 API 호출이 적절하게 구현되었습니다.async/await 패턴과 에러 처리가 잘 구현되어 있습니다.
65번 라인의 TODO 관련하여, 이전 학습 내용에 따르면 CustomAlertView를 통한 에러 처리 개선 계획이 있으시죠. 현재 단계에서는 사용자에게 에러 상황을 알리는 임시 방안이 필요할 수 있습니다.
간단한 임시 에러 처리 구현을 도와드릴까요?
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
Projects/Presentation/Resources/Images.xcassets/add_routine_icon.imageset/add_routine_icon.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/add_routine_icon.imageset/add_routine_icon@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/add_routine_icon.imageset/add_routine_icon@3x.pngis excluded by!**/*.png
📒 Files selected for processing (32)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift(1 hunks)Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift(1 hunks)Projects/DataSource/Sources/DTO/RecommendedRoutineDictionaryResponseDTO.swift(1 hunks)Projects/DataSource/Sources/DTO/RecommendedRoutineListResponseDTO.swift(1 hunks)Projects/DataSource/Sources/Endpoint/AuthEndpoint.swift(0 hunks)Projects/DataSource/Sources/Endpoint/OnboardingEndpoint.swift(0 hunks)Projects/DataSource/Sources/Endpoint/RecommendedRoutineEndpoint.swift(1 hunks)Projects/DataSource/Sources/Persistence/KeychainStorage.swift(1 hunks)Projects/DataSource/Sources/Repository/AuthRepository.swift(1 hunks)Projects/DataSource/Sources/Repository/RecommendedRoutineRepository.swift(1 hunks)Projects/Domain/Sources/DomainDependencyAssembler.swift(1 hunks)Projects/Domain/Sources/Entity/Enum/RoutineCategoryType.swift(1 hunks)Projects/Domain/Sources/Entity/Enum/RoutineLevelType.swift(1 hunks)Projects/Domain/Sources/Entity/RecommendedRoutineEntity.swift(1 hunks)Projects/Domain/Sources/Protocol/Repository/RecommendedRoutineRepositoryProtocol.swift(1 hunks)Projects/Domain/Sources/Protocol/UseCase/RecommendedRoutineUseCaseProtocol.swift(1 hunks)Projects/Domain/Sources/UseCase/RecommendedRoutine/RecommendedRoutineUseCase.swift(1 hunks)Projects/Presentation/Resources/Images.xcassets/add_routine_icon.imageset/Contents.json(1 hunks)Projects/Presentation/Sources/Common/Component/FloatingButton.swift(1 hunks)Projects/Presentation/Sources/Common/Component/FloatingMenuView.swift(1 hunks)Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift(1 hunks)Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift(3 hunks)Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift(2 hunks)Projects/Presentation/Sources/RecommendedRoutine/Model/RoutineCategoryType.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/Model/RoutineLevelType.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RecommendedRoutineCardView.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RegisterEmotionButton.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryButton.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryView.swift(4 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelButton.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift(10 hunks)Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift(3 hunks)
💤 Files with no reviewable changes (2)
- Projects/DataSource/Sources/Endpoint/OnboardingEndpoint.swift
- Projects/DataSource/Sources/Endpoint/AuthEndpoint.swift
✅ Files skipped from review due to trivial changes (7)
- Projects/DataSource/Sources/Repository/AuthRepository.swift
- Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelButton.swift
- Projects/Presentation/Resources/Images.xcassets/add_routine_icon.imageset/Contents.json
- Projects/DataSource/Sources/Repository/RecommendedRoutineRepository.swift
- Projects/Domain/Sources/UseCase/RecommendedRoutine/RecommendedRoutineUseCase.swift
- Projects/Presentation/Sources/Common/Component/FloatingMenuView.swift
- Projects/Presentation/Sources/Common/Component/FloatingButton.swift
🚧 Files skipped from review as they are similar to previous changes (21)
- Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryButton.swift
- Projects/DataSource/Sources/Persistence/KeychainStorage.swift
- Projects/Presentation/Sources/RecommendedRoutine/View/Component/RecommendedRoutineCardView.swift
- Projects/DataSource/Sources/DTO/RecommendedRoutineListResponseDTO.swift
- Projects/Presentation/Sources/RecommendedRoutine/View/Component/RegisterEmotionButton.swift
- Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift
- Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
- Projects/Domain/Sources/Protocol/UseCase/RecommendedRoutineUseCaseProtocol.swift
- Projects/Domain/Sources/DomainDependencyAssembler.swift
- Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift
- Projects/Domain/Sources/Protocol/Repository/RecommendedRoutineRepositoryProtocol.swift
- Projects/DataSource/Sources/DTO/RecommendedRoutineDictionaryResponseDTO.swift
- Projects/Domain/Sources/Entity/Enum/RoutineLevelType.swift
- Projects/Domain/Sources/Entity/RecommendedRoutineEntity.swift
- Projects/Presentation/Sources/RecommendedRoutine/Model/RoutineLevelType.swift
- Projects/Domain/Sources/Entity/Enum/RoutineCategoryType.swift
- Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryView.swift
- Projects/DataSource/Sources/Endpoint/RecommendedRoutineEndpoint.swift
- Projects/Presentation/Sources/RecommendedRoutine/Model/RoutineCategoryType.swift
- Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift
- Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift (1)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
🧬 Code Graph Analysis (1)
Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift (2)
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (1)
fetchRecommendedRoutines(199-217)Projects/Shared/Sources/Logger/Logger.swift (1)
log(13-22)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift
[Warning] 65-65: TODOs should be resolved (에러 토스트 메시지 보여주기)
(todo)
Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift
[Warning] 17-17: This memberwise initializer would be synthesized automatically - you do not need to define it
(unneeded_synthesized_initializer)
🔇 Additional comments (7)
Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift (2)
15-15: 새로운 routineLevel 프로퍼티 추가가 적절합니다.API 연동을 위한 난이도 정보 추가가 잘 구현되었습니다. 이니셜라이저에서 기본값을 제거하여 명시적인 값 전달을 강제하는 것도 데이터 일관성 측면에서 좋은 접근입니다.
Also applies to: 21-22, 28-28
37-40: 기본값(.recommendation, .easy) 사용 여부 확인 필요기본값 설정은 nil 안전성을 높이지만, 실제 서버 응답에 category나 level이 없는 경우가 있는지, 그리고
.easy를 기본값으로 사용하는 것이 비즈니스 요구사항에 부합하는지 확인해 주세요.
- 서버에서
category또는level이null/nil로 반환되는 상황이 있는지 백엔드팀에 문의.easy기본값이 적절한지 도메인 로직 검토Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift (5)
9-10: 의존성 주입 구조로 개선되었습니다.UseCase 프로토콜을 통한 의존성 주입으로 테스트 가능성과 코드 결합도가 개선되었습니다. 전체 루틴을 메모리에 캐싱하는 접근도 필터링 성능 향상에 도움이 됩니다.
Also applies to: 26-26, 31-33
14-14: API 호출 최적화가 잘 구현되었습니다.카테고리별 개별 API 호출을 통합된 단일 호출로 변경하여 네트워크 효율성이 향상되었습니다.
43-52: 액션 처리 로직이 명확하게 구조화되었습니다.각 입력 케이스에 대응하는 메서드 호출이 잘 정리되어 있습니다.
72-88: 필터링 방식 개선으로 사용자 경험이 향상되었습니다.API 호출에서 로컬 필터링으로 변경하여 즉시 반응하는 UI를 제공할 수 있게 되었습니다. 중복 선택 방지 로직도 적절히 유지되었습니다.
91-99: 필터링 로직이 효율적으로 구현되었습니다.카테고리와 레벨의 2단계 필터링이 명확하게 구현되어 있고, optional level 처리도 적절합니다.

🌁 Background
이전 추천 루틴 UI PR에서 만든 UI에 서버 통신 로직을 붙였습니다.
추가적으로 FloatingButton을 만들었습니다 !!!
📱 Screenshot
UI 구현보단 서버 통신 로직이 주된 작업이다 보니 !! UI 흐름을 포함한 동영상을 첨부하는 것으로 대체해도 될까요 ??
UI는 추천 루틴 UI PR 해당 PR에서 확인 가능합니다 !!
Simulator.Screen.Recording.-.iPhone.13.mini.-.2025-07-28.at.14.50.57.mp4
👩💻 Contents
✅ Testing
Swagger로 받은 추천 루틴 값과 화면에 보여지는 값이 동일한지 확인했습니다 !!
📝 Review Note
1. FloatingButton
FloatingButton Component를 구현하였습니다. !!
홈, 추천 루틴에서 동일하게 사용되어서 Custom Buttom Sheet처럼 dimmedView까지 하나의 UIViewController로 구현하고 싶었지만 ...
홈 UI에서 이것을 붙이는게 어려웠어서 일단 FloatingButton, FloatingMenu를 Component로 만들고 필요한 곳에서 dimmedView를 선언해서 해주는 것으로 일단. ..... 급한 불을 꺼봤어요 ㅠ.ㅠ
⭐️⭐️⭐️2. RoutineCreationViewModel 의존성 조립⭐️⭐️⭐️
플로팅 버튼에서 루틴 등록을 누르면 RoutineCreationView로 이동해야 합니다 !!!
근데 아직 PresentationDependencyAssembler에 RoutineCreationViewModel이 등록되어 있지 않아 제가 임의로 등록해두었습니다 !!
딩의 작업 내용인데 제가 건드려서 혹시 혼란이 오실까바 말씀 드려놓습니다 !!!!
3. 서버 Response 변수명
개발자 카톡방에서 얘기 나왔듯이 추천 루틴에 대한 변수명을 통일해달라고 요청드렸고, 현재는 Swagger 추천 루틴에 대한 API를 기준으로 구현하였습니다.
추후 확정된 response 변수명을 전달받는다면 온보딩, 추천 루틴에서 동일하게 사용할 수 있도록 수정하겠습니다 !!
4. 나가봐요_제보 카테고리
서버에서 보내주는 추천 루틴의 카테고리 값들은 다음과 같습니다.
하지만 이 중
.outdoorReport는 1차에서 제외되어야 하는 루틴들이므로 추천 루틴을 보여줄 때 해당 카테고리 값은 보여주지 않습니다.또한 RoutineCategoryView에서 추천 루틴들의 카테고리를 보여줄 때 outdoor와 outdoorReport는 동일하게 여겨집니다.
따라서 RoutineCategoryView의 카테고리 정렬에서는 outdoorReport케이스를 제외하고 보여주도록 하였고,
추후 2차에서 제보 기능이 추가된다면 뷰모델이나 UseCase 로직에서 outdoorReport 카테고리에 해당하는 추천 루틴들을 outdoor에 포함하는 로직을 추가하겠습니다.
5. 데이터가 없는 난이도별 추천 루틴
추천 루틴 페이지에서는 추천 루틴을 난이도로 필터링해서 볼 수 있습니다.
하지만 받은 값을 확인해봤을 때 특정 카테고리, 특정 난이도에 모든 데이터가 들어있는 것이 아니었고 -> 이로 인해 특정 카테고리, 특정 난이도를 선택했을 때 아무 데이터도 확인 못하는 경우가 있습니다.
이는 디기개 게시판에 PM, 디자이너에게 질문을 남겨두었습니다 !! (데이터를 추가 혹은 텅뷰 제공)
만약 emptyview를 만들어야 한다면 ... 확인 받은 이후에 만들어보겠습니다.
또한 이로 인해 파생된 이슈 22
정말 솔직하게 숨기고 싶었지만 솔직 케챱 고백드립니다. ............ ..
추천 루틴에서 "맞춤 추천"인 경우 .. 항상 감정 등록하기 버튼을 보여주어야 합니다.
하지만 제가 맞춤 추천인 경우를 어떻게 판단하냐면요 ..
추천 루틴들을 받아올 때 만약에 받아온 추천 루틴의 카테고리가 맞춤 추천이라면 감정 등록 버튼을 보여주도록 하였는데요 ....
말씀드린 것처럼 모든 경우에 데이터가 있는 것이 아니라. ... recommendedRoutines가 empty로 오는 경우가 있습니다. ...
그러면 이제 맞춤 추천인 경우에도 감정 등록 버튼이 안보이는 경우가 생기는거죠 ..ㅜㅜ 네네
ViewModel에 subject가 총 3개가 있는데 level 혹은 category를 선택하면 그 필터링 기준으로 recommendedRoutineSubject에 값을 쏴주고 있걸랑요 ....
카테고리 선택시에는 맞춤 추천이면 항상 showEmotionButton 하게 하면 될텐데 ..
level을 바꾼 경우에 만약에 추천 루틴들이 비어있다면 카테고리를 확인할 방법이 없어요 ...............
하.지.만 .. 데이터가 추가된다면 걱정할 일이 아니니까....
일단 데스노트로 남겨두겠습니다 ㅜㅜ
📣 Related Issue
Summary by CodeRabbit
신규 기능
기능 개선
디자인 및 스타일
리팩터 및 정리
문서 및 리소스