Skip to content

[Feat-T3-144] 루틴 완료 로직 구현#39

Merged
choijungp merged 7 commits intodevelopfrom
feat/routine-complete
Aug 7, 2025
Merged

[Feat-T3-144] 루틴 완료 로직 구현#39
choijungp merged 7 commits intodevelopfrom
feat/routine-complete

Conversation

@choijungp
Copy link
Copy Markdown
Contributor

@choijungp choijungp commented Aug 7, 2025

🌁 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이 모두 해당 프로토콜을 채택하도록 하였습니다.

HomeViewModelupdateRoutineCompletion 함수를 보시면 매우매우매우 복잡하고 더럽습니다 ㅜㅜ

  • 우선 완료할 루틴으로 매개변수로 받은 루틴을 Entity로 변환합니다.
  • 만약 매개변수로 받은 루틴이 메인 루틴이라면, 메인 루틴 안에 있는 서브 루틴들도 완료 처리를 하기 위해 Entity로 변환합니다.
  • 만약 매개변수로 받은 루틴이 서브 루틴이라면, 메인 루틴도 완료할지, 메인 루틴도 취소할지에 따라 메인 루틴도 Entity로 변환합니다.
  • 그렇게 해서 업데이트 처리해야 할 루틴들을 배열에 담아 서버 통신을 합니다 ..

... 개선할 수 있다면 꼭 개선할게요 ..


2. 루틴 완료 처리 + indicator view

이어서 루틴 완료 처리에 대해 말씀 드리자면 .. 낙관적 업데이트라는 개념이 있다고 하더라고요.

우선 UI부터 업데이트 한 후에 서버 통신 로직을 태우고, 만약 서버 통신에 실패한다면 다시 롤백하는 구조라고 합니다.

하지만 ... 지금 단계에 그것을 학습하고 적용하기에 시간이 너무 빠듯하다고 생각하여, 전 바로 서버 통신을 하도록 구현했습니다. ㅜㅜ

  • 루틴 완료 업데이트 처리 > Indicator > 다시 루틴 데이터 서버에서 refresh > refresh 완료 시 indicator hide 후 update view

이것에 대해 어떻게 생각하시나요 ?? ㅜㅜ


3. fetch 루틴 오류

현재 fetch에서 오류가 나는 경우가 종종 있어요 ㅠㅠ
이 PR에서 원인을 잡고 처리하기에는 PR 단위가 커질 수 있다고 판단되어 꼭 원인 파악 후 오늘 안에 다른 PR로 올려볼게용 ...


4. 루틴 등록 및 수정 이후 HomeView

루틴 등록 이후에 HomeView로 돌아오는 코드가 구현되어 있지 않는 것 같습니다 !!
만약 HomeView로 돌아온다면 그 때 다시 루틴을 fetch하는 로직이 필요합니다 !

단순히 viewWillAppear에 fetch를 넣을지, 아님 notification center를 이용해 구독할지 .. 고민이 되네요 !!
3번 수정하면서 구현해보겠습니다.

그리고 추가적으로 루틴 수정을 누르면 루틴 등록 폼에 수정할 루틴들이 채워져야 한다고 생각하는데 ..
그것이 구현되어 있찌 않더라고요 ㅠㅠ !! 혹시 제가 해야 하는 영역일까요 ??

루틴 등록 혹은 수정 시에 placeholder나 폼이 채워져야 하는 경우는 다음과 같습니다.

  • 홈 화면에서 루틴 선택 후 루틴 수정
  • 추천 루틴 화면에서 루틴 선택
  • 감정 등록 후 결과로 받은 추천 루틴을 선택하고 루틴 등록하기로 이동

📣 Related Issue

  • close #T3-144

Summary by CodeRabbit

  • 신규 기능

    • 루틴 완료 상태를 일괄 업데이트하는 기능이 추가되었습니다.
    • 루틴 및 서브루틴의 완료 상태 동기화 및 표시가 개선되었습니다.
    • 홈 화면에 비동기 작업 진행 시 표시되는 로딩 인디케이터가 추가되었습니다.
    • 감정 기록 존재 여부를 확인하고, 감정 기록이 있을 경우 관련 버튼이 자동으로 숨겨집니다.
    • 루틴 정렬 기능이 추가되어 완료 여부에 따라 정렬할 수 있습니다.
  • 버그 수정 및 개선

    • 루틴 및 서브루틴 체크 시 완료 상태가 상호 연동되어 자동으로 반영됩니다.
    • 날짜 선택, 루틴 삭제 등 다양한 사용자 액션에 대한 처리 및 UI 반영이 더 직관적으로 개선되었습니다.
  • 스타일 및 리팩터링

    • 일부 UI 컴포넌트의 색상 및 속성 적용 방식이 조건부로 변경되어 가독성이 향상되었습니다.
    • 코드 구조 및 의존성 주입 방식이 개선되었습니다.

@choijungp choijungp requested a review from taipaise August 7, 2025 05:54
@choijungp choijungp self-assigned this Aug 7, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 7, 2025

Walkthrough

