Skip to content

[Feat-T3-127] 감정 구슬 API 연동 및 추천 루틴 결과 화면 재사용#29

Merged
choijungp merged 12 commits intodevelopfrom
feat/emotionAPI
Jul 30, 2025
Merged

[Feat-T3-127] 감정 구슬 API 연동 및 추천 루틴 결과 화면 재사용#29
choijungp merged 12 commits intodevelopfrom
feat/emotionAPI

Conversation

@choijungp
Copy link
Copy Markdown
Contributor

@choijungp choijungp commented Jul 29, 2025

🌁 Background

감정 구슬 목록을 불러오는 API를 연동하고, 추천 루틴 결과 화면을 재사용할 수 있도록 수정했습니다.


📱 Screenshot

1. 목표 재설정에서의 추천 루틴 결과 화면

iPhone SE3 iPhone 13 mini iPhone 16 Pro
Simulator Screenshot - iPhone SE (3rd generation) - 2025-07-29 at 23 26 06 Simulator Screenshot - iPhone 13 mini - 2025-07-29 at 23 19 06 Simulator Screenshot - iPhone 16 Pro - 2025-07-29 at 23 30 40

2. 온보딩에서의 추천 루틴 결과 화면

iPhone SE3 iPhone 13 mini iPhone 16 Pro
Simulator Screenshot - iPhone SE (3rd generation) - 2025-07-29 at 23 25 34 Simulator Screenshot - iPhone 13 mini - 2025-07-29 at 23 18 19 Simulator Screenshot - iPhone 16 Pro - 2025-07-29 at 23 30 10

3. 감정 등록에서의 추천 루틴 결과 화면

iPhone SE3 iPhone 13 mini iPhone 16 Pro
1일 1등록이라... 캡처가 매우 어려운 상황 ☠️ Simulator Screenshot - iPhone 13 mini - 2025-07-30 at 00 02 44 Simulator Screenshot - iPhone 16 Pro - 2025-07-29 at 22 58 46

4. 그래픽 추가된 온보딩 결과 화면

iPhone SE3 iPhone 13 mini iPhone 16 Pro
Simulator Screenshot - iPhone SE (3rd generation) - 2025-07-29 at 23 25 31 Simulator Screenshot - iPhone 13 mini - 2025-07-29 at 23 18 11 Simulator Screenshot - iPhone 16 Pro - 2025-07-29 at 23 30 05

전체 흐름 (온보딩)

Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-07-29.at.23.42.22.mp4

전체 흐름 (목표 재설정)

Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-07-29.at.23.39.48.mp4

전체 흐름 (감정 등록)

Simulator.Screen.Recording.-.iPhone.13.mini.-.2025-07-30.at.00.02.56.mp4

감정 등록은 1일 1등록이라 테스트가 어려워요 ㅠ ㅠ ... 테스트 하긴 했어요 ㅠㅠ 🔥


👩‍💻 Contents

  • Kingfisher 라이브러리 의존성 추가
  • 감정 구슬 목록 API 연동
    • 그로 인한 ... 클라에서 저장하고 있던 EmotionType, 이미지 Asset 삭제
    • 그로 인한 .. EmotionOrbCollectionViewCell, emotionOrbCollectionView UI 수정
  • 추천 루틴 결과 화면 재사용
    • OnboardingUseCase 삭제
    • OnboardingRecommendedRoutineView 삭제
    • ResultRecommendedRoutineView 구현
    • ResultRecommendedRoutineViewModel에서 선택지에 따른 추천 루틴 받아오는 로직 구현
    • ResultRecommendedRoutineViewModel에서 추천 루틴 등록 로직 구현

✅ Testing

1. 온보딩 > 추천 루틴 Flow

  • 온보딩 선택 > 온보딩 선택지 결과 화면 > 선택지에 따른 결과 추천 루틴 화면
  • 추천 루틴 중복 선택 가능, 등록 시 당일 루틴으로 추가됨 (등록 후 홈 화면으로 이동)
  • 건너뛰기 시, 홈 화면으로 이동

2. 마이페이지 > 추천 루틴 Flow

  • 마이페이지 > 목표 재설정 선택 > 온보딩 선택 > 온보딩 선택지 결과 화면 > 선택지에 따른 결과 추천 루틴 화면
  • 추천 루틴 선택 불가능 (오로지 확인용)
  • 확인 버튼 클릭 시, 마이페이지로 이동

3. 감정등록 > 추천 루틴 Flow

  • 감정 선택 > 선택지에 따른 결과 추천 루틴 화면
  • 추천 루틴 단일 선택 가능, 루틴 등록하기 버튼 클릭 시 해당 값을 갖고 루틴 등록 화면으로 이동
  • 건너뛰기 시, 홈 화면 혹은 추천 루틴 화면으로 이동

📝 Review Note

스크린샷 2025-07-29 23 17 40

3가지 화면이 매우매우 동일해보이지만, 꽤 소소하게 다른 포인트를 갖고 있습니다.

온보딩 마이페이지 (목표 재설정) 감정 등록
Navigation Item progress bar + back button progress bar + back button only back button
Label 텍스트 다름... 어쨌든 달러요 세세하게 다름...
추천 루틴 선택 중복 선택 가능 선택 불가능 단일 선택 가능
확인 버튼 선택한 추천 루틴들 등록 마이페이지로 이동 선택한 추천 루틴 수정 화면으로 이동
건너뛰기 버튼 홈 화면으로 이동 마이 페이지로 이동 들어온 뷰로 이동 (홈 or 추천 루틴)

온보딩 · 목표 재설정은 OnboardingView를 같이 사용하고 있어서, isFromMypage라는 값을 받아 분기처리 하도록 했습니다.


그리고 ResultRecommendedRoutineViewModel에서는 viewModelType을 받아서 분기처리 할 수 있도록 했습니다. (띵 감사 !!!😇!)
다만, View와는 약간 다르다고 생각해서 ResultRecommendedRoutineView에서는 따로 EntryPoint를 받아서 위의 표들에 대한 분기처리를 하도록 했습니다.

생각보다 분기처리할 것이 많아 View에서 값을 갖고 있는게 좋다고 생각했어요 ㅠㅠ. ...


추천 루틴 결과 화면(ResultRecommendedRoutineView)에서 다음 화면으로 이동하는게 각각 다른데 ...
특히 감정 등록 화면에서는 또 홈에서 왔는지 감정 등록 화면에서 왔는지 분기 처리할 값을 넣기가 애매해서 ... 일단 popToViewController으로 처리했습니다. ....

if let navigationController = self.navigationController {
    let viewControllers = navigationController.viewControllers
    if viewControllers.count >= 3 {
        navigationController.popToViewController(viewControllers[viewControllers.count - 3], animated: false)
    }
}

그리고 마이페이지로 돌아가는 것도.. popToRootViewController로 처리했어요 ...

if
    let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
    let window = windowScene.windows.first(where: { $0.isKeyWindow }),
    let tabBarView = window.rootViewController as? TabBarView {
    self.navigationController?.popToRootViewController(animated: false)
    tabBarView.selectedIndex = 2
}

유저 입장에서 넘 어색해보이지 않을까요 ㅠㅠ ?? 더 좋은 방안이 있을까요 ??

📣 Related Issue

  • close #T3-127

Summary by CodeRabbit

Summary by CodeRabbit

새로운 기능

  • 감정 기반 추천 루틴 등록 및 조회 기능이 추가되었습니다.
  • 감정 리스트가 서버에서 동적으로 불러와지며, 감정 오브 이미지가 Kingfisher 라이브러리로 네트워크에서 로드됩니다.
  • 추천 루틴 결과 화면 및 뷰모델이 새롭게 도입되어 온보딩, 마이페이지, 감정 기반 진입을 지원합니다.

