Skip to content

[Feat] 활동 일지 화면 UI 구현#91

Open
choijungp wants to merge 7 commits into
developfrom
feat/activityHistoryView
Open

[Feat] 활동 일지 화면 UI 구현#91
choijungp wants to merge 7 commits into
developfrom
feat/activityHistoryView

Conversation

@choijungp

@choijungp choijungp commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🌁 Background

활동 일지 화면 구현에 들어갔어요 ~

📱 Screenshot

iPhone SE3 iPhone 13 mini iPhone 16 Pro
Simulator Screenshot - iPhone SE (3rd generation) - 2026-07-08 at 16 56 09 Simulator Screenshot - iPhone 13 mini - 2026-07-08 at 16 52 12 Simulator Screenshot - iPhone 16 Pro - 2026-07-08 at 16 48 57
이전 혹은 이후 달에 등록한 감정이 있을 시
Simulator Screenshot - iPhone 16 Pro - 2026-07-08 at 16 49 43

👩‍💻 Contents

  • 활동 일지 화면 ActivityHistoryViewController 구현

📝 Review Note

  • 서버 API 어떻게 될지 몰라서 일단 이미지 에셋 다 추가했어요 ! 추후 api 스펙 보고 수정될 수 있습니다 !!

Summary by CodeRabbit

  • New Features
    • 활동 일지 탭 추가로 월별 배지와 감정 기록을 한 화면에서 확인 가능
    • 감정 캘린더와 상세 보기 시트 추가로 날짜별 기록 탐색 강화
    • 배지 종류에 따라 단일/복수 상태와 전문가 문구가 함께 표시되도록 개선
  • Improvements
    • 배지 이미지 리소스 추가 및 월 표시 형식(yy년 M월) 단순화, 화면 구성이 더 일관되게 다듬어짐
  • Chores
    • iOS CI 실행 환경을 최신 macOS로 조정

@choijungp choijungp requested a review from taipaise July 8, 2026 08:03
@choijungp choijungp self-assigned this Jul 8, 2026
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c4fbf206-65d8-435a-9279-c4ed8daef90c

📥 Commits

Reviewing files that changed from the base of the PR and between 643c3ac and aa7802a.

📒 Files selected for processing (1)
  • .github/workflows/build_test.yml
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/build_test.yml

Walkthrough

배지 이미지 애셋과 그래픽 리소스가 추가되고, ActivityBadge 모델, ActivityHistoryViewModel, 배지 섹션/전문가 컴포넌트 뷰, 감정 캘린더 셀/뷰, ActivityHistoryViewController, EmotionDetailViewController가 신규 구현되었다. 탭바에 "활동 일지" 탭이 추가되고 DI 컨테이너에 뷰모델이 등록되었으며, Date 확장에 yearMonthShort 포맷이 추가되었다.

Changes

활동 일지 기능 추가

