Conversation
Walkthrough루틴 완료 상태 업데이트 기능이 도메인, 데이터, 프레젠테이션 계층 전반에 걸쳐 도입되었습니다. 새로운 DTO와 엔티티가 추가되었고, 관련 프로토콜 및 레포지토리에 루틴 완료 업데이트 메서드가 확장되었습니다. Home/RecommendedRoutine 화면의 뷰모델과 뷰에도 연동 로직 및 UI 변경이 반영되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant View as HomeView
participant VM as HomeViewModel
participant UseCase as RoutineUseCase
participant Repo as RoutineRepository
participant Endpoint as RoutineEndpoint
View->>VM: updateRoutineCompletion(updatedRoutine)
VM->>UseCase: updateRoutineCompletion(routines)
UseCase->>Repo: updateRoutineCompletions(routines)
Repo->>Endpoint: POST /completions (RoutineCompletionListDTO)
Endpoint-->>Repo: 응답 (성공/실패)
Repo-->>UseCase: 완료
UseCase-->>VM: 완료
VM-->>View: updateRoutineCompletionResultPublisher.emit()
Estimated code review effort🎯 4 (Complex) | ⏱️ ~35 minutes Suggested reviewers
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. 📜 Recent review detailsConfiguration used: .coderabbit.yaml 📒 Files selected for processing (18)
✅ Files skipped from review due to trivial changes (3)
🚧 Files skipped from review as they are similar to previous changes (13)
🧰 Additional context used🧠 Learnings (2)📓 Common learnings📚 Learning: choijungp의 homeviewmodel에서 fetchroutines(for date: date) 메서드는 기존 캐시 범위를 확장하는 것이 아니라, 요청된 날짜 주변의 새로운 ...Applied to files:
🧬 Code Graph Analysis (1)Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (2)
🪛 SwiftLint (0.57.0)Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift[Warning] 259-259: (for_where) ⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
🔇 Additional comments (8)
✨ 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. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (5)
Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift (1)
25-25: 메서드 명명 불일치 문제가 있습니다UseCase의
updateRoutineCompletion(단수)와 Repository의updateRoutineCompletions(복수) 사이에 명명 불일치가 있습니다. 일관성을 위해 동일한 이름을 사용하는 것이 좋겠습니다.다음과 같이 수정하는 것을 권장합니다:
- func updateRoutineCompletion(routines: [RoutineCompletionEntity]) async throws + func updateRoutineCompletions(routines: [RoutineCompletionEntity]) async throwsProjects/Domain/Sources/Entity/RoutineCompletionEntity.swift (1)
8-28: 도메인 엔티티 구조가 잘 정의되어 있습니다.루틴 완료 상태를 나타내는 엔티티가 명확하게 구성되었습니다. 다만 몇 가지 개선 사항을 고려해볼 수 있습니다:
completeYn속성명이 한국어 명명 규칙을 따르고 있는데, 다른 속성들과의 일관성을 위해isCompleted를 고려해볼 수 있습니다.performedDate가 String 타입인데, 타입 안전성을 위해 Date 타입 사용을 고려해보세요.public struct RoutineCompletionEntity { public let performedDate: String public let routineId: String - public let completeYn: Bool + public let isCompleted: Bool public let historySeq: Int public let routineType: String public init( performedDate: String, routineId: String, - completeYn: Bool, + isCompleted: Bool, historySeq: Int, routineType: String ) { self.performedDate = performedDate self.routineId = routineId - self.completeYn = completeYn + self.isCompleted = isCompleted self.historySeq = historySeq self.routineType = routineType } }Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (1)
176-179: 서브 루틴 완료 상태 확인 로직 개선을 고려해보세요.현재 로직은 올바르지만 성능을 개선할 수 있습니다.
func checkSubRoutines() { - let isDoneAllSubRoutines = routine.subRoutines.filter({ $0.isDone }).count == routine.subRoutines.count + let isDoneAllSubRoutines = routine.subRoutines.allSatisfy { $0.isDone } mainRoutineView.updateState(isDone: isDoneAllSubRoutines) }Projects/DataSource/Sources/DTO/RoutineCompletionDTO.swift (1)
8-18: DTO 구조가 API 통신에 적합하게 설계되었습니다.루틴 완료 정보를 전송하기 위한 DTO 구조가 잘 정의되어 있습니다. 다만 접근 제어자 일관성을 고려해보세요.
도메인 엔티티와 마찬가지로
public접근 제어자를 추가하는 것을 고려해보세요:-struct RoutineCompletionListDTO: Encodable { +public struct RoutineCompletionListDTO: Encodable { - let performedDate: String - let routineCompletionInfos: [RoutineCompletionDTO] + public let performedDate: String + public let routineCompletionInfos: [RoutineCompletionDTO] } -struct RoutineCompletionDTO: Encodable { +public struct RoutineCompletionDTO: Encodable { - let routineId: String - let completeYn: Bool - let historySeq: Int - let routineType: String + public let routineId: String + public let completeYn: Bool + public let historySeq: Int + public let routineType: String }Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (1)
258-259: SwiftLint 경고를 수정하세요.정적 분석 도구에서
where절 사용을 권장하고 있습니다.- for mainRoutine in mainRoutines { - if mainRoutine.subRoutines.contains(subRoutine) { + for mainRoutine in mainRoutines where mainRoutine.subRoutines.contains(subRoutine) {
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
Projects/DataSource/Sources/DTO/RoutineCompletionDTO.swift(1 hunks)Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift(3 hunks)Projects/DataSource/Sources/Repository/RoutineRepository.swift(1 hunks)Projects/Domain/Sources/Entity/RoutineCompletionEntity.swift(1 hunks)Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift(1 hunks)Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift(1 hunks)Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift(1 hunks)Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift(1 hunks)Projects/Presentation/Sources/Home/Model/MainRoutine.swift(1 hunks)Projects/Presentation/Sources/Home/Model/Routine.swift(1 hunks)Projects/Presentation/Sources/Home/Model/SubRoutine.swift(2 hunks)Projects/Presentation/Sources/Home/View/Component/MainRoutineView.swift(1 hunks)Projects/Presentation/Sources/Home/View/Component/RoutineView.swift(2 hunks)Projects/Presentation/Sources/Home/View/Component/WeekView.swift(1 hunks)Projects/Presentation/Sources/Home/View/HomeView.swift(12 hunks)Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift(9 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift(7 hunks)Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift(4 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#31
File: Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift:97-103
Timestamp: 2025-08-01T06:38:18.687Z
Learning: choijungp의 HomeViewModel에서 fetchRoutines(for date: Date) 메서드는 기존 캐시 범위를 확장하는 것이 아니라, 요청된 날짜 주변의 새로운 데이터 청크를 가져오기 위해 oldestDate와 latestDate를 모두 업데이트한다. 과거 날짜 요청 시에는 해당 날짜의 1주 전~1일 전, 미래 날짜 요청 시에는 해당 날짜의 1일 후~1주 후 범위로 설정한다.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#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: YAPP-Github/Bitnagil-iOS#30
File: Projects/DataSource/Sources/NetworkService/Plugin/RefreshTokenPlugin.swift:42-43
Timestamp: 2025-07-30T03:56:18.617Z
Learning: choijungp는 현재 테스트 단계에서 RefreshTokenPlugin의 토큰 갱신 로직 디버깅을 위해 액세스 토큰과 리프레시 토큰의 실제 값을 로그에 기록하는 것을 선호함.
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 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
📚 Learning: choijungp의 homeviewmodel에서 fetchroutines(for date: date) 메서드는 기존 캐시 범위를 확장하는 것이 아니라, 요청된 날짜 주변의 새로운 ...
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#31
File: Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift:97-103
Timestamp: 2025-08-01T06:38:18.687Z
Learning: choijungp의 HomeViewModel에서 fetchRoutines(for date: Date) 메서드는 기존 캐시 범위를 확장하는 것이 아니라, 요청된 날짜 주변의 새로운 데이터 청크를 가져오기 위해 oldestDate와 latestDate를 모두 업데이트한다. 과거 날짜 요청 시에는 해당 날짜의 1주 전~1일 전, 미래 날짜 요청 시에는 해당 날짜의 1일 후~1주 후 범위로 설정한다.
Applied to files:
Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swiftProjects/DataSource/Sources/Repository/RoutineRepository.swiftProjects/Domain/Sources/UseCase/Routine/RoutineUseCase.swiftProjects/Presentation/Sources/Home/ViewModel/HomeViewModel.swiftProjects/Presentation/Sources/Home/View/HomeView.swiftProjects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift
📚 Learning: onboardingrecommendedroutineview에서 viewwillappear에 registeronboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 ...
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Applied to files:
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swiftProjects/Presentation/Sources/Home/View/HomeView.swiftProjects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift
📚 Learning: baseviewcontroller의 viewdidload() 메서드에서 이미 configureattribute(), configurelayout(), bind()를 호출하므로, 하...
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Applied to files:
Projects/Presentation/Sources/Home/View/HomeView.swiftProjects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift
🧬 Code Graph Analysis (6)
Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift (1)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
updateRoutineCompletions(88-103)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
resolve(18-25)
Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (2)
Projects/Presentation/Sources/Home/View/Component/MainRoutineView.swift (1)
updateState(127-129)Projects/Presentation/Sources/Home/View/Component/SubRoutineButton.swift (1)
updateState(70-72)
Projects/Presentation/Sources/Home/View/Component/WeekView.swift (2)
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (1)
selectDate(163-166)Projects/Presentation/Sources/Home/View/Component/DateView.swift (1)
selectDate(99-101)
Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift (2)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
updateRoutineCompletion(82-84)Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (1)
updateRoutineCompletion(230-289)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (2)
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (1)
updateRoutineCompletion(230-289)Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
updateRoutineCompletions(88-103)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift
[Warning] 259-259: where clauses are preferred over a single if inside a for
(for_where)
🔇 Additional comments (25)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
52-55: 의존성 주입이 올바르게 구현되었습니다.
EmotionRepositoryProtocol의존성이 기존 패턴과 일관되게 추가되었고, guard let 구문을 통한 안전한 의존성 해결이 적용되었습니다.Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift (2)
10-10: Foundation import 추가가 적절합니다.
Date().convertToString()메서드 사용을 위해 Foundation import가 필요합니다.
16-16: 감정 데이터 로딩 기능이 잘 구현되었습니다.Input/Output 패턴과 CurrentValueSubject를 통한 상태 관리가 기존 패턴과 일관성 있게 구현되었습니다.
Also applies to: 25-25, 33-33
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (4)
64-68: viewWillAppear 사용이 적절합니다.이전 학습 내용에 따르면, 다른 화면에서 돌아올 때 데이터를 새로고침해야 하는 경우 viewWillAppear 사용이 적절합니다. 감정 등록 후 돌아올 때 상태를 업데이트하기 위해 올바른 선택입니다.
48-48: 감정 존재 상태 추적이 잘 구현되었습니다.
isExistEmotion프로퍼티를 통한 상태 관리가 적절합니다.
242-246: 감정 버튼 숨김 로직이 올바르게 구현되었습니다.
isExistEmotion상태에 따른 조기 반환 처리가 적절하게 추가되었습니다.
211-211: 메서드 주석 추가가 코드 가독성을 향상시킵니다.각 메서드의 기능을 명확하게 설명하는 주석이 추가되어 유지보수성이 개선되었습니다.
Also applies to: 232-232, 240-240, 263-263
Projects/Presentation/Sources/Home/Model/Routine.swift (1)
8-13: 프로토콜 설계가 적절합니다MainRoutine과 SubRoutine을 통합적으로 처리할 수 있도록 하는 Routine 프로토콜의 설계가 잘 되어 있습니다. 루틴 완료 로직에서 두 타입을 동일하게 다룰 수 있게 해주는 핵심적인 추상화입니다.
Projects/Presentation/Sources/Home/Model/MainRoutine.swift (1)
11-11: 프로토콜 적합성 선언이 올바릅니다MainRoutine이 Routine 프로토콜에 적합하도록 선언된 것이 좋습니다. 이미 필요한 모든 프로퍼티를 가지고 있어서 추가 구현 없이도 프로토콜 요구사항을 만족합니다.
Projects/Presentation/Sources/Home/View/Component/WeekView.swift (1)
140-148: 코드 포맷팅 개선이 좋습니다DateView 초기화 구문을 여러 줄로 나누어 가독성을 향상시킨 것이 좋습니다. 클로저 할당 후 공백 줄 추가도 코드 구조를 명확하게 해줍니다.
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
82-84: LGTM! 루틴 완료 상태 업데이트 메서드 구현이 적절합니다.도메인 레이어의 UseCase 패턴에 따라 단순하게 repository 메서드를 호출하는 구조가 적절합니다. 관련된 repository와 protocol 변경사항과 잘 연동됩니다.
Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift (4)
15-15: LGTM! 루틴 완료 업데이트를 위한 새로운 endpoint case가 추가되었습니다.
31-32: LGTM! 완료 상태 업데이트에 적합한 API 경로입니다."/completions" 경로는 루틴 완료 상태 업데이트 기능을 명확히 표현합니다.
40-40: LGTM! 루틴 완료 상태 업데이트에 적절한 HTTP 메서드입니다.POST 메서드 사용이 완료 상태 업데이트 작업에 적절합니다.
78-79: LGTM! DTO를 dictionary로 변환하여 request body에 포함하는 구현이 적절합니다.Projects/Presentation/Sources/Home/View/Component/MainRoutineView.swift (1)
78-78: LGTM! 초기 상태에 따른 체크 아이콘 색상 설정이 추가되었습니다.이제 뷰가 초기화될 때도 루틴의 완료 상태에 맞는 올바른 색상으로 표시됩니다. 기존
updateAttribute()메서드의 로직과 일관성이 있습니다.Projects/Presentation/Sources/Home/Model/SubRoutine.swift (3)
10-10: LGTM! Routine 프로토콜 채택으로 MainRoutine과 일관된 인터페이스를 제공합니다.이제 SubRoutine과 MainRoutine을 통일된 방식으로 처리할 수 있게 되었습니다.
16-17: LGTM! 루틴 완료 상태 업데이트에 필요한 새로운 프로퍼티가 추가되었습니다.
routineType과historySeq프로퍼티는RoutineCompletionEntity생성에 필요한 정보를 제공합니다.
29-31: LGTM! 새로운 프로퍼티들이 적절히 매핑되었습니다.
SubRoutineEntity에서routineType과historySeq값을 올바르게 가져와 초기화합니다.Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
88-103: 검증 제안 불필요: HomeViewModel에 해당 호출이 없습니다
updateRoutineCompletions는RoutineUseCase에서 직접 호출되며,HomeViewModel에는 호출부가 존재하지 않습니다.
따라서 HomeViewModel 레벨에서performedDate일관성을 검증하라는 제안은 부적절합니다.
필요하다면 UseCase 레이어에서 전달된performedDate가 올바른지 확인해 주세요.Likely an incorrect or invalid review comment.
Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (1)
159-162: 메인 루틴 상태 변경 시 서브 루틴 동기화가 올바르게 구현되었습니다.메인 루틴의 완료 상태가 변경될 때 모든 서브 루틴의 상태를 동기화하는 로직이 잘 구현되어 있습니다.
Projects/Presentation/Sources/Home/View/HomeView.swift (4)
84-84: 로딩 인디케이터 추가가 UX 개선에 도움이 됩니다.비동기 작업 중 사용자에게 적절한 피드백을 제공하는 로딩 인디케이터가 잘 추가되었습니다.
542-550: 로딩 상태 관리 메서드가 잘 구현되었습니다.사용자 상호작용을 제어하면서 로딩 상태를 관리하는 로직이 적절합니다. 비동기 작업 중 중복 요청을 방지하는 좋은 접근방식입니다.
102-105: 초기 로딩 시퀀스가 적절합니다.앱 시작 시 로딩 인디케이터를 먼저 표시하고 필요한 데이터를 순차적으로 로드하는 접근방식이 좋습니다.
556-558: 루틴 상호작용 시 로딩 상태 표시가 일관되게 구현되었습니다.메인 루틴과 서브 루틴 체크 버튼 탭 시 로딩 인디케이터를 표시하여 사용자에게 적절한 피드백을 제공합니다.
Also applies to: 571-574
| /// 루틴 완료 여부를 업데이트 합니다. | ||
| /// - Parameter routines: 완료 여부를 업데이트할 루틴 배열 | ||
| func updateRoutineCompletions(routines: [RoutineCompletionEntity]) async throws |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
메서드 추가는 적절하나 구현 시 주의사항이 있습니다
루틴 완료 상태를 일괄 업데이트하는 메서드 추가가 좋습니다. 다만 RoutineRepository.swift의 구현을 보면 첫 번째 루틴의 performedDate를 전체 배열에 사용하고 있습니다.
다음 스크립트로 배열 내 루틴들의 performedDate가 항상 동일한지 확인해보겠습니다:
🏁 Script executed:
#!/bin/bash
# updateRoutineCompletion 메서드 호출 부분을 검색하여 전달되는 루틴들의 performedDate가 동일한지 확인
rg -A 10 -B 5 "updateRoutineCompletion.*routines" --type swiftLength of output: 9194
🏁 Script executed:
#!/bin/bash
# Find where `routineCompletionEntities` is defined in HomeViewModel.swift
rg -n 'routineCompletionEntities' -A5 -B5 --type swift Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swiftLength of output: 2561
메서드 구현 확인 및 네이밍 일관성 권장
-
RoutineRepository의updateRoutineCompletions는 API 스펙대로 첫 번째 엔티티의performedDate를 사용하도록 의도된 구현입니다.
HomeViewModel에서 생성되는 모든RoutineCompletionEntity가 동일한performedDate를 가지므로, 현재로서는 잘 동작합니다. -
다만 프로토콜 계층 간 메서드 이름이 불일치합니다. 일관성을 위해 다음 중 하나로 통일을 권장드립니다.
• Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift
–func updateRoutineCompletions(routines: [RoutineCompletionEntity]) async throws
• Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift
–func updateRoutineCompletion(routines: [RoutineCompletionEntity]) async throws
🤖 Prompt for AI Agents
In Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift
around lines 41 to 43, the method name updateRoutineCompletions is inconsistent
with the corresponding method in RoutineUseCaseProtocol, which is named
updateRoutineCompletion. To fix this, rename the method in
RoutineRepositoryProtocol to match the use case protocol method name
updateRoutineCompletion to ensure naming consistency across protocol layers.
| private func updateRoutineCompletion(updatedRoutine: Routine) { | ||
| let performedDate = selectedDateSubject.value.convertToString(dateType: .yearMonthDate) | ||
| var routineCompletionEntities: [RoutineCompletionEntity] = [] | ||
|
|
||
| let isDone = !updatedRoutine.isDone | ||
| let routineCompletionEntity = RoutineCompletionEntity( | ||
| performedDate: performedDate, | ||
| routineId: updatedRoutine.id, | ||
| completeYn: isDone, | ||
| historySeq: updatedRoutine.historySeq, | ||
| routineType: updatedRoutine.routineType) | ||
| routineCompletionEntities.append(routineCompletionEntity) | ||
|
|
||
| // 메인 루틴이라면, 그 안의 세부 루틴 값도 업데이트 | ||
| if let mainRoutine = updatedRoutine as? MainRoutine { | ||
| for subRoutine in mainRoutine.subRoutines { | ||
| guard subRoutine.isDone != isDone else { continue } | ||
| let subRoutineCompletionEntity = RoutineCompletionEntity( | ||
| performedDate: performedDate, | ||
| routineId: subRoutine.id, | ||
| completeYn: isDone, | ||
| historySeq: subRoutine.historySeq, | ||
| routineType: subRoutine.routineType) | ||
| routineCompletionEntities.append(subRoutineCompletionEntity) | ||
| } | ||
| } else if let subRoutine = updatedRoutine as? SubRoutine { | ||
| // 세부 루틴이라면, 세부 루틴의 완료 값을 확인하여 메인 루틴도 업데이트 | ||
| let mainRoutines = routinesSubject.value | ||
| for mainRoutine in mainRoutines { | ||
| if mainRoutine.subRoutines.contains(subRoutine) { | ||
| let mainRoutineIsDone = mainRoutine.isDone | ||
| var subRoutineCompleted: Bool | ||
| if subRoutine.isDone { | ||
| subRoutineCompleted = mainRoutine.subRoutines.filter({ $0.isDone }).count - 1 == mainRoutine.subRoutines.count | ||
| } else { | ||
| subRoutineCompleted = mainRoutine.subRoutines.filter({ $0.isDone }).count + 1 == mainRoutine.subRoutines.count | ||
| } | ||
| if subRoutineCompleted != mainRoutineIsDone { | ||
| let mainRoutineCompletionEntity = RoutineCompletionEntity( | ||
| performedDate: performedDate, | ||
| routineId: mainRoutine.id, | ||
| completeYn: subRoutineCompleted, | ||
| historySeq: mainRoutine.historySeq, | ||
| routineType: mainRoutine.routineType) | ||
| routineCompletionEntities.append(mainRoutineCompletionEntity) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Task { | ||
| do { | ||
| try await routineUseCase.updateRoutineCompletion(routines: routineCompletionEntities) | ||
| updateRoutineCompletionResultSubject.send(true) | ||
| fetchRoutines() | ||
| } catch { | ||
| updateRoutineCompletionResultSubject.send(false) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
루틴 완료 로직이 복잡하지만 올바르게 구현되었습니다.
메인 루틴과 서브 루틴의 완료 상태를 동시에 처리하는 복잡한 로직이 잘 구현되어 있습니다. 하지만 가독성과 유지보수를 위해 메서드 분리를 고려해보세요.
복잡한 로직을 더 작은 메서드로 분리하는 것을 권장합니다:
private func updateRoutineCompletion(updatedRoutine: Routine) {
let performedDate = selectedDateSubject.value.convertToString(dateType: .yearMonthDate)
- var routineCompletionEntities: [RoutineCompletionEntity] = []
-
- let isDone = !updatedRoutine.isDone
- let routineCompletionEntity = RoutineCompletionEntity(
- performedDate: performedDate,
- routineId: updatedRoutine.id,
- completeYn: isDone,
- historySeq: updatedRoutine.historySeq,
- routineType: updatedRoutine.routineType)
- routineCompletionEntities.append(routineCompletionEntity)
-
- // 메인 루틴이라면, 그 안의 세부 루틴 값도 업데이트
- if let mainRoutine = updatedRoutine as? MainRoutine {
- for subRoutine in mainRoutine.subRoutines {
- guard subRoutine.isDone != isDone else { continue }
- let subRoutineCompletionEntity = RoutineCompletionEntity(
- performedDate: performedDate,
- routineId: subRoutine.id,
- completeYn: isDone,
- historySeq: subRoutine.historySeq,
- routineType: subRoutine.routineType)
- routineCompletionEntities.append(subRoutineCompletionEntity)
- }
- } else if let subRoutine = updatedRoutine as? SubRoutine {
- // 세부 루틴이라면, 세부 루틴의 완료 값을 확인하여 메인 루틴도 업데이트
- let mainRoutines = routinesSubject.value
- for mainRoutine in mainRoutines {
- if mainRoutine.subRoutines.contains(subRoutine) {
- let mainRoutineIsDone = mainRoutine.isDone
- var subRoutineCompleted: Bool
- if subRoutine.isDone {
- subRoutineCompleted = mainRoutine.subRoutines.filter({ $0.isDone }).count - 1 == mainRoutine.subRoutines.count
- } else {
- subRoutineCompleted = mainRoutine.subRoutines.filter({ $0.isDone }).count + 1 == mainRoutine.subRoutines.count
- }
- if subRoutineCompleted != mainRoutineIsDone {
- let mainRoutineCompletionEntity = RoutineCompletionEntity(
- performedDate: performedDate,
- routineId: mainRoutine.id,
- completeYn: subRoutineCompleted,
- historySeq: mainRoutine.historySeq,
- routineType: mainRoutine.routineType)
- routineCompletionEntities.append(mainRoutineCompletionEntity)
- }
- }
- }
- }
+ let routineCompletionEntities = buildRoutineCompletionEntities(
+ updatedRoutine: updatedRoutine,
+ performedDate: performedDate
+ )
Task {
do {
try await routineUseCase.updateRoutineCompletion(routines: routineCompletionEntities)
updateRoutineCompletionResultSubject.send(true)
fetchRoutines()
} catch {
updateRoutineCompletionResultSubject.send(false)
}
}
}
+private func buildRoutineCompletionEntities(
+ updatedRoutine: Routine,
+ performedDate: String
+) -> [RoutineCompletionEntity] {
+ var entities: [RoutineCompletionEntity] = []
+ let isDone = !updatedRoutine.isDone
+
+ // 업데이트된 루틴 자체의 엔티티 추가
+ entities.append(createRoutineCompletionEntity(
+ routine: updatedRoutine,
+ isDone: isDone,
+ performedDate: performedDate
+ ))
+
+ if let mainRoutine = updatedRoutine as? MainRoutine {
+ entities.append(contentsOf: buildSubRoutineEntities(
+ mainRoutine: mainRoutine,
+ isDone: isDone,
+ performedDate: performedDate
+ ))
+ } else if let subRoutine = updatedRoutine as? SubRoutine {
+ entities.append(contentsOf: buildMainRoutineEntities(
+ subRoutine: subRoutine,
+ performedDate: performedDate
+ ))
+ }
+
+ return entities
+}Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 259-259: where clauses are preferred over a single if inside a for
(for_where)
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift around lines
230 to 289, the updateRoutineCompletion method contains complex logic handling
both main and sub routine completion states. To improve readability and
maintainability, refactor this method by extracting the main routine update
logic and the sub routine update logic into separate smaller private methods.
Then call these helper methods from updateRoutineCompletion to keep the code
modular and easier to understand.
| if subRoutine.isDone { | ||
| subRoutineCompleted = mainRoutine.subRoutines.filter({ $0.isDone }).count - 1 == mainRoutine.subRoutines.count | ||
| } else { | ||
| subRoutineCompleted = mainRoutine.subRoutines.filter({ $0.isDone }).count + 1 == mainRoutine.subRoutines.count | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
서브 루틴 완료 상태 계산 로직을 단순화할 수 있습니다.
현재 로직이 복잡하고 이해하기 어렵습니다.
- var subRoutineCompleted: Bool
- if subRoutine.isDone {
- subRoutineCompleted = mainRoutine.subRoutines.filter({ $0.isDone }).count - 1 == mainRoutine.subRoutines.count
- } else {
- subRoutineCompleted = mainRoutine.subRoutines.filter({ $0.isDone }).count + 1 == mainRoutine.subRoutines.count
- }
+ // 서브 루틴 완료 후 메인 루틴 완료 여부 계산
+ let completedCount = mainRoutine.subRoutines.filter({ $0.isDone }).count
+ let adjustedCount = subRoutine.isDone ? completedCount - 1 : completedCount + 1
+ let subRoutineCompleted = adjustedCount == mainRoutine.subRoutines.count📝 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.
| if subRoutine.isDone { | |
| subRoutineCompleted = mainRoutine.subRoutines.filter({ $0.isDone }).count - 1 == mainRoutine.subRoutines.count | |
| } else { | |
| subRoutineCompleted = mainRoutine.subRoutines.filter({ $0.isDone }).count + 1 == mainRoutine.subRoutines.count | |
| } | |
| // 서브 루틴 완료 후 메인 루틴 완료 여부 계산 | |
| let completedCount = mainRoutine.subRoutines.filter({ $0.isDone }).count | |
| let adjustedCount = subRoutine.isDone ? completedCount - 1 : completedCount + 1 | |
| let subRoutineCompleted = adjustedCount == mainRoutine.subRoutines.count |
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift around lines
262 to 266, the logic calculating subRoutineCompleted is overly complex and hard
to understand. Simplify it by directly comparing the count of completed
subRoutines to the total count, adjusting for the current subRoutine's isDone
state without subtracting or adding 1 manually. This will make the condition
clearer and easier to maintain.
| viewModel.output.emotionExistPublisher | ||
| .receive(on: DispatchQueue.main) | ||
| .sink { [weak self] isExistEmotion in | ||
| if isExistEmotion { | ||
| self?.isExistEmotion = isExistEmotion | ||
| self?.registerEmotionButton.isHidden = true | ||
| } | ||
| } | ||
| .store(in: &cancellables) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
감정 존재 여부 바인딩 로직을 개선할 수 있습니다.
현재는 감정이 존재할 때만 처리하고 있지만, 감정이 삭제되는 경우도 고려해야 합니다.
viewModel.output.emotionExistPublisher
.receive(on: DispatchQueue.main)
- .sink { [weak self] isExistEmotion in
- if isExistEmotion {
- self?.isExistEmotion = isExistEmotion
- self?.registerEmotionButton.isHidden = true
- }
- }
+ .sink { [weak self] isExistEmotion in
+ self?.isExistEmotion = isExistEmotion
+ if isExistEmotion {
+ self?.registerEmotionButton.isHidden = true
+ } else {
+ // 현재 추천 카테고리인 경우에만 버튼을 다시 표시
+ let isRecommendationCategory = self?.viewModel.output.selectedCategoryPublisher.value == .recommendation
+ if isRecommendationCategory == true {
+ self?.showEmotionButton(isShowEmotionButton: true)
+ }
+ }
+ }🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift
around lines 200 to 208, the current binding logic only updates the UI when an
emotion exists, ignoring the case when an emotion is deleted. Modify the sink
closure to handle both cases by updating isExistEmotion and the visibility of
registerEmotionButton accordingly for both true and false values of
isExistEmotion.
| let emotionEntity = try await emotionRepository.fetchEmotion(date: Date().convertToString(dateType: .yearMonthDate)) | ||
| if emotionEntity == nil { | ||
| emotionExistSubject.send(false) | ||
| } else { | ||
| emotionExistSubject.send(true) | ||
| } | ||
| } catch { | ||
|
|
||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
에러 처리 개선이 필요합니다.
현재 catch 블록이 비어있어 네트워크 오류나 데이터 파싱 오류 등이 무시됩니다. 사용자에게 적절한 피드백을 제공하거나 최소한 로깅을 추가하는 것이 좋습니다.
} catch {
-
+ BitnagilLogger.log(logType: .error, message: "감정 데이터 로딩 실패: \(error.localizedDescription)")
+ emotionExistSubject.send(false)
}또한 nil 체크 로직을 간소화할 수 있습니다:
- if emotionEntity == nil {
- emotionExistSubject.send(false)
- } else {
- emotionExistSubject.send(true)
- }
+ emotionExistSubject.send(emotionEntity != nil)📝 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.
| let emotionEntity = try await emotionRepository.fetchEmotion(date: Date().convertToString(dateType: .yearMonthDate)) | |
| if emotionEntity == nil { | |
| emotionExistSubject.send(false) | |
| } else { | |
| emotionExistSubject.send(true) | |
| } | |
| } catch { | |
| } | |
| } | |
| } | |
| let emotionEntity = try await emotionRepository.fetchEmotion(date: Date().convertToString(dateType: .yearMonthDate)) | |
| emotionExistSubject.send(emotionEntity != nil) | |
| } catch { | |
| BitnagilLogger.log(logType: .error, message: "감정 데이터 로딩 실패: \(error.localizedDescription)") | |
| emotionExistSubject.send(false) | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift
around lines 115 to 125, the catch block is empty, causing errors like network
or parsing failures to be ignored. Add proper error handling by logging the
error or providing user feedback inside the catch block. Also, simplify the nil
check by directly sending the boolean result of the nil comparison to
emotionExistSubject instead of using an if-else statement.
- 세부 루틴 완료 값에 따른 메인 루틴 완료 값 업데이트 - 메인 루틴 완료 값에 따른 세부 루틴 완료 값 업데이트
5e2c1fa to
0f5154d
Compare
🌁 Background
루틴 완료 로직을 구현했어요 ~~
📱 Screenshot
Simulator.Screen.Recording.-.iPhone.13.mini.-.2025-08-07.at.14.32.36.mp4
👩💻 Contents
✅ Testing
📝 Review Note
1. 복잡한 완료 로직 🤯
일단 Routine 완료 처리는 메인 루틴이든, 서부 루틴이든 같은 형식으로 서버에게 요청을 보냅니다.
따라서 Routine이라는 Protocol을 구현하고, MainRoutine, SubRoutine이 모두 해당 프로토콜을 채택하도록 하였습니다.
HomeViewModel의updateRoutineCompletion함수를 보시면 매우매우매우 복잡하고 더럽습니다 ㅜㅜ... 개선할 수 있다면 꼭 개선할게요 ..
2. 루틴 완료 처리 + indicator view
이어서 루틴 완료 처리에 대해 말씀 드리자면 .. 낙관적 업데이트라는 개념이 있다고 하더라고요.
우선 UI부터 업데이트 한 후에 서버 통신 로직을 태우고, 만약 서버 통신에 실패한다면 다시 롤백하는 구조라고 합니다.
하지만 ... 지금 단계에 그것을 학습하고 적용하기에 시간이 너무 빠듯하다고 생각하여, 전 바로 서버 통신을 하도록 구현했습니다. ㅜㅜ
이것에 대해 어떻게 생각하시나요 ?? ㅜㅜ
3. fetch 루틴 오류
현재 fetch에서 오류가 나는 경우가 종종 있어요 ㅠㅠ
이 PR에서 원인을 잡고 처리하기에는 PR 단위가 커질 수 있다고 판단되어 꼭 원인 파악 후 오늘 안에 다른 PR로 올려볼게용 ...
4. 루틴 등록 및 수정 이후 HomeView
루틴 등록 이후에 HomeView로 돌아오는 코드가 구현되어 있지 않는 것 같습니다 !!
만약 HomeView로 돌아온다면 그 때 다시 루틴을 fetch하는 로직이 필요합니다 !
단순히 viewWillAppear에 fetch를 넣을지, 아님 notification center를 이용해 구독할지 .. 고민이 되네요 !!
3번 수정하면서 구현해보겠습니다.
그리고 추가적으로 루틴 수정을 누르면 루틴 등록 폼에 수정할 루틴들이 채워져야 한다고 생각하는데 ..
그것이 구현되어 있찌 않더라고요 ㅠㅠ !! 혹시 제가 해야 하는 영역일까요 ??
루틴 등록 혹은 수정 시에 placeholder나 폼이 채워져야 하는 경우는 다음과 같습니다.
📣 Related Issue
Summary by CodeRabbit
신규 기능
버그 수정 및 개선
스타일 및 리팩터링