버그 수정 및 개선

  • 온보딩 플로우가 개선되어, 추천 루틴 관련 화면 및 뷰모델이 분리되고 진입점에 따라 동작이 달라집니다.
  • 감정 오브 관련 이미지 및 리소스가 정리되었습니다.

라이브러리 및 의존성

  • Kingfisher 라이브러리가 새롭게 추가되었습니다.

기타

  • 불필요한 온보딩 관련 파일 및 코드가 삭제되었습니다.

choijungp added 10 commits July 29, 2025 14:23
- EmotionEndpoint 정의
- EmotionRepositoryProtocol, EmotionRepository 정의 및 구현 (+ EmotionResponseDTO)
- EmotionUseCaseProtocol, EmotionUseCase 정의 및 구현 (+ EmotionEntity)
- 서버 통신으로 받은 값들을 보여주도록 수정
- 감정 구슬 이미지 Kingfisher로 출력
- OnboardingUseCaseProtocol, OnboardingUseCase 삭제
- OnboardingRecommendedRoutineView 삭제
- 기존 온보딩 선택지 · 감정 값을 등록하고 추천 루틴을 받아오는 로직 이동
…l 구현 (#T3-127)

- 추천 루틴 결과 값을 보여주는 화면(ResultRecommendedRoutineView) 구현
- 온보딩 선택지 or 감정 구슬 선택지에 따른 추천 루틴 받아오는 로직
- 선택한 추천 루틴 등록 로직
- MypageView, OnboardingResultView, EmotionRegisterView에서 재사용할 수 있도록 분기 처리
- OnboardingResultView에 그래픽 추가
@choijungp choijungp requested a review from taipaise July 29, 2025 15:00
@choijungp choijungp self-assigned this Jul 29, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jul 29, 2025

Walkthrough

이번 변경에서는 감정 기반 추천 루틴 기능이 대대적으로 도입되었습니다. 도메인, 데이터소스, 프레젠테이션 계층에 감정 엔티티 및 관련 프로토콜, 레포지토리, 유스케이스, 뷰모델, 뷰가 추가 및 리팩터링되었으며, 기존 온보딩 추천 루틴 흐름이 분리·통합되었습니다. Kingfisher 외부 라이브러리가 이미지 URL 비동기 로딩을 위해 도입되었습니다. 감정 오브 이미지 자산 일부가 제거되고, 그래픽 리소스와 뷰 관련 코드가 새 구조에 맞게 정리되었습니다.

Changes

Cohort / File(s) Change Summary
도메인: 감정 엔티티 및 프로토콜/유스케이스 도입
Projects/Domain/Sources/Entity/EmotionEntity.swift, Projects/Domain/Sources/Protocol/Repository/EmotionRepositoryProtocol.swift, Projects/Domain/Sources/Protocol/UseCase/EmotionUseCaseProtocol.swift, Projects/Domain/Sources/UseCase/Emotion/EmotionUseCase.swift
감정 엔티티, 감정 레포지토리 프로토콜, 감정 유스케이스 프로토콜 및 구현 클래스가 추가되었습니다.
도메인: 추천 루틴 유스케이스 리팩터링
Projects/Domain/Sources/Protocol/UseCase/ResultRecommendedRoutineUseCaseProtocol.swift, Projects/Domain/Sources/UseCase/ResultRecommendedRoutine/ResultRecommendedRoutineUseCase.swift
온보딩 유스케이스가 ResultRecommendedRoutine 유스케이스로 리네이밍 및 확장되어 감정 기반 추천을 지원합니다.
도메인: DI 어셈블러 및 프로토콜 정리
Projects/Domain/Sources/DomainDependencyAssembler.swift, Projects/Domain/Sources/Protocol/UseCase/OnboardingUseCaseProtocol.swift
DI 어셈블러가 감정 레포지토리 및 추천 루틴 유스케이스 등록으로 변경, OnboardingUseCaseProtocol 삭제.
데이터소스: 감정 DTO, 엔드포인트, 레포지토리 도입
Projects/DataSource/Sources/DTO/EmotionResponseDTO.swift, Projects/DataSource/Sources/Endpoint/EmotionEndpoint.swift, Projects/DataSource/Sources/Repository/EmotionRepository.swift, Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift
감정 DTO, 감정 API 엔드포인트, 감정 레포지토리 구현 및 DI 등록이 추가되었습니다.
프레젠테이션: 감정 등록 뷰모델/모델/뷰 리팩터링
Projects/Presentation/Sources/EmotionRegister/Model/Emotion.swift, Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift, Projects/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.swift, Projects/Presentation/Sources/EmotionRegister/View/Component/EmotionOrbCollectionViewCell.swift
감정 모델 구조체 및 변환 메서드 추가, 뷰모델이 감정 리스트를 비동기로 받아오고, 뷰가 이를 표시하도록 변경. Kingfisher를 통한 이미지 URL 로딩 적용.
프레젠테이션: 추천 루틴 결과 뷰/뷰모델 신규 도입
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift, Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift
추천 루틴 결과 뷰 및 뷰모델이 새롭게 추가되어 온보딩/마이페이지/감정 기반 진입점에 따라 동작합니다.
프레젠테이션: 온보딩 뷰/뷰모델/DI 리팩터링
Projects/Presentation/Sources/Onboarding/View/OnboardingView.swift, Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift, Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift, Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift, Projects/Presentation/Sources/MyPage/View/MypageView.swift
온보딩 뷰 계층이 추천 루틴 로직 분리, DI 등록 방식 변경, 온보딩 결과 뷰에서 진입점에 따라 분기하도록 리팩터링.
프레젠테이션: 그래픽/이미지 리소스 및 enum 정리
Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift, Projects/Presentation/Resources/Images.xcassets/Graphic/onboarding_graphic.imageset/Contents.json, Projects/Presentation/Resources/Images.xcassets/EmotionOrb/anxiety_orb.imageset/Contents.json, Projects/Presentation/Resources/Images.xcassets/EmotionOrb/calm_orb.imageset/Contents.json, Projects/Presentation/Resources/Images.xcassets/EmotionOrb/lethargy_orb.imageset/Contents.json, Projects/Presentation/Resources/Images.xcassets/EmotionOrb/tired_orb.imageset/Contents.json, Projects/Presentation/Resources/Images.xcassets/EmotionOrb/vitality_orb.imageset/Contents.json, Projects/Presentation/Sources/EmotionRegister/Model/EmotionType.swift
감정 오브 관련 이미지 자산 및 enum 제거, 그래픽 enum이 onboarding 관련 이미지로 정리됨.
프레젠테이션: 프로젝트/패키지 구성 변경
Projects/Presentation/Project.swift, Tuist/Package.swift, Tuist/Package.resolved
Kingfisher 라이브러리 외부 의존성 추가.
프레젠테이션: 온보딩 추천 루틴 뷰 삭제
Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift
기존 온보딩 추천 루틴 뷰 삭제.

Sequence Diagram(s)

sequenceDiagram
actor User
participant EmotionRegisterView
participant EmotionRegisterViewModel
participant EmotionUseCase
participant EmotionRepository
participant NetworkService

User->>EmotionRegisterView: 화면 진입
EmotionRegisterView->>EmotionRegisterViewModel: .fetchEmotions 액션
EmotionRegisterViewModel->>EmotionUseCase: fetchEmotions()
EmotionUseCase->>EmotionRepository: fetchEmotions()
EmotionRepository->>NetworkService: 감정 리스트 API 요청
NetworkService-->>EmotionRepository: 감정 DTO 배열 응답
EmotionRepository-->>EmotionUseCase: EmotionEntity 배열 반환
EmotionUseCase-->>EmotionRegisterViewModel: EmotionEntity 배열 반환
EmotionRegisterViewModel-->>EmotionRegisterView: Emotion 배열 퍼블리시
EmotionRegisterView->>User: 감정 리스트 표시 및 이미지 로딩 (Kingfisher)
Loading
sequenceDiagram
actor User
participant ResultRecommendedRoutineView
participant ResultRecommendedRoutineViewModel
participant ResultRecommendedRoutineUseCase
participant EmotionRepository
participant OnboardingRepository

User->>ResultRecommendedRoutineView: 감정/온보딩/마이페이지 진입
ResultRecommendedRoutineView->>ResultRecommendedRoutineViewModel: .fetchResultRecommendedRoutines 액션
alt 감정 기반
  ResultRecommendedRoutineViewModel->>ResultRecommendedRoutineUseCase: fetchResultRecommendedRoutines(emotion)
  ResultRecommendedRoutineUseCase->>EmotionRepository: registerEmotion(emotion)
  EmotionRepository->>NetworkService: 추천 루틴 API 요청
  NetworkService-->>EmotionRepository: 추천 루틴 DTO 응답
  EmotionRepository-->>ResultRecommendedRoutineUseCase: RecommendedRoutineEntity 배열
  ResultRecommendedRoutineUseCase-->>ResultRecommendedRoutineViewModel: RecommendedRoutineEntity 배열
else 온보딩 기반
  ResultRecommendedRoutineViewModel->>ResultRecommendedRoutineUseCase: fetchResultRecommendedRoutines(onboardingChoices)
  ResultRecommendedRoutineUseCase->>OnboardingRepository: 추천 루틴 API 요청
  OnboardingRepository-->>ResultRecommendedRoutineUseCase: RecommendedRoutineEntity 배열
  ResultRecommendedRoutineUseCase-->>ResultRecommendedRoutineViewModel: RecommendedRoutineEntity 배열
end
ResultRecommendedRoutineViewModel-->>ResultRecommendedRoutineView: 추천 루틴 퍼블리시
ResultRecommendedRoutineView->>User: 루틴 선택/등록/스킵 UI 표시
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • [Feat-T3-69] 감정 등록 화면 구현 #28: 감정 등록 기능을 초기 정적 enum 및 로컬 자산 기반에서 네트워크 기반 동적 리스트로 확장하는 PR로, 감정 관련 뷰모델 및 뷰를 리팩터링함.
  • [Feat-T3-121] 추천 루틴 API 연동 #27: 데이터소스 DI 어셈블러에 RecommendedRoutineRepository 등록 및 관련 레포지토리, DTO, 엔드포인트, 유스케이스 구현 PR로, 본 PR과 DI 레이어에서 연관성이 있음.

Poem

🐰
감정 구슬 반짝반짝,
추천 루틴이 쏟아진다!
새 뷰모델과 뷰,
Kingfisher로 이미지도 척척,
온보딩은 더 똑똑,
토끼는 깡총깡총,
코드 리뷰도 신나게 hop-hop!
✨🦋

Note

⚡️ Unit Test Generation is now available in beta!

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

✨ 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/emotionAPI

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.
    • @coderabbitai modularize this function.
  • 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.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @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: 16

🔭 Outside diff range comments (1)
Projects/Domain/Sources/UseCase/ResultRecommendedRoutine/ResultRecommendedRoutineUseCase.swift (1)

37-39: Dictionary 변환 로직 개선 필요

현재 로직에서는 특정 타입의 choice를 찾지 못하면 break로 루프를 종료하여 불완전한 dictionary를 반환할 수 있습니다.

guard let choice = onboardingChoices.first(where: { $0.onboardingType == type })
-else { break }
+else { continue }

또는 모든 필수 타입이 있는지 검증하는 로직을 추가하는 것도 고려해보세요.

🧹 Nitpick comments (7)
Projects/Domain/Sources/DomainDependencyAssembler.swift (1)

42-54: 의존성 해결 패턴 일관성 개선 제안

일부 의존성은 메서드 시작 부분에서 해결하고(authRepository, emotionRepository), 다른 의존성은 등록 클로저 내에서 해결하고 있습니다. 일관된 패턴을 사용하면 코드 가독성과 유지보수성이 향상됩니다.

모든 의존성을 메서드 시작 부분에서 해결하거나, 모두 클로저 내에서 해결하는 방식으로 통일하는 것을 고려해보세요.

Projects/DataSource/Sources/Endpoint/EmotionEndpoint.swift (1)

29-35: 불필요한 헤더 제거 제안

"accept": "*/*" 헤더는 기본값이므로 명시적으로 추가할 필요가 없습니다.

     var headers: [String : String] {
         let headers: [String: String] = [
-            "Content-Type": "application/json",
-            "accept": "*/*"
+            "Content-Type": "application/json"
         ]
         return headers
     }
Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift (1)

46-49: 에러 처리 구현 필요

TODO 주석에 명시된 대로 사용자에게 에러를 알리는 토스트뷰 구현이 필요합니다. 과거 학습 내용에 따르면 CustomAlertView를 만들어 에러 처리를 UI로 해결하실 계획이 있으신 것으로 알고 있습니다.

CustomAlertView 구현이나 새로운 이슈 생성을 도와드릴까요?

Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (1)

93-94: 불필요한 빈 배열 전송

실제 데이터를 보내기 전에 빈 배열을 먼저 보내는 것은 UI 깜빡임을 유발할 수 있습니다. 빈 배열 전송을 제거하는 것이 좋습니다.

let recommendedRoutines = entities.map({ $0.toRecommendedRoutine() })
-resultRecommendedRoutinesSubject.send([])
resultRecommendedRoutinesSubject.send(recommendedRoutines)

Also applies to: 101-102

Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (1)

63-63: 사용하지 않는 클로저 파라미터 수정

사용하지 않는 클로저 파라미터는 _로 대체하는 것이 좋습니다.

-}, completion: { [weak self] finshed in
+}, completion: { [weak self] _ in
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (2)

96-103: cancellables 초기화를 프로퍼티 선언 시점으로 이동하세요

cancellables를 프로퍼티 선언 시점에 초기화하면 초기화 로직이 더 간결해집니다.

-    private var cancellables: Set<AnyCancellable>
+    private var cancellables = Set<AnyCancellable>()

     private let entryPoint: EntryPoint
     init(entryPoint: EntryPoint, viewModel: ResultRecommendedRoutineViewModel) {
         self.entryPoint = entryPoint
-        cancellables = []
         super.init(viewModel: viewModel)
     }

335-336: 탭 인덱스를 상수로 관리하세요

하드코딩된 탭 인덱스 값을 상수로 관리하면 유지보수가 용이합니다.

+private enum TabIndex {
+    static let routine = 2
+}

 self.navigationController?.popToRootViewController(animated: false)
-tabBarView.selectedIndex = 2
+tabBarView.selectedIndex = TabIndex.routine
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 52339a4 and 6532e55.

⛔ Files ignored due to path filters (24)
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/anxiety_orb.imageset/anxiety_orb.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/anxiety_orb.imageset/anxiety_orb@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/anxiety_orb.imageset/anxiety_orb@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/calm_orb.imageset/calm_orb.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/calm_orb.imageset/calm_orb@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/calm_orb.imageset/calm_orb@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/lethargy_orb.imageset/lethargy_orb.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/lethargy_orb.imageset/lethargy_orb@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/lethargy_orb.imageset/lethargy_orb@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/satisfied_orb.imageset/satisfied_orb.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/satisfied_orb.imageset/satisfied_orb@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/satisfied_orb.imageset/satisfied_orb@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/tired_orb.imageset/tired_orb.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/tired_orb.imageset/tired_orb@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/tired_orb.imageset/tired_orb@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/vitality_orb.imageset/vitality_orb.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/vitality_orb.imageset/vitality_orb@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/vitality_orb.imageset/vitality_orb@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Graphic/intro_graphic.imageset/IntroGraphic.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Graphic/intro_graphic.imageset/IntroGraphic@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Graphic/intro_graphic.imageset/IntroGraphic@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Graphic/onboarding_graphic.imageset/onboarding_graphic.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Graphic/onboarding_graphic.imageset/onboarding_graphic@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Graphic/onboarding_graphic.imageset/onboarding_graphic@3x.png is excluded by !**/*.png
📒 Files selected for processing (35)
  • Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1 hunks)
  • Projects/DataSource/Sources/DTO/EmotionResponseDTO.swift (1 hunks)
  • Projects/DataSource/Sources/Endpoint/EmotionEndpoint.swift (1 hunks)
  • Projects/DataSource/Sources/Repository/EmotionRepository.swift (1 hunks)
  • Projects/Domain/Sources/DomainDependencyAssembler.swift (1 hunks)
  • Projects/Domain/Sources/Entity/EmotionEntity.swift (1 hunks)
  • Projects/Domain/Sources/Protocol/Repository/EmotionRepositoryProtocol.swift (1 hunks)
  • Projects/Domain/Sources/Protocol/UseCase/EmotionUseCaseProtocol.swift (1 hunks)
  • Projects/Domain/Sources/Protocol/UseCase/OnboardingUseCaseProtocol.swift (0 hunks)
  • Projects/Domain/Sources/Protocol/UseCase/ResultRecommendedRoutineUseCaseProtocol.swift (1 hunks)
  • Projects/Domain/Sources/UseCase/Emotion/EmotionUseCase.swift (1 hunks)
  • Projects/Domain/Sources/UseCase/ResultRecommendedRoutine/ResultRecommendedRoutineUseCase.swift (1 hunks)
  • Projects/Presentation/Project.swift (1 hunks)
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/anxiety_orb.imageset/Contents.json (0 hunks)
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/calm_orb.imageset/Contents.json (0 hunks)
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/lethargy_orb.imageset/Contents.json (0 hunks)
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/tired_orb.imageset/Contents.json (0 hunks)
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/vitality_orb.imageset/Contents.json (0 hunks)
  • Projects/Presentation/Resources/Images.xcassets/Graphic/onboarding_graphic.imageset/Contents.json (1 hunks)
  • Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift (1 hunks)
  • Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (2 hunks)
  • Projects/Presentation/Sources/EmotionRegister/Model/Emotion.swift (1 hunks)
  • Projects/Presentation/Sources/EmotionRegister/Model/EmotionType.swift (0 hunks)
  • Projects/Presentation/Sources/EmotionRegister/View/Component/EmotionOrbCollectionViewCell.swift (2 hunks)
  • Projects/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.swift (4 hunks)
  • Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift (1 hunks)
  • Projects/Presentation/Sources/MyPage/View/MypageView.swift (1 hunks)
  • Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift (0 hunks)
  • Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (8 hunks)
  • Projects/Presentation/Sources/Onboarding/View/OnboardingView.swift (2 hunks)
  • Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (7 hunks)
  • Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (1 hunks)
  • Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (1 hunks)
  • Tuist/Package.resolved (1 hunks)
  • Tuist/Package.swift (1 hunks)
💤 Files with no reviewable changes (8)
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/anxiety_orb.imageset/Contents.json
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/tired_orb.imageset/Contents.json
  • Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift
  • Projects/Domain/Sources/Protocol/UseCase/OnboardingUseCaseProtocol.swift
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/vitality_orb.imageset/Contents.json
  • Projects/Presentation/Sources/EmotionRegister/Model/EmotionType.swift
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/lethargy_orb.imageset/Contents.json
  • Projects/Presentation/Resources/Images.xcassets/EmotionOrb/calm_orb.imageset/Contents.json
🧰 Additional context used
🧠 Learnings (12)
📓 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/MyPage/View/MypageView.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/Onboarding/View/OnboardingView.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/Domain/Sources/UseCase/ResultRecommendedRoutine/ResultRecommendedRoutineUseCase.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/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.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/Onboarding/View/OnboardingResultView.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/Onboarding/ViewModel/OnboardingViewModel.swift (2)

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/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/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.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/Protocol/UseCase/ResultRecommendedRoutineUseCaseProtocol.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/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.

🧬 Code Graph Analysis (11)
Projects/Domain/Sources/Protocol/Repository/EmotionRepositoryProtocol.swift (2)
Projects/Domain/Sources/UseCase/Emotion/EmotionUseCase.swift (1)
  • fetchEmotions (15-18)
Projects/DataSource/Sources/Repository/EmotionRepository.swift (2)
  • fetchEmotions (13-20)
  • registerEmotion (22-29)
Projects/Domain/Sources/DomainDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
  • resolve (18-25)
  • register (14-16)
Projects/DataSource/Sources/Repository/EmotionRepository.swift (1)
Projects/Domain/Sources/UseCase/Emotion/EmotionUseCase.swift (1)
  • fetchEmotions (15-18)
Projects/Domain/Sources/UseCase/ResultRecommendedRoutine/ResultRecommendedRoutineUseCase.swift (3)
Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (3)
  • fetchResultRecommendedRoutines (70-87)
  • fetchResultRecommendedRoutines (90-95)
  • fetchResultRecommendedRoutines (98-103)
Projects/DataSource/Sources/Repository/OnboardingRepository.swift (1)
  • registerOnboarding (13-20)
Projects/DataSource/Sources/Repository/EmotionRepository.swift (1)
  • registerEmotion (22-29)
Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (3)
Projects/Domain/Sources/UseCase/ResultRecommendedRoutine/ResultRecommendedRoutineUseCase.swift (3)
  • fetchResultRecommendedRoutines (18-22)
  • fetchResultRecommendedRoutines (24-27)
  • registerRecommendedRoutines (29-31)
Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift (1)
  • toRecommendedRoutine (33-41)
Projects/Shared/Sources/Logger/Logger.swift (1)
  • log (13-22)
Projects/Domain/Sources/Protocol/UseCase/EmotionUseCaseProtocol.swift (2)
Projects/Domain/Sources/UseCase/Emotion/EmotionUseCase.swift (1)
  • fetchEmotions (15-18)
Projects/DataSource/Sources/Repository/EmotionRepository.swift (1)
  • fetchEmotions (13-20)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
  • register (14-16)
  • resolve (18-25)
Projects/Domain/Sources/Protocol/UseCase/ResultRecommendedRoutineUseCaseProtocol.swift (2)
Projects/Domain/Sources/UseCase/ResultRecommendedRoutine/ResultRecommendedRoutineUseCase.swift (3)
  • fetchResultRecommendedRoutines (18-22)
  • fetchResultRecommendedRoutines (24-27)
  • registerRecommendedRoutines (29-31)
Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (3)
  • fetchResultRecommendedRoutines (70-87)
  • fetchResultRecommendedRoutines (90-95)
  • fetchResultRecommendedRoutines (98-103)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
  • register (14-16)
Projects/Domain/Sources/UseCase/Emotion/EmotionUseCase.swift (1)
Projects/DataSource/Sources/Repository/EmotionRepository.swift (1)
  • fetchEmotions (13-20)
Projects/DataSource/Sources/Endpoint/EmotionEndpoint.swift (1)
Projects/DataSource/Sources/Repository/EmotionRepository.swift (2)
  • fetchEmotions (13-20)
  • registerEmotion (22-29)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift

[Warning] 63-63: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift

[Warning] 47-47: TODOs should be resolved (에러 처리 토스트뷰)

(todo)

⏰ 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 (29)
Projects/Presentation/Project.swift (1)

16-16: LGTM! Kingfisher 의존성 추가가 적절합니다.

감정 구슬 이미지를 URL에서 비동기적으로 로드하기 위한 Kingfisher 라이브러리 추가가 PR 목표와 일치하며, 의존성 배열에서의 위치도 적절합니다.

Tuist/Package.resolved (1)

21-29: LGTM! Kingfisher 패키지 해상도가 올바르게 추가되었습니다.

Kingfisher 8.5.0 버전이 적절하게 해상도에 추가되었으며, 패키지 구조와 리비전 정보가 올바릅니다.

Tuist/Package.swift (1)

9-9: LGTM! Kingfisher 패키지 의존성이 적절하게 선언되었습니다.

Kingfisher 8.0.0 이상 버전으로 최소 버전 제약이 적절하게 설정되었으며, 패키지 URL이 올바릅니다.

Projects/Domain/Sources/Protocol/Repository/EmotionRepositoryProtocol.swift (1)

8-18: LGTM! 잘 설계된 Repository 프로토콜입니다.

EmotionRepositoryProtocol이 Clean Architecture 원칙을 잘 따르고 있습니다:

  • 비동기 네트워크 작업에 적합한 async throws 메서드 시그니처
  • DTO가 아닌 도메인 엔티티 반환으로 계층 분리 준수
  • 명확하고 목적이 분명한 메서드 네이밍
  • 포괄적인 한국어 문서화

관련 코드에서 DataSource 계층의 구현체와 Domain 계층의 UseCase에서 적절히 활용되는 것을 확인했습니다.

Projects/Presentation/Resources/Images.xcassets/Graphic/onboarding_graphic.imageset/Contents.json (1)

4-4: LGTM! 이미지 에셋 이름이 일관되게 변경되었습니다.

모든 스케일 팩터(1x, 2x, 3x)에서 "satisfied_orb"에서 "onboarding_graphic"으로 파일명이 일관되게 변경되었습니다. 이는 정적 감정 구슬 이미지에서 API 기반 동적 데이터로의 리팩터링 목표와 잘 맞습니다.

Also applies to: 9-9, 14-14

Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1)

32-34: 의존성 등록이 올바르게 구현되었습니다.

EmotionRepositoryProtocol의 등록이 기존 패턴과 일치하며, 감정 기능을 위한 의존성 주입이 적절히 설정되었습니다.

Projects/Domain/Sources/Protocol/UseCase/EmotionUseCaseProtocol.swift (1)

8-12: 프로토콜 정의가 명확하고 적절합니다.

비동기 처리와 에러 핸들링을 위한 async throws 시그니처가 올바르게 정의되었고, 한국어 문서화도 잘 작성되었습니다.

Projects/Presentation/Sources/MyPage/View/MypageView.swift (1)

161-166: MyPage에서 목표 재설정 플로우가 적절히 구현되었습니다.

isFromMypage 파라미터를 통해 온보딩 플로우를 구분하고, hidesBottomBarWhenPushed를 설정하여 네비게이션 UX를 개선한 것이 좋습니다.

Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (3)

32-34: OnboardingViewModel 등록이 단순화되었습니다.

의존성이 제거되어 등록이 간소화된 것이 적절합니다.


50-55: EmotionRegisterViewModel의 의존성 주입이 올바르게 구현되었습니다.

EmotionUseCaseProtocol 의존성을 적절히 해결하고, 누락 시 명확한 에러 메시지를 제공합니다.


61-66: ResultRecommendedRoutineViewModel 등록이 적절히 추가되었습니다.

새로운 뷰모델의 의존성 주입이 기존 패턴과 일치하며, 적절한 에러 처리를 포함합니다.

Projects/Domain/Sources/UseCase/Emotion/EmotionUseCase.swift (1)

8-19: EmotionUseCase 구현이 클린 아키텍처 원칙을 잘 따릅니다.

의존성 주입, 에러 전파, 그리고 레포지토리 계층으로의 단순한 패스스루 구현이 적절합니다. 비즈니스 로직이 필요하지 않은 현재 요구사항에 맞는 깔끔한 구현입니다.

Projects/Presentation/Sources/EmotionRegister/View/Component/EmotionOrbCollectionViewCell.swift (2)

8-8: Kingfisher 라이브러리 추가가 적절합니다.

비동기 이미지 로딩을 위한 Kingfisher 라이브러리 도입이 정적 이미지에서 동적 URL 기반 이미지 로딩으로의 전환에 적합합니다.


57-63: API 기반 데이터 모델로의 전환이 잘 구현되었습니다.

기존 EmotionType enum에서 Emotion 모델로 변경되어 동적 데이터를 처리할 수 있게 되었고, optional URL에 대한 안전한 처리와 Kingfisher를 통한 이미지 로딩이 적절히 구현되었습니다.

Projects/Presentation/Sources/Onboarding/View/OnboardingView.swift (2)

39-50: MyPage 진입점 구분을 위한 플래그 추가가 적절합니다.

isFromMypage 플래그를 통해 온보딩과 MyPage에서의 목표 재설정 플로우를 구분할 수 있게 되었습니다. 기본값 false 설정으로 기존 동작을 보장하면서 새로운 요구사항을 지원합니다.


221-227: 네비게이션 체인에서 상태 전달이 올바르게 구현되었습니다.

다음 단계로 이동할 때 isFromMypage 플래그를 일관되게 전달하여 전체 플로우에서 진입점 정보를 보존합니다. 이는 추천 루틴 결과 화면 재사용이라는 PR 목표와 일치합니다.

Projects/Presentation/Sources/EmotionRegister/Model/Emotion.swift (1)

11-15: 프레젠테이션 모델 구조가 적절합니다.

도메인과 프레젠테이션 계층 간의 명확한 분리를 위한 별도 모델 정의가 잘 되어 있습니다.

Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift (1)

15-17: 정적 이미지 자산 정리가 적절합니다.

기존 정적 감정 구슬 이미지들을 제거하고 API 기반 동적 이미지 로딩으로 전환한 것이 전체 아키텍처 변경사항과 일치합니다. 새로운 onboardingGraphic 추가와 이미지 명명 규칙 개선도 좋습니다.

Projects/Domain/Sources/Protocol/UseCase/ResultRecommendedRoutineUseCaseProtocol.swift (1)

10-24: 프로토콜 설계가 깔끔합니다

각 메서드가 명확한 책임을 가지고 있으며, 문서화도 잘 되어 있습니다. 온보딩과 감정 기반 추천 루틴 조회를 별도 메서드로 분리한 설계가 적절합니다.

Projects/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.swift (1)

114-117: 컬렉션 뷰 리로드 최적화

감정 리스트가 비어있지 않을 때만 리로드하는 것은 좋은 접근입니다. 성능 최적화가 잘 되어있네요.

Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (1)

197-209: 의존성 주입 및 뷰 구성 로직 잘 구현됨

isFromMypage 플래그에 따라 적절히 분기 처리하여 뷰모델을 구성하고 뷰를 생성하는 로직이 깔끔하게 구현되었습니다.

Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (4)

13-28: Input/Output 구조가 단순화되어 개선되었습니다!

추천 루틴 관련 로직이 제거되고 온보딩 선택에만 집중하도록 리팩토링된 것이 좋습니다.


30-49: 의존성이 제거되어 뷰모델이 더 단순해졌습니다!

온보딩 로직에만 집중하도록 구조가 개선되었습니다.


142-146: 다음 버튼 활성화 로직이 단순화되어 좋습니다!

감정 선택의 경우 비어있지 않으면 활성화되는 간단한 로직이 적절합니다.


167-184: 온보딩 선택지 통합 로직이 안전하게 구현되었습니다!

필수 선택 항목 검증과 배열 구성이 적절합니다.

Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (4)

15-65: EntryPoint enum 설계가 잘 되어 있습니다!

진입점별로 UI 동작과 텍스트를 명확하게 분리하여 관리하는 구조가 좋습니다. 특히 mypage에서는 루틴 선택을 비활성화하고 건너뛰기 버튼을 숨기는 등 각 플로우에 맞는 세심한 처리가 잘 되어 있습니다.


67-87: 레이아웃 상수 관리가 훌륭합니다!

화면 크기에 따른 동적 여백 조정이 잘 구현되어 있어 다양한 기기에서 일관된 사용자 경험을 제공할 수 있습니다.


109-118: 뷰 라이프사이클 메서드가 적절히 구현되었습니다!

추천 루틴 fetch 타이밍과 네비게이션 설정이 적절한 시점에 수행됩니다.


119-213: UI 구성이 체계적으로 잘 되어 있습니다!

진입점별 UI 속성 설정과 레이아웃 제약이 명확하게 구현되어 있습니다. 특히 진입점에 따른 버튼 상태 초기화가 각 플로우의 요구사항에 맞게 적절히 처리되었습니다.

Comment thread Projects/DataSource/Sources/DTO/EmotionResponseDTO.swift Outdated
return EmotionEntity(
emtionType: type,
emotionName: name,
emotionImageUrl: URL(string: imageUrl))
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Jul 29, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

URL 생성 시 안전성 확보 필요

URL(string:)은 유효하지 않은 URL 문자열의 경우 nil을 반환할 수 있습니다. API 응답의 imageUrl이 항상 유효한 URL 형식임을 보장할 수 없다면, 안전한 처리가 필요합니다.

EmotionEntityemotionImageUrl이 옵셔널 타입이라면 현재 코드가 적절합니다. 그렇지 않다면 기본값 처리나 에러 처리를 고려해주세요.

🤖 Prompt for AI Agents
In Projects/DataSource/Sources/DTO/EmotionResponseDTO.swift at line 28, the
URL(string: imageUrl) initializer can return nil if imageUrl is invalid. Check
if emotionImageUrl in EmotionEntity is optional; if not, modify the code to
safely unwrap the URL or provide a default URL or error handling to avoid
runtime crashes due to nil values.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

맞는 말씀이지만, 서버님들이 유효한 URL을 내려주실거라 믿어요~
어차피 유효한 URL이 아니면 기본 이미지를 표시하겟죠???????

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

마주아요 !!! 유효한 URL을 내려주실거지만 ~ 없다면 default 구슬 (empty 구슬을 보여드리겠슴니다 !!!)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@taipaise 네, 말씀하신 대로 EmotionEntityemotionImageUrl이 옵셔널 타입(URL?)이라서 현재 구현이 안전합니다!

그리고 Kingfisher를 사용하고 계시니 nil이나 유효하지 않은 URL의 경우 자동으로 placeholder나 기본 이미지를 표시해줄 거예요. 서버 팀을 믿고 가시는 것도 합리적인 접근 방식이네요 👍

   /|   /|  
  ( :v:  ) 
   |(_)|
  /     \
 /       \
/_________\

✏️ Learnings added
Learnt from: taipaise
PR: YAPP-Github/Bitnagil-iOS#29
File: Projects/DataSource/Sources/DTO/EmotionResponseDTO.swift:28-28
Timestamp: 2025-07-30T13:04:33.867Z
Learning: 사용자 taipaise는 서버에서 제공하는 데이터의 품질을 신뢰하는 편이며, URL 검증보다는 서버 팀의 데이터 검증을 믿는 접근 방식을 선호한다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +14 to +20
let endpoint = EmotionEndpoint.fetchEmotions
guard let response = try await networkService.request(endpoint: endpoint, type: [EmotionResponseDTO].self)
else { return [] }

let emotionEntities = response.compactMap({ $0.toEmotionEntity() })
return emotionEntities
}
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

에러 처리 개선 필요

두 메서드 모두 네트워크 요청 실패 시 빈 배열을 반환하고 있습니다. 이는 실제로 빈 결과와 에러를 구분하기 어렵게 만듭니다.

호출하는 측에서 에러를 적절히 처리할 수 있도록 에러를 throw하는 것이 좋습니다:

-guard let response = try await networkService.request(endpoint: endpoint, type: [EmotionResponseDTO].self)
-else { return [] }
+let response = try await networkService.request(endpoint: endpoint, type: [EmotionResponseDTO].self)

Also applies to: 22-29

🤖 Prompt for AI Agents
In Projects/DataSource/Sources/Repository/EmotionRepository.swift around lines
14 to 20 and 22 to 29, the current implementation returns an empty array on
network request failure, which makes it impossible to distinguish between an
actual empty result and an error. Modify both methods to throw errors instead of
returning empty arrays when the network request fails. Update the function
signatures to include throws and propagate the caught errors so that callers can
handle them appropriately.

Comment thread Projects/Domain/Sources/DomainDependencyAssembler.swift Outdated
Comment thread Projects/Domain/Sources/Entity/EmotionEntity.swift
Comment on lines +310 to +316
case .emotion:
if let navigationController = self.navigationController {
let viewControllers = navigationController.viewControllers
if viewControllers.count >= 3 {
navigationController.popToViewController(viewControllers[viewControllers.count - 3], animated: 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

네비게이션 로직을 개선하세요

현재 하드코딩된 값(3)으로 네비게이션 스택을 관리하는 것은 취약합니다. 특정 뷰 타입을 찾아서 이동하는 방식이 더 안전합니다.

 case .emotion:
     if let navigationController = self.navigationController {
         let viewControllers = navigationController.viewControllers
-        if viewControllers.count >= 3 {
-            navigationController.popToViewController(viewControllers[viewControllers.count - 3], animated: false)
-        }
+        // EmotionRegisterView로 돌아가기
+        if let targetVC = viewControllers.first(where: { $0 is EmotionRegisterView }) {
+            navigationController.popToViewController(targetVC, animated: false)
+        }
     }
📝 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
case .emotion:
if let navigationController = self.navigationController {
let viewControllers = navigationController.viewControllers
if viewControllers.count >= 3 {
navigationController.popToViewController(viewControllers[viewControllers.count - 3], animated: false)
}
}
case .emotion:
if let navigationController = self.navigationController {
let viewControllers = navigationController.viewControllers
// EmotionRegisterView로 돌아가기
if let targetVC = viewControllers.first(where: { $0 is EmotionRegisterView }) {
navigationController.popToViewController(targetVC, animated: false)
}
}
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift
around lines 310 to 316, the navigation logic uses a hardcoded index (3) to pop
to a view controller, which is fragile. Instead, iterate through the
navigationController's viewControllers array to find the specific view
controller type you want to navigate back to, then pop to that view controller
safely without relying on fixed indices.

Comment on lines +339 to +340
guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self)
else { fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.") }
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

의존성 주입 실패를 더 우아하게 처리하세요

fatalError 대신 옵셔널 바인딩으로 처리하고 에러 로깅을 추가하는 것이 좋습니다.

-guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self)
-else { fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.") }
+guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) else {
+    BitnagilLogger.log(logType: .error, message: "routineCreationViewModel 의존성이 등록되지 않았습니다.")
+    return
+}
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift
around lines 339 to 340, replace the fatalError call used when dependency
injection for RoutineCreationViewModel fails with optional binding that
gracefully handles the nil case. Add error logging to record the failure instead
of crashing the app, allowing the app to continue running or handle the error
appropriately.

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: 0

♻️ Duplicate comments (4)
Projects/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.swift (1)

126-127: 의존성 해결 실패 시 더 안전한 처리가 필요합니다

의존성이 등록되지 않은 경우 앱이 크래시됩니다. 사용자에게 더 나은 경험을 제공하기 위해 에러 처리를 고려해보세요.

Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (3)

237-248: 루틴 등록 실패 시 사용자 피드백을 추가하세요

현재 등록 실패 시 로그만 남기고 있습니다. 사용자에게 에러 알림을 표시하는 것이 좋겠습니다.

학습 기록에 따르면 CustomAlertView를 만들어 에러 처리를 할 계획이 있으신 것으로 알고 있습니다. 에러 알림 구현을 도와드릴까요?


310-316: 네비게이션 로직을 개선하세요

현재 하드코딩된 값(3)으로 네비게이션 스택을 관리하는 것은 취약합니다. 특정 뷰 타입을 찾아서 이동하는 방식이 더 안전합니다.


339-340: 의존성 주입 실패를 더 우아하게 처리하세요

fatalError 대신 옵셔널 바인딩으로 처리하고 에러 로깅을 추가하는 것이 좋습니다.

🧹 Nitpick comments (1)
Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (1)

94-95: UI 깜빡임을 방지하기 위해 이중 send 패턴을 개선해보세요

빈 배열을 먼저 보낸 후 실제 데이터를 보내는 패턴이 UI에서 깜빡임을 유발할 수 있습니다.

다음과 같이 개선할 수 있습니다:

-        resultRecommendedRoutinesSubject.send([])
-        resultRecommendedRoutinesSubject.send(recommendedRoutines)
+        resultRecommendedRoutinesSubject.send(recommendedRoutines)

Also applies to: 102-103

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6532e55 and 7e53f73.

📒 Files selected for processing (8)
  • Projects/DataSource/Sources/DTO/EmotionResponseDTO.swift (1 hunks)
  • Projects/Domain/Sources/DomainDependencyAssembler.swift (1 hunks)
  • Projects/Domain/Sources/Entity/EmotionEntity.swift (1 hunks)
  • Projects/Presentation/Sources/EmotionRegister/Model/Emotion.swift (1 hunks)
  • Projects/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.swift (4 hunks)
  • Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift (1 hunks)
  • Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (1 hunks)
  • Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • Projects/Domain/Sources/DomainDependencyAssembler.swift
  • Projects/Presentation/Sources/EmotionRegister/Model/Emotion.swift
  • Projects/Domain/Sources/Entity/EmotionEntity.swift
  • Projects/DataSource/Sources/DTO/EmotionResponseDTO.swift
🧰 Additional context used
🧠 Learnings (4)
📓 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/EmotionRegister/View/EmotionRegisterView.swift (2)

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/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.

Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (5)

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/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.

Learnt from: choijungp
PR: #19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.

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/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.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/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.

Learnt from: choijungp
PR: #19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.

🧬 Code Graph Analysis (1)
Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift (2)
Projects/Presentation/Sources/EmotionRegister/Model/Emotion.swift (1)
  • toEmotion (18-23)
Projects/Shared/Sources/Logger/Logger.swift (1)
  • log (13-22)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift

[Warning] 47-47: TODOs should be resolved (에러 처리 토스트뷰)

(todo)

Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift

[Warning] 84-84: TODOs should be resolved (에러 처리)

(todo)

🔇 Additional comments (6)
Projects/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.swift (1)

41-41: 동적 감정 데이터 구조로의 개선 좋습니다!

정적 enum에서 API 기반 동적 데이터로 변경한 아키텍처 개선이 우수합니다. 확장성과 유지보수성이 크게 향상되었습니다.

Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift (2)

24-30: 도메인 계층 통합이 잘 구현되었습니다!

UseCase 의존성 주입과 Combine 퍼블리셔 구조가 깔끔하게 설계되었습니다. 관심사 분리가 잘 이루어졌네요.


39-51: 비동기 에러 처리 구조가 적절합니다

async/await 패턴과 에러 로깅이 잘 구현되어 있습니다. 학습 기록에 따르면 CustomAlertView 구현 계획이 있으시니, 현재 TODO 주석은 적절합니다.

Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (2)

14-18: 다양한 컨텍스트를 지원하는 깔끔한 아키텍처입니다!

ViewModelType enum을 통해 온보딩, 마이페이지, 감정 등록의 세 가지 흐름을 하나의 ViewModel로 통합한 설계가 우수합니다.


116-119: 감정 컨텍스트의 단일 선택 로직이 잘 구현되었습니다

감정 기반 루틴 추천에서는 단일 선택만 허용하는 비즈니스 로직이 적절히 구현되어 있습니다.

Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (1)

15-65: EntryPoint enum 설계가 매우 우수합니다!

세 가지 진입점(온보딩, 마이페이지, 감정 등록)에 따른 UI 차이를 computed property로 깔끔하게 처리한 설계가 훌륭합니다. 코드 중복 없이 각 컨텍스트의 요구사항을 잘 반영했습니다.

Copy link
Copy Markdown
Collaborator

@taipaise taipaise left a comment

Choose a reason for hiding this comment

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

image

토끼도 인정하는 대형 작업!!! 이번에도 고생하셨습니다.. 진짜 iOS는 1인 프로젝트 그 잡채라고 당당히 말씀 드릴 수 있을거 같아요!!!!

조금만 더 파이팅입니다~!!!!~!

return EmotionEntity(
emtionType: type,
emotionName: name,
emotionImageUrl: URL(string: imageUrl))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

맞는 말씀이지만, 서버님들이 유효한 URL을 내려주실거라 믿어요~
어차피 유효한 URL이 아니면 기본 이미지를 표시하겟죠???????

static let skipButtonBottomSpacing: CGFloat = 20

static var mainLabelTopSpacing: CGFloat {
let height = UIScreen.main.bounds.height
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

정말 슬프게도,, UIScreen.main은 deprecated 되었답니다 ㅜㅜ
image

아래와 같은 방법으로도, 화면의 height를 구할 수 있긴 합니다

guard let screen =  view.window?.windowScene?.screen else { return }
let height = screen.bounds.height

이미 알고 계신 부분일 수 있지만, 사실 위와 같은 코드는 지금같은 상황에서는 view 인스턴스가 없어서 높이를 잡을 수 없는 문제가 발생할 것 같긴합니다.. 따라서 Layout 열거형 안에서 디바이스 화면 크기를 바탕으로 값을 유동적으로 가져가기 조금 힘들지 않을까?? 합니다.

혹시 이렇게 디바이스에 따라 값을 다르게 한 이유가 있을까요? 추가로 만약 꼭 디바이스 높이에 따라 다르게 해야한다면, 이 부분은 Layout 열거형 안이 아닌, VC의 프로퍼티로 관리해야할 수도 있을 것 같습니다~!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

요 부분은 SE 대응때문에 어쩔 수 없이 값을 다르게 가져갔습니다 !!
선택지가 2줄로 표시되고, subTitle도 있다면 mainLabelTopSpacing를 조정해야 했습니다. ㅜㅜ

스크린샷 2025-07-30 22 06 39

그럼 요부분은 Layout이 아닌 밑으로 빼보도록 하겠습니다 !

confirmButton.snp.makeConstraints { make in
make.leading.equalTo(safeArea).offset(Layout.horizontalMargin)
make.trailing.equalTo(safeArea).inset(Layout.horizontalMargin)
if entryPoint.isHiddenSkipButton {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

분기가 많이 들어가긴 하네요 ㅜㅜ 고생하셨습니다.

}

// ViewModelType의 설정합니다. (setter)
func configure(viewModelType: ResultRecommendedRoutineViewModelType) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

진짜 꼭!!! 나중에 고도화 해봐요!!!! setter 주입 좀 더 고민해보는걸로~!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

물론 setter가 나쁘다는건 아닙니다!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

인정인정 데스노트 다 죽이기 목표 .......... ☠️🔥

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: 1

♻️ Duplicate comments (4)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (1)

213-214: 의존성 주입 실패를 더 우아하게 처리하세요

fatalError 대신 옵셔널 바인딩으로 처리하고 에러 로깅을 추가하는 것이 좋습니다.

-        guard let resultRecommendedRoutineViewModel = DIContainer.shared.resolve(type: ResultRecommendedRoutineViewModel.self)
-        else{ fatalError("resultRecommendedRoutineViewModel 의존성이 등록되지 않았습니다.") }
+        guard let resultRecommendedRoutineViewModel = DIContainer.shared.resolve(type: ResultRecommendedRoutineViewModel.self) else {
+            BitnagilLogger.log(logType: .error, message: "resultRecommendedRoutineViewModel 의존성이 등록되지 않았습니다.")
+            return
+        }
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (3)

253-264: 루틴 등록 실패 시 사용자 피드백을 추가하세요

현재 등록 실패 시 로그만 남기고 있습니다. 사용자에게 에러 알림을 표시하는 것이 좋겠습니다.

learnings에 따르면 CustomAlertView를 만들어 에러 처리를 할 계획이 있으신 것으로 알고 있습니다. 에러 알림 구현을 도와드릴까요?


326-332: 네비게이션 로직을 개선하세요

현재 하드코딩된 값(3)으로 네비게이션 스택을 관리하는 것은 취약합니다. 특정 뷰 타입을 찾아서 이동하는 방식이 더 안전합니다.

 case .emotion:
     if let navigationController = self.navigationController {
         let viewControllers = navigationController.viewControllers
-        if viewControllers.count >= 3 {
-            navigationController.popToViewController(viewControllers[viewControllers.count - 3], animated: false)
-        }
+        // EmotionRegisterView로 돌아가기
+        if let targetVC = viewControllers.first(where: { $0 is EmotionRegisterView }) {
+            navigationController.popToViewController(targetVC, animated: false)
+        }
     }

355-356: 의존성 주입 실패를 더 우아하게 처리하세요

fatalError 대신 옵셔널 바인딩으로 처리하고 에러 로깅을 추가하는 것이 좋습니다.

-            guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self)
-            else { fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.") }
+            guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) else {
+                BitnagilLogger.log(logType: .error, message: "routineCreationViewModel 의존성이 등록되지 않았습니다.")
+                return
+            }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7e53f73 and 93e9a1b.

📒 Files selected for processing (3)
  • Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (9 hunks)
  • Projects/Presentation/Sources/Onboarding/View/OnboardingView.swift (5 hunks)
  • Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Projects/Presentation/Sources/Onboarding/View/OnboardingView.swift
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
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 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (5)

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/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.

Learnt from: choijungp
PR: #19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.

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/Onboarding/View/OnboardingResultView.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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.

🧬 Code Graph Analysis (1)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (2)
Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (1)
  • action (51-65)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
  • resolve (18-25)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift

[Warning] 63-63: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

⏰ 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 (10)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (7)

10-11: 적절한 모듈 임포트입니다

Shared 모듈은 DIContainer 사용을 위해, SnapKit은 레이아웃 제약 조건 설정을 위해 필요합니다.


18-27: 레이아웃 상수 정의가 개선되었습니다

화면 크기에 따른 동적 spacing 처리와 그래픽 뷰의 고정 크기 설정이 적절합니다.


36-47: 프로퍼티 정의와 초기화가 적절합니다

UIImageView로의 변경, 레이아웃 업데이트 추적, 그리고 네비게이션 컨텍스트를 위한 isFromMypage 플래그 추가가 모두 적절합니다.


75-89: 레이아웃 업데이트 로직이 잘 구현되었습니다

viewDidLayoutSubviews에서 화면 크기에 따른 동적 spacing 조정이 적절하게 구현되었으며, 중복 업데이트 방지 로직도 포함되어 있습니다.


109-109: 그래픽 뷰 설정이 개선되었습니다

UIImageView에 맞게 이미지 설정이 적절하게 변경되었습니다.


127-127: 레이아웃 제약 조건이 개선되었습니다

mainLabel의 top constraint 참조 저장과 graphicView의 중앙 정렬 및 고정 크기 설정이 적절합니다.

Also applies to: 152-154


166-171: 적절한 바인딩 구현입니다

Combine을 사용한 reactive 프로그래밍과 메모리 관리가 적절하게 구현되었습니다.

Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (3)

16-66: EntryPoint 열거형이 잘 설계되었습니다

각 진입점에 대한 UI 구성을 computed property로 깔끔하게 관리하고 있습니다. 유지보수성과 확장성이 뛰어난 설계입니다.


68-84: 레이아웃 상수가 잘 정의되었습니다

모든 레이아웃 값이 명확하게 정의되어 있으며, 화면 크기에 따른 동적 spacing 처리를 위한 상수들이 적절히 포함되어 있습니다.


128-133: 화면 크기에 따른 동적 spacing 처리가 적절합니다

view.bounds.height를 사용하여 deprecated API를 피하고, SE 기기 대응을 위한 동적 spacing 조정이 잘 구현되었습니다.

Comment on lines +61 to 65
UIView.animate(withDuration: 0.5, delay: 2, options: .curveEaseInOut, animations: {
self.view.alpha = 0.0
}, completion: { [weak self] finished in
guard let self else { return }
let recommendedRoutineView = OnboardingRecommendedRoutineView(viewModel: self.viewModel)
self.navigationController?.pushViewController(recommendedRoutineView, animated: true)
}, completion: { [weak self] finshed in
self?.viewModel.action(input: .fetchOnboardingChoices)
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

사용하지 않는 파라미터를 수정하세요

SwiftLint가 지적한 대로 사용하지 않는 클로저 파라미터는 _로 대체해야 합니다.

-        }, completion: { [weak self] finshed in
+        }, completion: { [weak self] _ in
             self?.viewModel.action(input: .fetchOnboardingChoices)
📝 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
UIView.animate(withDuration: 0.5, delay: 2, options: .curveEaseInOut, animations: {
self.view.alpha = 0.0
}, completion: { [weak self] finished in
guard let self else { return }
let recommendedRoutineView = OnboardingRecommendedRoutineView(viewModel: self.viewModel)
self.navigationController?.pushViewController(recommendedRoutineView, animated: true)
}, completion: { [weak self] finshed in
self?.viewModel.action(input: .fetchOnboardingChoices)
})
UIView.animate(withDuration: 0.5, delay: 2, options: .curveEaseInOut, animations: {
self.view.alpha = 0.0
}, completion: { [weak self] _ in
self?.viewModel.action(input: .fetchOnboardingChoices)
})
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 63-63: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift
around lines 61 to 65, the closure parameter 'finshed' in the completion block
is unused. Replace the parameter name with an underscore (_) to indicate it is
intentionally unused and satisfy SwiftLint rules.

@choijungp choijungp merged commit 6934e6f into develop Jul 30, 2025
2 checks passed
@choijungp choijungp deleted the feat/emotionAPI branch July 30, 2025 14:18
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.

2 participants