Skip to content

[Feat-T3-173] 추천 루틴 화면 서버 v2 연동#52

Merged
choijungp merged 3 commits intodevelopfrom
feat/recommended-routine-api
Aug 18, 2025
Merged

[Feat-T3-173] 추천 루틴 화면 서버 v2 연동#52
choijungp merged 3 commits intodevelopfrom
feat/recommended-routine-api

Conversation

@choijungp
Copy link
Copy Markdown
Contributor

@choijungp choijungp commented Aug 18, 2025

🌁 Background

이전 추천 루틴 화면 디자인 v2 PR에 이어서 서버 연동을 했습니다 ~

이렇게 가벼울 줄 알았다면 ... 저번 PR에 합쳐서 하는것이었는데요 .. ㅠㅠ
너무너무너무 가벼운 PR이니 매우매우매우 가볍게 봐주세요 !! 미리감사 ~~

📱 Screenshot

Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-08-18.at.15.16.39.mp4

👩‍💻 Contents

  • 기존 RecommendedRoutineDTO, Entity에 type 값 추가
  • RoutineCardView와 RecommendedRoutine 연결

📝 Review Note

RoutineCategory vs. RoutineType

RecommendedRoutineEntity를 보면 category랑 type 값이 있는데요, 둘이 상당부분 많이 비슷합니다 ㅠㅠ !!!
타입도 걍 똑같죠 ???

public struct RecommendedRoutineEntity {
    public let id: Int
    public let title: String
    public let description: String
    public let category: RoutineCategoryType?
    public let type: RoutineCategoryType
    public let level: RoutineLevelType?
    public let subRoutines: [RecommendedSubRoutineEntity]
}

category와 type이랑 뭐가 다른 것이냐 하면 ..
category는 추천 루틴 상단에 카테고리를 뜻합니다 !! 그래서 맞춤 추천 값이 들어올 수 있어요 !!!

하지만 type은 해당 추천 루틴의 타입 값을 의미합니다.
그래서 맞춤 추천일 때에 어떤 루틴 타입인지 보여주기 위해 2개의 값이 모두 필요해요 ㅠㅠ

이거슨 어쩔 수 없겟죠 ?? ...

📣 Related Issue

  • close #T3-173

Summary by CodeRabbit

  • 신규 기능
    • 추천 루틴 카드에서 + 버튼 탭 시 루틴 생성 화면으로 이동.
    • 감정 등록 버튼 탭 시 감정 등록 화면으로 이동.
  • 개선 사항
    • 추천 루틴 카드가 루틴의 제목, 카테고리 아이콘, 하위 루틴 목록을 실제 데이터로 표시.
    • 필터(카테고리/레벨) 변경 시 목록이 상단으로 자동 스크롤되어 가독성 향상.
  • UI 변경
    • 추천 루틴 카드 컴포넌트를 새 디자인으로 교체하고 자동 높이 적용.
    • 스크롤 영역 하단 여백 및 스택 간격 조정으로 화면 배치 최적화.

@choijungp choijungp requested a review from taipaise August 18, 2025 06:27
@choijungp choijungp self-assigned this Aug 18, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 18, 2025

Walkthrough

RecommendedRoutine 데이터 흐름에 routineType 추가(DTO→Entity→Presentation Model). Domain 엔터티에 type 필드 도입 및 이니셜라이저 갱신. Presentation에서 새로운 RoutineCardView 도입 및 델리게이트 패턴 적용, 기존 RecommendedRoutineCardView 제거. ViewController는 새 카드/델리게이트로 교체하고 레이아웃·스크롤 동작 일부 조정.

Changes