Layer / File(s) Summary
배지 이미지 애셋 및 그래픽 리소스
Projects/Presentation/Resources/Images.xcassets/Badge/*.imageset/Contents.json, Projects/Presentation/Resources/Images.xcassets/Badge/Contents.json, Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift
배지 이미지셋 Contents.json 파일들과 BitnagilGraphic에 배지 관련 static 이미지 프로퍼티 8개가 추가됨.
ActivityBadge 모델 및 뷰모델
Projects/Presentation/Sources/ActivityHistory/Model/ActivityBadge.swift, Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift, Projects/Shared/Sources/Extension/Date+.swift
배지 종류별 타이틀/전문가 문구/이미지를 반환하는 enum과 월간 배지·감정 기록을 발행하는 Combine 뷰모델, yearMonthShort 날짜 포맷 케이스가 추가됨.
배지 섹션 및 전문가 컴포넌트 뷰
Projects/Presentation/Sources/ActivityHistory/View/Component/ActivityBadgeExpertComponentView.swift, Projects/Presentation/Sources/ActivityHistory/View/Component/ActivityBadgeSectionView.swift
배지 개수에 따라 타이틀·전문가 라벨·이미지 스택을 동적으로 구성하는 UI 컴포넌트가 추가됨.
감정 캘린더 셀 및 뷰
Projects/Presentation/Sources/ActivityHistory/View/Component/EmotionCalendarCell.swift, Projects/Presentation/Sources/ActivityHistory/View/Component/EmotionCalendarView.swift
FSCalendar 기반 원형 표시 셀과 월 이동/날짜 선택 이벤트를 발행하는 캘린더 뷰가 추가됨.
화면 조립 및 상세 화면
Projects/Presentation/Sources/ActivityHistory/View/ActivityHistoryViewController.swift, Projects/Presentation/Sources/ActivityHistory/View/EmotionDetailViewController.swift
뷰모델 출력을 배지 섹션·캘린더에 바인딩하고, 날짜 선택 시 감정 상세를 시트로 표시하는 화면이 추가됨.
DI 등록 및 탭바 연결
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift, Projects/Presentation/Sources/Common/View/TabBarView.swift, .github/workflows/build_test.yml
ActivityHistoryViewModel이 DI에 등록되고, 탭바에 "활동 일지" 탭이 추가되며, CI 러너가 macos-15로 고정됨.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ActivityHistoryViewController
  participant ActivityHistoryViewModel
  participant EmotionCalendarView
  participant EmotionDetailViewController

  ActivityHistoryViewController->>ActivityHistoryViewModel: fetchMonthlyBadge / fetchMonthlyEmotionHistory
  ActivityHistoryViewModel-->>ActivityHistoryViewController: 배지 목록 퍼블리시
  ActivityHistoryViewController->>ActivityHistoryViewController: badgeSectionView.configureBadge
  ActivityHistoryViewModel-->>ActivityHistoryViewController: 감정 기록 딕셔너리 퍼블리시
  ActivityHistoryViewController->>EmotionCalendarView: update(records)
  EmotionCalendarView-->>ActivityHistoryViewController: dateSelected(date, emotion)
  ActivityHistoryViewController->>EmotionDetailViewController: 생성 후 시트 프리젠트
Loading

Poem

배지를 모으고 달력을 넘기며 🐰
감정 구슬 하나씩 콕콕 심었죠
새 탭이 열리니 깡총깡총 신나서
Contents.json도 한 줄씩 채웠어요
오늘의 기록, 내일도 잘 부탁해! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 활동 일지 화면의 UI 구현과 관련된 뷰, 캘린더, 배지, 탭바 추가를 잘 요약한 제목입니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/activityHistoryView

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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Projects/Presentation/Sources/ActivityHistory/View/ActivityHistoryViewController.swift`:
- Around line 174-192: `ActivityHistoryViewController.bind()` currently
subscribes to selected dates and monthly data publishers, but it misses
`EmotionCalendarView.pageChanged`, so month navigation never triggers a refresh.
Add a Combine subscription to `emotionCalendarView.pageChanged` in `bind()` and
forward the new page/month to the ViewModel’s fetch/update path used in `init`
for the current `.now` month, so `monthlyBadgePublisher` and
`monthlyEmotionHistoryPublisher` reload when the user changes months.

In
`@Projects/Presentation/Sources/ActivityHistory/View/Component/ActivityBadgeExpertComponentView.swift`:
- Around line 68-74: In
ActivityBadgeExpertComponentView.configure(expertTitle:), the icon state is only
updated for "예비 전문가" and is never restored for other titles, so subsequent calls
can leave iconView stuck in template/white mode. Update configure(expertTitle:)
to always set iconView back to its default image rendering and tint before
applying the special-case styling, using the existing iconView, expertLabel, and
BitnagilIcon.shineIcon references to locate the change.

In
`@Projects/Presentation/Sources/ActivityHistory/View/EmotionDetailViewController.swift`:
- Around line 26-30: Remove the leftover debug print from
EmotionDetailViewController.viewDidLoad and finish wiring up the intended UI. In
viewDidLoad, delete the print of emotion.koreanDescription, then either add the
label property to the view hierarchy and configure it with the emotion/date data
or remove the unused label and date properties if this screen is not ready yet.
Keep the fix centered on EmotionDetailViewController and its viewDidLoad setup.

In
`@Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift`:
- Around line 32-52: The `ActivityHistoryViewModel.action(input:)`
implementation is sending `monthlyBadgeSubject` and
`monthlyEmotionHistorySubject` from a `Task {}` that may run off the main
thread, which can push UI work into background execution through
`ActivityHistoryViewController.bind()`. Remove the unnecessary `Task` wrappers
in the `.fetchMonthlyBadge` and `.fetchMonthlyEmotionHistory` cases and send the
mock values directly on the main thread, or alternatively make the subscription
in `bind()` use `.receive(on: DispatchQueue.main)` before the `sink` that calls
`configureBadge` and `emotionCalendarView.update`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fc7fbc6c-6648-4945-bbc4-586930f54e63

📥 Commits

Reviewing files that changed from the base of the PR and between bfa3fee and 643c3ac.

⛔ Files ignored due to path filters (24)
  • Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/badge_background_graphic.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/badge_background_graphic@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/badge_background_graphic@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge@3x.png is excluded by !**/*.png