루틴 완료 상태 업데이트 기능이 도메인, 데이터, 프레젠테이션 계층 전반에 걸쳐 도입되었습니다. 새로운 DTO와 엔티티가 추가되었고, 관련 프로토콜 및 레포지토리에 루틴 완료 업데이트 메서드가 확장되었습니다. Home/RecommendedRoutine 화면의 뷰모델과 뷰에도 연동 로직 및 UI 변경이 반영되었습니다.

Changes

Cohort / File(s) Change Summary
DTO 및 엔티티 추가
Projects/DataSource/Sources/DTO/RoutineCompletionDTO.swift, Projects/Domain/Sources/Entity/RoutineCompletionEntity.swift
루틴 완료 상태 전송을 위한 DTO(RoutineCompletionDTO, RoutineCompletionListDTO) 및 도메인 엔티티(RoutineCompletionEntity) 추가
엔드포인트 및 레포지토리 확장
Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift, Projects/DataSource/Sources/Repository/RoutineRepository.swift
루틴 완료 상태 업데이트용 엔드포인트 케이스 및 POST 요청 구현, DTO 변환 및 네트워크 호출 추가
도메인 프로토콜 및 유스케이스 확장
Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift, Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift, Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift
루틴 완료 상태 업데이트 메서드 프로토콜/유스케이스에 추가 및 구현
DI 및 의존성 주입 변경
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
RecommendedRoutineViewModel 생성자에 EmotionRepositoryProtocol 추가 의존성 주입
루틴 모델 및 프로토콜 확장
Projects/Presentation/Sources/Home/Model/MainRoutine.swift, Projects/Presentation/Sources/Home/Model/Routine.swift, Projects/Presentation/Sources/Home/Model/SubRoutine.swift
Routine 프로토콜 신설 및 MainRoutine, SubRoutine에 적용, 관련 프로퍼티 추가
루틴 뷰 및 컴포넌트 UI/상태 동기화
Projects/Presentation/Sources/Home/View/Component/MainRoutineView.swift, Projects/Presentation/Sources/Home/View/Component/RoutineView.swift, Projects/Presentation/Sources/Home/View/Component/WeekView.swift
루틴 완료 상태에 따른 아이콘 색상, 메인/서브 루틴 상태 동기화, UI 생성 코드 포맷 정리 등
홈뷰 로딩 인디케이터 및 액션 개선
Projects/Presentation/Sources/Home/View/HomeView.swift
비동기 작업 시 로딩 인디케이터 표시/숨김, 액션 호출 방식 및 delegate 메서드 시그니처 일관성 개선
홈뷰모델 루틴 완료/정렬/선택/감정 기능 추가
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift
루틴 완료 상태 업데이트, 날짜 선택, 감정 불러오기, 정렬 등 다양한 Input/Output 및 내부 메서드 추가
추천 루틴 뷰/뷰모델 감정 존재 여부 연동
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift, Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift
감정 존재 여부 확인 및 버튼 표시 제어, 관련 퍼블리셔/메서드/프로퍼티 추가 및 라이프사이클 로직 변경

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()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~35 minutes

Suggested reviewers

  • taipaise

Poem

변화의 바람이 불어와
루틴의 완료를 기록해요
체크박스에 빛이 들어오고
감정의 흔적도 놓치지 않죠
토끼는 깡총깡총 춤을 추며
새로워진 홈에서
오늘도 한 걸음, 기분 좋게! 🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e2c1fa and 0f5154d.

📒 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)
✅ Files skipped from review due to trivial changes (3)
  • Projects/Presentation/Sources/Home/View/Component/MainRoutineView.swift
  • Projects/Presentation/Sources/Home/Model/Routine.swift
  • Projects/DataSource/Sources/DTO/RoutineCompletionDTO.swift
🚧 Files skipped from review as they are similar to previous changes (13)
  • Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift
  • Projects/Presentation/Sources/Home/Model/MainRoutine.swift
  • Projects/Presentation/Sources/Home/Model/SubRoutine.swift
  • Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift
  • Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift
  • Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift
  • Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift
  • Projects/DataSource/Sources/Repository/RoutineRepository.swift
  • Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift
  • Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
  • Projects/Presentation/Sources/Home/View/Component/WeekView.swift
  • Projects/Presentation/Sources/Home/View/HomeView.swift
  • Projects/Domain/Sources/Entity/RoutineCompletionEntity.swift
🧰 Additional context used
🧠 Learnings (2)
📓 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/Presentation/Sources/Home/ViewModel/HomeViewModel.swift
🧬 Code Graph Analysis (1)
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)
🪛 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)

⏰ 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)
  • GitHub Check: build
🔇 Additional comments (8)
Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (2)

159-161: 메인 루틴 상태 변경 시 서브 루틴 동기화 로직이 추가되었습니다.

메인 루틴의 완료 상태가 변경될 때 모든 서브 루틴도 동일한 상태로 업데이트하는 로직이 올바르게 구현되었습니다.


176-179: 서브 루틴 완료 상태 확인 로직이 올바르게 구현되었습니다.

모든 서브 루틴이 완료되었는지 확인하여 메인 루틴 상태를 동기화하는 로직이 적절합니다.

Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (6)