Cohort / File(s) Summary
DataSource DTO 업데이트
Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift
DTO에 routineType: String 추가, CodingKeys에 "recommendedRoutineType" 매핑. Entity 변환 시 RoutineCategoryType(rawValue:) ?? .rest로 도출해 전달.
Domain Entity 확장
Projects/Domain/Sources/Entity/RecommendedRoutineEntity.swift
type: RoutineCategoryType 공용 프로퍼티 추가. 이니셜라이저에 type 파라미터 추가(비옵셔널) 및 할당.
Presentation 모델 확장
Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift
subRoutines: [String], routineType: RoutineCategoryType 추가. 이니셜라이저 갱신. Entity→Model 매핑에서 subRoutines.map { $0.title }, routineType: type로 설정.
새 카드 컴포넌트 도입
Projects/Presentation/Sources/Common/Component/RoutineCardView.swift
RoutineCardViewDelegate 추가. RoutineCardViewRecommendedRoutine을 주입받아 타이틀·카테고리·서브루틴 바인딩. 플러스 버튼 탭을 델리게이트로 전달. 기존 무인자 init 제거.
기존 카드 컴포넌트 제거
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RecommendedRoutineCardView.swift
RecommendedRoutineCardView 및 관련 델리게이트 전면 삭제.
뷰컨트롤러 통합 및 레이아웃 조정
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineViewController.swift
카드 타입을 RoutineCardView로 교체, 딕셔너리 제네릭 수정. 새 델리게이트 구현으로 플러스 버튼 탭 시 RoutineCreation 화면으로 네비게이션. Emotion 등록 버튼 델리게이트 추가. 스크롤뷰/스택뷰 하단 제약 및 스크롤 리셋 로직 조정. 테스트 UI 코드 제거.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Card as RoutineCardView
  participant VC as RecommendedRoutineViewController
  participant Nav as UINavigationController
  participant RC as RoutineCreationViewController

  User->>Card: Tap Plus
  Card-->>VC: routineCardView(_:didTapPlusButton: routine)
  VC->>Nav: push RC(routine.id)
  Nav->>RC: show
Loading
sequenceDiagram
  participant API as API/JSON
  participant DTO as RecommendedRoutineDTO
  participant Entity as RecommendedRoutineEntity
  participant Model as RecommendedRoutine

  API-->>DTO: recommendedRoutineType (String)
  DTO->>Entity: type = RoutineCategoryType(rawValue) ?? .rest
  Entity-->>Model: routineType = type<br/>subRoutines = map(.title)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

밤하늘 카드에 토끼가 점프! ✨
플러스 눌러 휙—새 화면으로 쿵!
타입이 붙고 흐름이 정돈되어
DTO에서 모델까지 길이 반짝—
오늘도 코드밭에 당근 한 줌, hoppity-hop! 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ 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/recommended-routine-api

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.
    • 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.
  • 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 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/Issue comments)

Type @coderabbitai help to get the list of available commands.

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.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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: 1

🔭 Outside diff range comments (1)
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineViewController.swift (1)

81-83: 메모리 릭 가능성: 버튼 액션 클로저에서 self 강한 캡처

levelButtonUIAction을 강하게 보유하므로, 현재 구현은 VC ↔︎ 버튼 간 강한 참조 사이클을 만들 수 있습니다. 다른 곳에서처럼 [weak self]를 사용하세요.

-        levelButton.addAction(UIAction { _ in
-            self.showBottomSheet()
-        }, for: .touchUpInside)
+        levelButton.addAction(UIAction { [weak self] _ in
+            self?.showBottomSheet()
+        }, for: .touchUpInside)
🧹 Nitpick comments (4)
Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift (1)

15-15: 서버 필드 누락 대비: routineType을 Optional로 디코딩 고려

v2에서 항상 내려온다면 현재도 문제는 없지만, 회귀/실험 플래그 등으로 필드가 누락될 경우 디코딩 자체가 실패합니다. 디코딩을 옵셔널로 완화하고 폴백을 유지하면 회복력이 좋아집니다.

백엔드에서 recommendedRoutineType 필드가 항상 보장되는지 확인 부탁드립니다. 누락 가능성이 있다면 아래와 같이 변경을 제안합니다.

-    let routineType: String
+    let routineType: String?
@@
-            type: RoutineCategoryType(rawValue: routineType) ?? .rest,
+            type: RoutineCategoryType(rawValue: routineType ?? "") ?? .rest,

Also applies to: 23-23, 44-45

Projects/Presentation/Sources/Common/Component/RoutineCardView.swift (1)

38-39: 미사용 프로퍼티 정리 권장 (edit/delete 버튼)

editButton, deleteButton이 선언만 있고 사용되지 않습니다. 향후 사용 계획이 없다면 제거하여 뷰의 책임을 명확히 하고 노이즈를 줄이는 것을 권장합니다.

-    private let editButton = UIButton()
-    private let deleteButton = UIButton()
Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift (1)

19-35: 불필요한 멤버와이즈 이니셜라이저 제거 제안

Swift는 동일 시그니처의 멤버와이즈 이니셜라이저를 자동 합성합니다. 커스텀 로직이 없다면 제거하여 보일러플레이트를 줄이는 것이 좋습니다. SwiftLint 경고(unneeded_synthesized_initializer)와도 일치합니다.

-    init(
-        id: Int,
-        mainTitle: String,
-        subTitle: String?,
-        subRoutines: [String],
-        routineCategory: RoutineCategoryType,
-        routineType: RoutineCategoryType,
-        routineLevel: RoutineLevelType
-    ) {
-        self.id = id
-        self.mainTitle = mainTitle
-        self.subTitle = subTitle
-        self.subRoutines = subRoutines
-        self.routineCategory = routineCategory
-        self.routineType = routineType
-        self.routineLevel = routineLevel
-    }
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineViewController.swift (1)

226-231: 정리 제안: 스택뷰 초기화 시 불필요한 예외 조건 제거

registerEmotionButton은 스택뷰의 arrangedSubviews에 포함되지 않습니다. 조건문을 제거하고 단순화할 수 있습니다.

-        recommendedRoutineStackView.arrangedSubviews.forEach { view in
-            if view != registerEmotionButton {
-                recommendedRoutineStackView.removeArrangedSubview(view)
-                view.removeFromSuperview()
-            }
-        }
+        recommendedRoutineStackView.arrangedSubviews.forEach { view in
+            recommendedRoutineStackView.removeArrangedSubview(view)
+            view.removeFromSuperview()
+        }
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ffdcac2 and abb2254.

📒 Files selected for processing (6)
  • Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift (2 hunks)
  • Projects/Domain/Sources/Entity/RecommendedRoutineEntity.swift (2 hunks)
  • Projects/Presentation/Sources/Common/Component/RoutineCardView.swift (5 hunks)
  • Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift (2 hunks)
  • Projects/Presentation/Sources/RecommendedRoutine/View/Component/RecommendedRoutineCardView.swift (0 hunks)
  • Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineViewController.swift (8 hunks)
💤 Files with no reviewable changes (1)
  • Projects/Presentation/Sources/RecommendedRoutine/View/Component/RecommendedRoutineCardView.swift
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-16T09:21:15.038Z
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/RecommendedRoutine/View/RecommendedRoutineViewController.swift
🧬 Code Graph Analysis (2)
Projects/Presentation/Sources/Common/Component/RoutineCardView.swift (2)
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineViewController.swift (1)
  • routineCardView (334-341)
Projects/Presentation/Sources/Common/Extension/UIImage+.swift (1)
  • resizeAspectFit (20-35)
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineViewController.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
  • resolve (18-25)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift

[Warning] 19-19: This memberwise initializer would be synthesized automatically - you do not need to define it

(unneeded_synthesized_initializer)

🔇 Additional comments (6)
Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift (1)

44-46: Enum 변환 실패 시 .rest 기본값 적용, 도메인 불변성 보장

서버 값이 미정의/미일치일 때 .rest로 폴백하는 처리가 깔끔합니다. Domaintype이 비-옵셔널인 제약과도 잘 맞습니다.

Projects/Domain/Sources/Entity/RecommendedRoutineEntity.swift (1)

13-15: Domain에 비-옵셔널 type 추가 방향 적절

type을 비-옵셔널로 올린 설계가 DTO→Entity→Presentation 전파와도 잘 맞고, 후단 UI 로직 단순화에도 도움됩니다.

Also applies to: 22-24, 30-32

Projects/Presentation/Sources/Common/Component/RoutineCardView.swift (1)

35-37: routineType 기반 카테고리 아이콘 연동 좋습니다

routine.routineType을 사용해 아이콘/배경색을 결정하는 구조가 데이터 흐름과 잘 맞습니다.

Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift (1)

14-17: 모델 확장(서브루틴/루틴 타입) 전파 OK

subRoutinesroutineType 추가 및 Entity→Model 매핑이 일관적입니다. 이후 UI 컴포넌트(카드 뷰)에서도 해당 데이터를 잘 활용하고 있습니다.

Also applies to: 24-26, 31-34, 44-47

Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineViewController.swift (2)

236-240: 새 카드 뷰 적용 및 델리게이트 연결 정상

RoutineCardView(routine:) 생성, 캐시 딕셔너리 저장, 델리게이트 연결, 스택뷰 추가까지 흐름이 간결하고 명확합니다.


200-203: 필터 변경 시 스크롤 상단 복귀 UX 반영 👍

카테고리/난이도 변경 시 상단으로 스크롤 이동하는 처리가 사용자 경험에 적합합니다.

Also applies to: 303-304

Comment on lines +74 to 79
plusButton.addAction(
UIAction { [weak self] _ in
guard let self else { return }
delegate?.routineCardView(self, didTapPlusButton: routine)
}, for: .touchUpInside)

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

컴파일 오류: 클로저 내부에서 self 명시 및 프로퍼티 접근자 누락

클로저 내부에서 self를 명시해야 하며, delegate/routine 접근 시에도 self.가 필요합니다. 현재 코드는 컴파일되지 않습니다.

아래와 같이 수정해주세요.

-        plusButton.addAction(
-            UIAction { [weak self] _ in
-                guard let self else { return }
-                delegate?.routineCardView(self, didTapPlusButton: routine)
-            }, for: .touchUpInside)
+        plusButton.addAction(
+            UIAction { [weak self] _ in
+                guard let self = self else { return }
+                self.delegate?.routineCardView(self, didTapPlusButton: self.routine)
+            },
+            for: .touchUpInside
+        )
📝 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
plusButton.addAction(
UIAction { [weak self] _ in
guard let self else { return }
delegate?.routineCardView(self, didTapPlusButton: routine)
}, for: .touchUpInside)
plusButton.addAction(
UIAction { [weak self] _ in
guard let self = self else { return }
self.delegate?.routineCardView(self, didTapPlusButton: self.routine)
},
for: .touchUpInside
)
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Common/Component/RoutineCardView.swift around
lines 74–79, the closure uses an implicit guard let self and accesses
delegate/routine without self, causing a compile error; change the guard to
"guard let self = self else { return }" and prefix property accesses with self
(e.g., self.delegate and self.routine) inside the closure so the code compiles
and captures self weakly correctly.

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.

행복한 PR이군요~ 이게 바로 '정상화' 아닐까요? 가능하면 항상 pr을 1000줄 이하로 올리고 싶은데 요즘 너무 작업에 치이기도 하고, UI 쪽 작업이 커서 그러기 쉽지 않았던것 같아요

말씀해주신 엔티티는 하나의 추천 루틴을 구분하기 위해서라면 어쩔 수 없지 않을까 합니다~!~!
오늘도 고생하셨습니다!


private let headerInfoStackView = UIStackView()
private let categoryIconView = RoutineCategoryIcon(routineCategory: .connection)
private lazy var categoryIconView = RoutineCategoryIcon(routineCategory: routine.routineType)
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.

새로 수정된 코드에서 이 categoryIconView 관련 코드를 찾아볼 수 없었는데요! 혹시 lazy var로 설정해주신 이유를 알 수 있을까요?!?

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.

RoutineCardView에서만 CategoryIconView를 사용할 것 같아서 같은 파일 내부에 fileprivate으로 구현해두었습니다 !!!

그리고 RoutineCardView의 생성자로 들어온 routine의 카테고리 값으로 초기화 해야 해가주구 lazy var로 설정했습니다 !!!

@choijungp choijungp merged commit 4db6969 into develop Aug 18, 2025
2 checks passed
@choijungp choijungp deleted the feat/recommended-routine-api branch August 18, 2025 12:17
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