📒 Files selected for processing (21)
  • Projects/Presentation/Resources/Images.xcassets/Badge/Contents.json
  • Projects/Presentation/Resources/Images.xcassets/Badge/badge_background_graphic.imageset/Contents.json
  • Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/Contents.json
  • Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/Contents.json
  • Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/Contents.json
  • Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/Contents.json
  • Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/Contents.json
  • Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/Contents.json
  • Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/Contents.json
  • Projects/Presentation/Sources/ActivityHistory/Model/ActivityBadge.swift
  • Projects/Presentation/Sources/ActivityHistory/View/ActivityHistoryViewController.swift
  • Projects/Presentation/Sources/ActivityHistory/View/Component/ActivityBadgeExpertComponentView.swift
  • Projects/Presentation/Sources/ActivityHistory/View/Component/ActivityBadgeSectionView.swift
  • Projects/Presentation/Sources/ActivityHistory/View/Component/EmotionCalendarCell.swift
  • Projects/Presentation/Sources/ActivityHistory/View/Component/EmotionCalendarView.swift
  • Projects/Presentation/Sources/ActivityHistory/View/EmotionDetailViewController.swift
  • Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift
  • Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift
  • Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
  • Projects/Presentation/Sources/Common/View/TabBarView.swift
  • Projects/Shared/Sources/Extension/Date+.swift

Comment on lines +174 to +192
override func bind() {
emotionCalendarView.dateSelected.sink { [weak self] date, emotion in
guard let emotion else { return }
self?.presentEmotionDetail(date: date, emotion: emotion)
}
.store(in: &cancellables)

viewModel.output.monthlyBadgePublisher.sink { [weak self] badges in
guard let self else { return }
self.badgeSectionView.configureBadge(badges: badges)
}
.store(in: &cancellables)

viewModel.output.monthlyEmotionHistoryPublisher.sink { [weak self] emotionRecords in
guard let self else { return }
self.emotionCalendarView.update(records: emotionRecords)
}
.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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

pageChanged 퍼블리셔가 구독되지 않아 월 이동 시 데이터가 갱신되지 않음

EmotionCalendarView에서 pageChanged 퍼블리셔가 선언되고 calendarCurrentPageDidChange에서 send되지만, bind()에서 구독하지 않습니다. init에서 .now 기준으로만 데이터를 fetch하므로, 사용자가 이전/다음 달 버튼이나 인접 월 날짜를 탭하여 월을 이동해도 ViewModel에 새 데이터 요청이 전달되지 않습니다. 결과적으로 이동한 월의 배지 및 감정 기록이 stale하거나 비어 있게 됩니다.

🐛 제안 수정안
 override func bind() {
     emotionCalendarView.dateSelected.sink { [weak self] date, emotion in
         guard let emotion else { return }
         self?.presentEmotionDetail(date: date, emotion: emotion)
     }
     .store(in: &cancellables)

+    emotionCalendarView.pageChanged.sink { [weak self] date in
+        self?.viewModel.action(input: .fetchMonthlyBadge(date: date))
+        self?.viewModel.action(input: .fetchMonthlyEmotionHistory(date: date))
+    }
+    .store(in: &cancellables)
+
     viewModel.output.monthlyBadgePublisher.sink { [weak self] badges in
📝 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
override func bind() {
emotionCalendarView.dateSelected.sink { [weak self] date, emotion in
guard let emotion else { return }
self?.presentEmotionDetail(date: date, emotion: emotion)
}
.store(in: &cancellables)
viewModel.output.monthlyBadgePublisher.sink { [weak self] badges in
guard let self else { return }
self.badgeSectionView.configureBadge(badges: badges)
}
.store(in: &cancellables)
viewModel.output.monthlyEmotionHistoryPublisher.sink { [weak self] emotionRecords in
guard let self else { return }
self.emotionCalendarView.update(records: emotionRecords)
}
.store(in: &cancellables)
}
override func bind() {
emotionCalendarView.dateSelected.sink { [weak self] date, emotion in
guard let emotion else { return }
self?.presentEmotionDetail(date: date, emotion: emotion)
}
.store(in: &cancellables)
emotionCalendarView.pageChanged.sink { [weak self] date in
self?.viewModel.action(input: .fetchMonthlyBadge(date: date))
self?.viewModel.action(input: .fetchMonthlyEmotionHistory(date: date))
}
.store(in: &cancellables)
viewModel.output.monthlyBadgePublisher.sink { [weak self] badges in
guard let self else { return }
self.badgeSectionView.configureBadge(badges: badges)
}
.store(in: &cancellables)
viewModel.output.monthlyEmotionHistoryPublisher.sink { [weak self] emotionRecords in
guard let self else { return }
self.emotionCalendarView.update(records: emotionRecords)
}
.store(in: &cancellables)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Projects/Presentation/Sources/ActivityHistory/View/ActivityHistoryViewController.swift`
around lines 174 - 192, `ActivityHistoryViewController.bind()` currently
subscribes to selected dates and monthly data publishers, but it misses
`EmotionCalendarView.pageChanged`, so month navigation never triggers a refresh.
Add a Combine subscription to `emotionCalendarView.pageChanged` in `bind()` and
forward the new page/month to the ViewModel’s fetch/update path used in `init`
for the current `.now` month, so `monthlyBadgePublisher` and
`monthlyEmotionHistoryPublisher` reload when the user changes months.

Comment on lines +68 to +74
func configure(expertTitle: String) {
expertLabel.text = expertTitle
if expertTitle == "예비 전문가" {
iconView.image = BitnagilIcon.shineIcon?.withRenderingMode(.alwaysTemplate)
iconView.tintColor = .white
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

configure 호출 시 아이콘 상태가 리셋되지 않는 버그

configure(expertTitle:)에서 "예비 전문가"일 때 iconView를 template rendering + white tint로 변경하지만, 이후 다른 title로 호출될 때 아이콘을 원래 상태로 되돌리지 않습니다. monthlyBadgePublisher가 월별로 다른 배지 개수를 방출하면, "예비 전문가" → "능숙한 전문가" 순서로 호출될 때 아이콘이 여전히 template/white 상태로 남아 의도하지 않은 UI가 표시됩니다.

🐛 제안 수정안
 func configure(expertTitle: String) {
     expertLabel.text = expertTitle
     if expertTitle == "예비 전문가" {
         iconView.image = BitnagilIcon.shineIcon?.withRenderingMode(.alwaysTemplate)
         iconView.tintColor = .white
+    } else {
+        iconView.image = BitnagilIcon.shineIcon
+        iconView.tintColor = 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
func configure(expertTitle: String) {
expertLabel.text = expertTitle
if expertTitle == "예비 전문가" {
iconView.image = BitnagilIcon.shineIcon?.withRenderingMode(.alwaysTemplate)
iconView.tintColor = .white
}
}
func configure(expertTitle: String) {
expertLabel.text = expertTitle
if expertTitle == "예비 전문가" {
iconView.image = BitnagilIcon.shineIcon?.withRenderingMode(.alwaysTemplate)
iconView.tintColor = .white
} else {
iconView.image = BitnagilIcon.shineIcon
iconView.tintColor = nil
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Projects/Presentation/Sources/ActivityHistory/View/Component/ActivityBadgeExpertComponentView.swift`
around lines 68 - 74, In
ActivityBadgeExpertComponentView.configure(expertTitle:), the icon state is only
updated for "예비 전문가" and is never restored for other titles, so subsequent calls
can leave iconView stuck in template/white mode. Update configure(expertTitle:)
to always set iconView back to its default image rendering and tint before
applying the special-case styling, using the existing iconView, expertLabel, and
BitnagilIcon.shineIcon references to locate the change.

Comment on lines +26 to +30
override func viewDidLoad() {
super.viewDidLoad()
print("\(emotion.koreanDescription)")
view.backgroundColor = .white
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

print 디버그 문과 미구현 UI

viewDidLoadprint("\(emotion.koreanDescription)")가 남아있고, label 프로퍼티가 생성되었지만 view hierarchy에 추가되지 않았습니다. date 프로퍼티도 사용되지 않습니다. API 스펙 미확정으로 임시 구현일 수 있으나, print 문은 프로덕션 코드에서 제거해야 합니다.

🐛 제안 수정안
 override func viewDidLoad() {
     super.viewDidLoad()
-    print("\(emotion.koreanDescription)")
     view.backgroundColor = .white
 }
📝 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
override func viewDidLoad() {
super.viewDidLoad()
print("\(emotion.koreanDescription)")
view.backgroundColor = .white
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Projects/Presentation/Sources/ActivityHistory/View/EmotionDetailViewController.swift`
around lines 26 - 30, Remove the leftover debug print from
EmotionDetailViewController.viewDidLoad and finish wiring up the intended UI. In
viewDidLoad, delete the print of emotion.koreanDescription, then either add the
label property to the view hierarchy and configure it with the emotion/date data
or remove the unused label and date properties if this screen is not ready yet.
Keep the fix centered on EmotionDetailViewController and its viewDidLoad setup.

Comment on lines +32 to +52
func action(input: Input) {
switch input {
case .fetchMonthlyBadge(let date):
// TODO: 추후 서버 로직 붙이기
Task {
let badges: [ActivityBadge] = []
monthlyBadgeSubject.send(badges)
}
case .fetchMonthlyEmotionHistory(let date):
// TODO: 추후 서버 로직 붙이기
Task {
let mockEmotionRecords: [String: Marble] = [
"2026-07-01": .ANXIETY,
"2026-07-05": .FATIGUE,
"2026-07-11": .LETHARGY,
"2026-07-16": .VITALITY,
"2026-07-23": .CALM
]
monthlyEmotionHistorySubject.send(mockEmotionRecords)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

백그라운드 스레드에서 UI 업데이트가 발생할 수 있습니다.

ActivityHistoryViewModel@MainActor가 아니므로, Task {} 블록은 cooperative thread pool(백그라운드)에서 실행됩니다. CurrentValueSubject.send는 호출 스레드에서 값을 전달하므로, ActivityHistoryViewController.bind()sink 클로저가 백그라운드 스레드에서 configureBadgeemotionCalendarView.update 같은 UI 작업을 수행하게 됩니다. 이는 UIKit 스레딩 규칙 위반입니다.

현재 Task 내부에 await가 없으므로 동기적으로 실행되는 코드입니다. Task 래퍼를 제거하고 메인 스레드에서 직접 send를 호출하거나, 구독부에 .receive(on: DispatchQueue.main)를 추가하세요.

🔧 제안: Task 제거 및 메인 스레드 전송
 func action(input: Input) {
     switch input {
     case .fetchMonthlyBadge(let date):
         // TODO: 추후 서버 로직 붙이기
-        Task {
-            let badges: [ActivityBadge] = []
-            monthlyBadgeSubject.send(badges)
-        }
+        let badges: [ActivityBadge] = []
+        monthlyBadgeSubject.send(badges)
     case .fetchMonthlyEmotionHistory(let date):
         // TODO: 추후 서버 로직 붙이기
-        Task {
-            let mockEmotionRecords: [String: Marble] = [
-                "2026-07-01": .ANXIETY,
-                "2026-07-05": .FATIGUE,
-                "2026-07-11": .LETHARGY,
-                "2026-07-16": .VITALITY,
-                "2026-07-23": .CALM
-            ]
-            monthlyEmotionHistorySubject.send(mockEmotionRecords)
-        }
+        let mockEmotionRecords: [String: Marble] = [
+            "2026-07-01": .ANXIETY,
+            "2026-07-05": .FATIGUE,
+            "2026-07-11": .LETHARGY,
+            "2026-07-16": .VITALITY,
+            "2026-07-23": .CALM
+        ]
+        monthlyEmotionHistorySubject.send(mockEmotionRecords)
     }
 }
📝 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
func action(input: Input) {
switch input {
case .fetchMonthlyBadge(let date):
// TODO: 추후 서버 로직 붙이기
Task {
let badges: [ActivityBadge] = []
monthlyBadgeSubject.send(badges)
}
case .fetchMonthlyEmotionHistory(let date):
// TODO: 추후 서버 로직 붙이기
Task {
let mockEmotionRecords: [String: Marble] = [
"2026-07-01": .ANXIETY,
"2026-07-05": .FATIGUE,
"2026-07-11": .LETHARGY,
"2026-07-16": .VITALITY,
"2026-07-23": .CALM
]
monthlyEmotionHistorySubject.send(mockEmotionRecords)
}
}
func action(input: Input) {
switch input {
case .fetchMonthlyBadge(let date):
// TODO: 추후 서버 로직 붙이기
let badges: [ActivityBadge] = []
monthlyBadgeSubject.send(badges)
case .fetchMonthlyEmotionHistory(let date):
// TODO: 추후 서버 로직 붙이기
let mockEmotionRecords: [String: Marble] = [
"2026-07-01": .ANXIETY,
"2026-07-05": .FATIGUE,
"2026-07-11": .LETHARGY,
"2026-07-16": .VITALITY,
"2026-07-23": .CALM
]
monthlyEmotionHistorySubject.send(mockEmotionRecords)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift`
around lines 32 - 52, The `ActivityHistoryViewModel.action(input:)`
implementation is sending `monthlyBadgeSubject` and
`monthlyEmotionHistorySubject` from a `Task {}` that may run off the main
thread, which can push UI work into background execution through
`ActivityHistoryViewController.bind()`. Remove the unnecessary `Task` wrappers
in the `.fetchMonthlyBadge` and `.fetchMonthlyEmotionHistory` cases and send the
mock values directly on the main thread, or alternatively make the subscription
in `bind()` use `.receive(on: DispatchQueue.main)` before the `sink` that calls
`configureBadge` and `emotionCalendarView.update`.

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