15-23: 새로운 Input 케이스들이 적절하게 추가되었습니다.

루틴 완료 상태 업데이트, 감정 로딩, 날짜 선택 등의 새로운 기능을 위한 Input 케이스들이 잘 정의되었습니다.


28-33: 새로운 Output 퍼블리셔들이 적절하게 추가되었습니다.

감정 상태, 선택된 날짜, 루틴 완료 업데이트 결과를 노출하는 퍼블리셔들이 올바르게 정의되었습니다.


123-133: 감정 데이터 로딩 로직이 올바르게 구현되었습니다.

오늘 날짜의 감정 데이터를 비동기적으로 가져와서 퍼블리시하는 로직이 적절합니다.


163-166: 날짜 선택 로직이 간단명료하게 구현되었습니다.

선택된 날짜를 업데이트하고 해당 날짜의 루틴을 가져오는 로직이 적절합니다.


291-304: 루틴 정렬 로직이 올바르게 구현되었습니다.

완료/미완료 상태에 따른 정렬과 원래 순서로 되돌리기 기능이 적절하게 구현되었습니다.


196-196: 루틴 삭제 후 데이터 새로고침이 적절하게 추가되었습니다.

루틴 삭제 성공 후 fetchRoutines() 호출을 추가하여 UI와 데이터 동기화를 보장하는 것이 좋은 개선사항입니다.

Also applies to: 222-222

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/routine-complete

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary or @coderabbitai 요약 to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@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: 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 throws
Projects/Domain/Sources/Entity/RoutineCompletionEntity.swift (1)

8-28: 도메인 엔티티 구조가 잘 정의되어 있습니다.

루틴 완료 상태를 나타내는 엔티티가 명확하게 구성되었습니다. 다만 몇 가지 개선 사항을 고려해볼 수 있습니다:

  1. completeYn 속성명이 한국어 명명 규칙을 따르고 있는데, 다른 속성들과의 일관성을 위해 isCompleted를 고려해볼 수 있습니다.
  2. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7726a5a and 5e2c1fa.

📒 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.swift
  • Projects/DataSource/Sources/Repository/RoutineRepository.swift
  • Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift
  • Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift
  • Projects/Presentation/Sources/Home/View/HomeView.swift
  • Projects/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.swift
  • Projects/Presentation/Sources/Home/View/HomeView.swift
  • Projects/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.swift
  • Projects/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! 루틴 완료 상태 업데이트에 필요한 새로운 프로퍼티가 추가되었습니다.

routineTypehistorySeq 프로퍼티는 RoutineCompletionEntity 생성에 필요한 정보를 제공합니다.


29-31: LGTM! 새로운 프로퍼티들이 적절히 매핑되었습니다.

SubRoutineEntity에서 routineTypehistorySeq 값을 올바르게 가져와 초기화합니다.

Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)

88-103: 검증 제안 불필요: HomeViewModel에 해당 호출이 없습니다
updateRoutineCompletionsRoutineUseCase에서 직접 호출되며, 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

Comment on lines +41 to +43
/// 루틴 완료 여부를 업데이트 합니다.
/// - Parameter routines: 완료 여부를 업데이트할 루틴 배열
func updateRoutineCompletions(routines: [RoutineCompletionEntity]) async throws
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

메서드 추가는 적절하나 구현 시 주의사항이 있습니다

루틴 완료 상태를 일괄 업데이트하는 메서드 추가가 좋습니다. 다만 RoutineRepository.swift의 구현을 보면 첫 번째 루틴의 performedDate를 전체 배열에 사용하고 있습니다.

다음 스크립트로 배열 내 루틴들의 performedDate가 항상 동일한지 확인해보겠습니다:


🏁 Script executed:

#!/bin/bash
# updateRoutineCompletion 메서드 호출 부분을 검색하여 전달되는 루틴들의 performedDate가 동일한지 확인
rg -A 10 -B 5 "updateRoutineCompletion.*routines" --type swift

Length 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.swift

Length of output: 2561


메서드 구현 확인 및 네이밍 일관성 권장

  • RoutineRepositoryupdateRoutineCompletions는 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.

Comment on lines +230 to +289
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)
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Comment on lines +262 to +266
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
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Suggested change
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.

Comment on lines +200 to +208
viewModel.output.emotionExistPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] isExistEmotion in
if isExistEmotion {
self?.isExistEmotion = isExistEmotion
self?.registerEmotionButton.isHidden = true
}
}
.store(in: &cancellables)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Comment on lines +115 to +125
let emotionEntity = try await emotionRepository.fetchEmotion(date: Date().convertToString(dateType: .yearMonthDate))
if emotionEntity == nil {
emotionExistSubject.send(false)
} else {
emotionExistSubject.send(true)
}
} catch {

}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Suggested change
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.

@choijungp choijungp force-pushed the feat/routine-complete branch from 5e2c1fa to 0f5154d Compare August 7, 2025 15:28
@choijungp choijungp merged commit b757c56 into develop Aug 7, 2025
2 checks passed
@choijungp choijungp deleted the feat/routine-complete branch August 7, 2025 16:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant