Skip to content

Conversation

@seoyoon513
Copy link
Contributor

@seoyoon513 seoyoon513 commented Jan 8, 2026

🔗 관련 이슈

📙 작업 설명

단일 독서 기록 모델 통합

  • 현재 단일 독서 기록(ReadingRecord)에 대한 서버 응답 모델이 동일하여 이를 하나의 모델로 통합했습니다.
  • V2 버저닝 도입 시 기존 V1 적용 영역에 대한 영향을 최소화하기 위해 ReadingRecordV2 모델로 분리하여 적용했습니다.
ReadingRecordsResponse.kt

.
.
.
@Serializable
data class ReadingRecordV2(
    @SerialName("id")
    val id: String,
    @SerialName("userBookId")
    val userBookId: String,
    @SerialName("pageNumber")
    val pageNumber: Int,
    @SerialName("quote")
    val quote: String,
    @SerialName("review")
    val review: String?,
    @SerialName("primaryEmotion")
    val primaryEmotion: PrimaryEmotion,
    @SerialName("detailEmotions")
    val detailEmotions: List<DetailEmotion>,
    @SerialName("createdAt")
    val createdAt: String,
    @SerialName("updatedAt")
    val updatedAt: String,
    @SerialName("bookTitle")
    val bookTitle: String?,
    @SerialName("bookPublisher")
    val bookPublisher: String?,
    @SerialName("bookCoverImageUrl")
    val bookCoverImageUrl: String?,
    @SerialName("author")
    val author: String?,
)

대표 감정 및 세부 감정 서버 기반 데이터로 변경

  • 대표 감정 및 세부 감정 정보를 로컬 데이터가 아닌 GET /api/v2/emotions API 응답 데이터를 기준으로 표시하도록 변경했습니다.

EmotionCode Enum 모델 도입

  • 서버 데이터 emotionCode 값을 매핑하기 위해 EmotionCode enum 모델을 도입했습니다.
enum class EmotionCode {
    WARMTH, JOY, SADNESS, INSIGHT, OTHER;

    companion object {
        fun fromCode(code: String): EmotionCode? {
            return EmotionCode.entries.find { it.name == code }
        }
    }
}
  • :designsystem 모듈에서 EmotionCode를 기반으로 리소스를 매핑할 수 있도록 확장 함수를 추가했습니다.
val EmotionCode.graphicRes: Int
    get() = when (this) {
        EmotionCode.WARMTH -> R.drawable.img_warmth
        EmotionCode.JOY -> R.drawable.img_joy
        EmotionCode.SADNESS -> R.drawable.img_sadness
        EmotionCode.INSIGHT -> R.drawable.img_insight
        EmotionCode.OTHER -> R.drawable.img_other
    }

val EmotionCode.categoryGraphicRes: Int?
    get() = when (this) {
        EmotionCode.WARMTH -> R.drawable.img_category_warmth
        EmotionCode.JOY -> R.drawable.img_category_joy
        EmotionCode.SADNESS -> R.drawable.img_category_sadness
        EmotionCode.INSIGHT -> R.drawable.img_category_insight
        EmotionCode.OTHER -> null
    }

UI 변경사항

  • 기록 상세 화면, 기록 수정 화면, 감정 수정 화면 UI를 변경했습니다.
  • 기록 카드에 기타(OTHER) 이미지 리소스를 추가하고, EotionCode 기반으로 카드 리소스가 매핑되도록 수정했습니다.

📸 스크린샷 또는 시연 영상

기록 상세 기록 수정 감정 수정
Reed_기록 상세 Reed_기록 수정 Reed_감정 수정
기타 감정 카드
Reed_기타 감정 카드

기록 등록 및 수정 플로우 시연 영상

Reed_.mp4

💬 추가 설명 or 리뷰 포인트

  • 페이지 번호 미입력 UI 작업은 아래 서버 이슈 해결 이후 진행할 예정입니다.
  • 도서 상세 화면 작업 시,
    • Emotion 기반으로 적용된 UI를 EmotionCode 기반으로 변경해주세요.
    • 독서 기록 목록 조회(V2) 버저닝 작업 시 단일 독서 기록 모델을 ReadingRecord 에서 ReadingRecordV2로 변경해주세요. (TODO 주석 남겼습니다!)
    • BookDetailPresenter에서 고정값 임시 조치 부분도 확인 후 수정 부탁드립니다. (TODO 주석 남겼습니다!)
  • 기존 V1 UI는 모두 제거했으며 V2 UI는 V2 surffix를 유지한 상태입니다. 히스토리를 위해 해당 네이밍을 유지하는 것이 나을지, surffix를 제거하는 것이 나을지에 대한 의견 부탁드려요~~

Summary by CodeRabbit

  • New Features

    • 기본 감정(Primary) + 세부 감정(Detail) 기반 감정 그룹 및 선택 도입
    • 감정 편집용 하단 시트와 EmotionItem/EmotionDetail 컴포넌트 추가
    • 읽기 기록 모델 V2 및 감정 조회 API(v2) 도입
  • Bug Fixes & Improvements

    • 등록/수정 흐름에서 감정 데이터 구조 통일, 페이지 번호 nullable 처리
    • 불필요한 인상 단계 제거로 등록 UI 간소화
    • 감정 관련 문자열 리소스와 미리보기/표시 일관성 개선

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

Walkthrough

독서 기록 API를 v2로 마이그레이션하고 감정 모델을 그룹/세부 구조로 재설계했습니다. 네트워크·모델·매퍼·레포지토리·DI·디자인시스템·UI(등록/수정/상세/편집)와 네비게이션 인자들이 이에 맞춰 일괄 변경되었습니다.

Changes

응집 단위 / 파일(들) 변경 요약
모델 추가/변경
core/model/src/main/kotlin/.../EmotionModel.kt, core/model/src/main/kotlin/.../ReadingRecordsModel.kt
EmotionGroupsModel, EmotionGroupModel, DetailEmotionModel, EmotionCode enum, ReadingRecordModelV2, PrimaryEmotionModel 등 신규/버전화된 데이터 모델 추가
네트워크: 응답·요청·서비스
core/network/.../response/EmotionGroupsResponse.kt, core/network/.../response/ReadingRecordsResponse.kt, core/network/.../request/RecordRegisterRequest.kt, core/network/.../service/ReedService.kt
감정 그룹 응답 모델 추가, reading-records v1→v2 엔드포인트 전환, emotionTagsprimaryEmotion + detailEmotionTagIds 요청 변경, getEmotions() API 추가
데이터 레이어: API/구현/매퍼/DI
core/data/api/.../EmotionRepository.kt, core/data/api/.../RecordRepository.kt, core/data/impl/.../repository/DefaultEmotionRepository.kt, core/data/impl/.../repository/DefaultRecordRepository.kt, core/data/impl/.../mapper/ResponseToModel.kt, core/data/impl/.../di/DataGraph.kt
EmotionRepository 인터페이스/구현 추가, 매퍼에 EmotionGroups/ReadingRecordV2 매핑 추가, RecordRepository 시그니처 및 DefaultRecordRepository 페이로드/반환형 변경, DI 바인딩 추가
디자인시스템·리소스
core/designsystem/src/main/kotlin/.../Emotion.kt, core/designsystem/src/main/kotlin/.../RecordStep.kt, core/designsystem/src/main/res/values/strings.xml
감정 속성 API를 EmotionEmotionCode로 이동(그래픽/카테고리/설명), OTHER 케이스 추가, RecordStep.IMPRESSION 제거, 문자열 리소스 추가
감정 편집 화면·컴포넌트
feature/edit/.../EmotionEditPresenter.kt, EmotionEditUi*.kt, component/EmotionDetailBottomSheet.kt, component/EmotionItem.kt, res/values/strings.xml
EmotionRepository 주입으로 감정그룹 로드, UI 상태(EmotionUiState) 추가, 바텀시트 기반 세부 감정 편집 컴포저블 및 EmotionItem 도입, 이벤트 재구성
레코드 등록/수정/상세 흐름 → V2 적용
feature/record/.../register/*, feature/record/.../step_v2/*, feature/detail/.../record/*, feature/detail/.../card/*, feature/edit/.../record/*, feature/edit/.../record/component/*
레코드 CRUD 시그니처에서 pageNumber nullable, primaryEmotion·detailEmotionTagIds 사용으로 전환; UI/프리뷰/네비게이션 인자(PrimaryEmotionArg/DetailEmotionArg) 일괄 업데이트
화면 네비게이션 인자 및 결과
feature/screens/src/main/kotlin/.../Screens.kt, feature/screens/.../arguments/RecordEditArgs.kt
EmotionEditScreen/RecordCardScreen 인자·결과를 string → 구조체(PrimaryEmotionArg, DetailEmotionArg, EmotionCode)로 변경, RecordEditArgs 업데이트
삭제된 컴포넌트
feature/record/.../component/CustomTooltipBox.kt, ImpressionGuideBottomSheet.kt, ImpressionGuideBox.kt, step/QuoteStep.kt, step/EmotionStep.kt, step/ImpressionStep.kt
IMPRESSION 관련 UI 및 툴팁/가이드 컴포넌트 제거(기존 3단계 → 2단계 변경 반영)
기타·소규모 변경
여러 feature 파일(예: RecordCard, RecordEdit, RecordProgressBar, QuoteItem 등)
호출부·프리뷰·시그니처 일괄 업데이트(감정 타입/데이터 구조 반영), RecordProgressBar 단계 수 3→2 등 소규모 수정

Sequence Diagram(s)

sequenceDiagram
    participant UI as UI (EmotionEdit / RecordRegister)
    participant Presenter as Presenter
    participant Repo as EmotionRepository
    participant Service as ReedService
    participant Server as Backend

    UI->>Presenter: 화면 진입 (LaunchedEffect)
    Presenter->>Repo: getEmotions()
    Repo->>Service: GET /api/v2/emotions
    Service->>Server: 요청
    Server-->>Service: EmotionGroupsResponse
    Service-->>Repo: EmotionGroupsResponse
    Repo->>Presenter: Result<EmotionGroupsModel>
    Presenter-->>UI: EmotionEditUiState(emotionGroups)
    
    Note over UI,Presenter: 사용자 선택/바텀시트 흐름
    UI->>Presenter: OnSelectEmotionCode(code)
    Presenter-->>UI: show detail bottom sheet
    UI->>Presenter: OnEmotionDetailToggled(detailId)
    Presenter->>Presenter: update selectedEmotionMap
    UI->>Presenter: OnEmotionDetailCommitted
    Presenter-->>Repo: postRecord / editRecord(primaryEmotion, detailEmotionTagIds, pageNumber?)
    Repo->>Service: POST/PUT api/v2/reading-records...
    Service->>Server: 요청
    Server-->>Service: ReadingRecordV2
    Service-->>Repo: ReadingRecordV2
    Repo-->>Presenter: Result<ReadingRecordModelV2>
    Presenter-->>UI: 저장 완료 / 내비게이션
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🐰 감정의 정원을 다시 가꿨네요,
카테고리와 세부로 나뉜 작은 꽃들,
API는 새 길을 따라 흐르고,
UI는 가볍게 두 단계로 춤추며,
기록은 더 섬세히 숨을 쉬겠어요. 🌿✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 주요 변경사항인 'API v2 버저닝 적용'과 '독서 기록/수정/공유 UI 변경'을 명확하게 요약하고 있습니다.
Linked Issues check ✅ Passed 읽기 기록 API v2 변경사항 적용, 감정 데이터 구조 변경, v2 모델 도입, UI 업데이트 등 연결된 이슈 #244의 모든 주요 목표가 충족되었습니다.
Out of Scope Changes check ✅ Passed RecordProgressBar의 진행도 바 세그먼트 수 감소(3->2)는 RecordStep.IMPRESSION 제거와 연관되어 범위 내입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt (2)

325-346: 독서 기록 수정 시 원본 감정 데이터가 손실됩니다.

Lines 334-338에서 실제 독서 기록의 감정 정보가 아닌 고정값(EmotionCode.OTHER, 빈 DetailEmotionArg)을 전달하고 있습니다. 사용자가 기록을 수정하고 저장하면 원래 선택했던 감정 데이터가 모두 사라지는 심각한 데이터 손실이 발생합니다.

selectedRecordInfo에서 실제 감정 데이터를 가져와야 하는데, 현재 ReadingRecordModel 타입에는 V2 감정 필드(primaryEmotion, detailEmotions)가 없는 것으로 추정됩니다. PR 목표에 명시된 대로 독서 기록 모델을 ReadingRecordV2로 마이그레이션해야 합니다.


85-85: ReadingRecordsModelReadingRecordsModelV2로 변경 필요

Line 85의 readingRecords 변수가 V1 모델을 사용하고 있어, Lines 334-338의 감정 데이터(primaryEmotion, detailEmotions)를 제대로 처리할 수 없습니다. 또한 RecordRepository의 getReadingRecords 메서드도 V2 모델로 변경 필요(TODO 주석 확인)하므로, 해당 변경 이후에 이 Presenter를 V2 모델로 마이그레이션해야 합니다.

🤖 Fix all issues with AI agents
In
@feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt:
- Around line 178-186: The failure path in EmotionEditPresenter uses
handleException with an empty onError lambda, so users receive no feedback on
API errors; update the onFailure block to pass a non-empty onError that shows a
user-visible message (e.g., use the same Toast pattern as
RecordRegisterPresenter) by supplying an onError lambda that calls the toast
helper or UI feedback function used elsewhere in the module, while keeping
onLoginRequired behavior (navigator.resetRoot(LoginScreen())) and leaving
handleException handling intact.

In @feature/edit/src/main/res/values/strings.xml:
- Line 16: The Korean postposition in the string resource
edit_emotion_detail_title is incorrect for items like "기타" (OTHER) because "을"
doesn't work after a vowel; update the string to avoid hardcoded 조사 by either
replacing it with a variant without the postposition (e.g., "어떤 %1$s 감정을
느꼈나요?"), switch to a neutral alternative (e.g., "%1$s에 대해 더 자세히 알려주세요"), or
implement dynamic 조사 handling in code and keep a neutral placeholder in the
resource; locate and update the string resource named edit_emotion_detail_title
accordingly.
🧹 Nitpick comments (17)
core/designsystem/src/main/res/values/strings.xml (1)

11-11: 문구 패턴의 일관성을 고려해볼 수 있습니다.

추가된 emotion_other_description 리소스는 의미상 적절하지만, 기존 감정 설명들이 "...한 순간" 패턴을 따르는 반면 새로운 설명은 "...할 때" 패턴을 사용하고 있습니다. OTHER 카테고리의 특성상 의도적인 차별화일 수 있으나, 일관성을 위해 "표현하기 어려운 순간" 같은 형태로 통일하는 것도 고려해볼 수 있습니다.

다만 현재 문구도 의미 전달이 명확하므로 필수 변경사항은 아닙니다.

♻️ 일관성을 위한 대안
-    <string name="emotion_other_description">네 가지 감정으로 표현하기 어려울 때</string>
+    <string name="emotion_other_description">네 가지 감정으로 표현하기 어려운 순간</string>
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewItem.kt (2)

65-128: EmotionItem.kt의 EmotionContent와 중복 로직 존재

EmotionItem.ktEmotionContent와 이 함수가 거의 동일한 구조를 가지고 있습니다 (Image + 감정 이름 + 세부 감정 FlowRow). 차이점은 끝부분에 chevron 아이콘 vs 날짜 텍스트입니다.

추후 공통 컴포넌트로 추출하여 재사용성을 높이는 것을 고려해 보세요.


106-115: FlowRow 마지막 항목 뒤 불필요한 Spacer

forEach 루프에서 마지막 항목 이후에도 Spacer가 추가됩니다. forEachIndexed를 사용하거나 horizontalArrangement를 활용하면 더 깔끔합니다.

♻️ 제안된 수정
-                FlowRow {
-                    detailEmotions.forEach { detail ->
-                        Text(
-                            text = "#${detail.name}",
-                            color = ReedTheme.colors.contentTertiary,
-                            style = ReedTheme.typography.caption1Regular,
-                        )
-                        Spacer(modifier = Modifier.width(ReedTheme.spacing.spacing2))
-                    }
-                }
+                FlowRow(
+                    horizontalArrangement = Arrangement.spacedBy(ReedTheme.spacing.spacing2),
+                ) {
+                    detailEmotions.forEach { detail ->
+                        Text(
+                            text = "#${detail.name}",
+                            color = ReedTheme.colors.contentTertiary,
+                            style = ReedTheme.typography.caption1Regular,
+                        )
+                    }
+                }
feature/edit/src/main/res/values/strings.xml (1)

17-17: 문장 표현을 더 자연스럽게 개선할 수 있습니다.

"선택 기록할"이라는 표현이 문법적으로는 맞지만 다소 어색하게 느껴집니다. 좀 더 자연스러운 표현으로 개선하면 가독성이 향상됩니다.

제안하는 수정안
-    <string name="edit_emotion_detail_description">더 자세한 감정을 선택 기록할 수 있어요.</string>
+    <string name="edit_emotion_detail_description">더 자세한 감정을 선택하여 기록할 수 있어요.</string>

또는:

-    <string name="edit_emotion_detail_description">더 자세한 감정을 선택 기록할 수 있어요.</string>
+    <string name="edit_emotion_detail_description">더 자세한 감정을 선택해 기록할 수 있어요.</string>
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/component/EmotionItem.kt (1)

103-110: 세부 감정 항목 간 spacing 로직을 개선할 수 있습니다.

현재 forEach 내부에서 모든 항목 뒤에 Spacer를 추가하고 있어, 마지막 항목 뒤에도 불필요한 공백이 생길 수 있습니다. forEachIndexed를 사용하여 마지막 항목 뒤에는 spacing을 추가하지 않도록 개선할 수 있습니다.

♻️ 개선 제안
-                FlowRow {
-                    detailEmotions.forEach { detail ->
-                        Text(
-                            text = "#${detail.name}",
-                            color = ReedTheme.colors.contentTertiary,
-                            style = ReedTheme.typography.caption1Regular,
-                        )
-                        Spacer(modifier = Modifier.width(ReedTheme.spacing.spacing2))
-                    }
-                }
+                FlowRow {
+                    detailEmotions.forEachIndexed { index, detail ->
+                        Text(
+                            text = "#${detail.name}",
+                            color = ReedTheme.colors.contentTertiary,
+                            style = ReedTheme.typography.caption1Regular,
+                        )
+                        if (index < detailEmotions.lastIndex) {
+                            Spacer(modifier = Modifier.width(ReedTheme.spacing.spacing2))
+                        }
+                    }
+                }
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/component/EmotionItem.kt (4)

77-108: contentDescription의 다국어화를 권장합니다.

Lines 82, 105의 contentDescription이 영어로 하드코딩되어 있어 접근성 측면에서 다국어 지원이 제한됩니다. stringResource를 사용하여 다국어화하는 것이 좋습니다.

♻️ 다국어화 제안

strings.xml에 리소스 추가:

<string name="emotion_image_content_description">감정 이미지</string>
<string name="chevron_right_content_description">선택 아이콘</string>

코드 수정:

             Image(
                 painter = painterResource(emotionGraphicRes),
-                contentDescription = "Emotion Image",
+                contentDescription = stringResource(R.string.emotion_image_content_description),
                 modifier = Modifier
                     .size(60.dp)
                     .clip(CircleShape),
             )

             Icon(
                 imageVector = ImageVector.vectorResource(iconRes),
-                contentDescription = "Chevron Right",
+                contentDescription = stringResource(R.string.chevron_right_content_description),
                 tint = iconTint,
             )

110-130: 세부 감정 칩 렌더링 로직이 적절합니다.

선택된 세부 감정 ID를 기반으로 칩을 렌더링하는 로직이 잘 구현되어 있습니다. Line 118의 방어 코드(firstOrNull ... ?: return@forEach)는 데이터 불일치 시 크래시를 방지하는 좋은 패턴입니다.

선택사항: 데이터 불일치 감지를 위한 로깅 추가

디버깅 편의를 위해 detailId를 찾지 못한 경우 로그를 남기는 것을 고려해보세요:

             selectedEmotionDetailIds.forEach { detailId ->
-                val detailName = emotionGroup.detailEmotions.firstOrNull { it.id == detailId }?.name ?: return@forEach
+                val detailName = emotionGroup.detailEmotions.firstOrNull { it.id == detailId }?.name ?: run {
+                    Log.w("EmotionItem", "Detail emotion not found for id: $detailId")
+                    return@forEach
+                }
                 ReedRemovableChip(

132-181: 프리뷰 함수가 잘 구현되어 있습니다.

샘플 데이터가 현실적이고, 주요 기능(감정 그룹, 세부 감정, 제거 가능한 칩)을 잘 시연하고 있습니다.

선택 상태(isSelected = true)를 보여주는 추가 프리뷰를 만들면 디자인 확인에 도움이 될 수 있습니다:

@ComponentPreview
@Composable
private fun EmotionItemSelectedPreview() {
    ReedTheme {
        EmotionItem(
            emotionGroup = /* same warmthEmotionGroup */,
            selectedEmotionDetailIds = /* same IDs */,
            onClick = {},
            isSelected = true, // 선택 상태
            onEmotionDetailRemove = {},
            modifier = Modifier.fillMaxWidth(),
        )
    }
}

43-130: 공유 모듈로 추출하여 코드 중복 제거를 강력히 권장합니다.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionItem.kt와 현재 파일의 EmotionItem 컴포저블이 패키지만 다르고 구현은 완전히 동일합니다. 두 파일 모두 동일한 로직과 UI를 갖고 있으므로, core/designsystem/component 또는 공유 UI 모듈로 단일 구현으로 추출하여 향후 유지보수 비용을 크게 줄일 수 있습니다.

feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/component/EmotionDetailBottomSheet.kt (1)

78-84: 접근성: contentDescription을 문자열 리소스로 변경 권장

"Close Icon" 하드코딩된 문자열 대신 stringResource를 사용하면 다국어 지원 및 접근성이 향상됩니다.

♻️ 제안된 수정
 Icon(
     imageVector = ImageVector.vectorResource(designR.drawable.ic_close),
-    contentDescription = "Close Icon",
+    contentDescription = stringResource(R.string.close_icon_description),
     modifier = Modifier.clickableSingle {
         onCloseButtonClick()
     },
 )
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt (1)

192-204: Preview에서 명시적 감정 데이터 사용 권장

Preview에서 primaryEmotiondetailEmotions 필드가 기본값을 사용합니다. 실제 UI를 더 정확하게 테스트하려면 명시적인 감정 데이터를 추가하는 것이 좋습니다.

♻️ 제안된 변경
recordDetailInfo = ReadingRecordModelV2(
    id = "",
    userBookId = "",
    pageNumber = 90,
    quote = "소설가들은 늘 소재를 찾아 떠도는 존재 같지만, 실은 그 반대인 경우가 더 잦다.",
    review = "소설가들은 늘 소재를 찾아 떠도는 존재 같지만, 실은 그 반대인 경우가 더 잦다",
    primaryEmotion = PrimaryEmotionModel(
        code = EmotionCode.WARMTH,
        displayName = "따뜻함",
    ),
    detailEmotions = listOf(
        DetailEmotionModel(id = "1", name = "위로받은"),
    ),
    createdAt = "2023.10.10",
    // ...
),
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt (1)

145-161: Map 타입 일관성 개선이 필요합니다.

OnEmotionDetailCommittedOnEmotionDetailSkipped 이벤트에서 committedEmotionMapselectedEmotionMap을 설정할 때 mapOf()persistentMapOf()가 혼용되고 있습니다. 초기값과 다른 이벤트에서는 persistentMapOf()를 사용하므로 일관성을 위해 통일하는 것이 좋습니다.

제안된 수정
                 is EmotionEditUiEvent.OnEmotionDetailCommitted -> {
                     val emotionKey = selectedEmotionCode ?: return
                     val details = selectedEmotionMap[emotionKey] ?: persistentListOf()

                     committedEmotionCode = emotionKey
-                    committedEmotionMap = mapOf(emotionKey to details)
-                    selectedEmotionMap = mapOf(emotionKey to details)
+                    committedEmotionMap = persistentMapOf(emotionKey to details)
+                    selectedEmotionMap = persistentMapOf(emotionKey to details)
                     isEmotionDetailBottomSheetVisible = false
                 }
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionDetailBottomSheet.kt (1)

41-147: 동일한 기능의 중복 컴포넌트 통합 검토 권장

feature/editfeature/record 모듈에 있는 두 EmotionDetailBottomSheet 컴포넌트가 UI 구조와 로직 측면에서 완전히 동일합니다. 유일한 차이점은 각 모듈의 문자열 리소스 키뿐입니다(edit_emotion_detail_* vs emotion_detail_*). 이 중복을 제거하기 위해 공통 모듈(예: core/ui)로 추출하고, 문자열 리소스를 매개변수로 받거나 공유 리소스 모듈을 통해 관리하는 방식의 리팩토링을 고려해 보세요.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt (2)

240-246: emptyMap() vs persistentMapOf() 사용의 일관성을 확인해주세요.

Lines 77, 79에서 초기값으로 emptyMap()을 사용하고, 여기서는 persistentMapOf()를 사용합니다. 이 둘의 동작은 동일하지만, 코드 일관성을 위해 하나의 방식으로 통일하는 것이 좋습니다.

♻️ 일관성을 위한 수정 제안
 is RecordRegisterUiEvent.OnEmotionDetailSkipped -> {
     committedEmotionCode = selectedEmotionCode
     // 건너뛰기 시 세부감정 선택 초기화
-    committedEmotionMap = persistentMapOf()
-    selectedEmotionMap = persistentMapOf()
+    committedEmotionMap = emptyMap()
+    selectedEmotionMap = emptyMap()
     isEmotionDetailBottomSheetVisible = false
 }

297-299: 감정 그룹 로딩 중 상태 표시 고려

getEmotionGroups() 호출 중 로딩 상태를 표시하지 않아, 감정 목록이 로드되기 전에 빈 화면이 보일 수 있습니다. 네트워크 지연 시 사용자 경험 개선을 위해 로딩 인디케이터 추가를 고려해보세요.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionItem.kt (1)

117-126: 세부 감정 ID 조회 실패 시 로깅 추가 고려

detailIdemotionGroup.detailEmotions에서 찾을 수 없을 때 return@forEach로 조용히 건너뛰는데, 이는 데이터 불일치를 나타낼 수 있습니다. 디버깅을 위해 로깅을 추가하거나, 최소한 주석으로 의도를 명시하는 것이 좋겠습니다.

♻️ 디버깅용 로깅 추가 제안
 selectedEmotionDetailIds.forEach { detailId ->
-    val detailName = emotionGroup.detailEmotions.firstOrNull { it.id == detailId }?.name ?: return@forEach
+    val detailName = emotionGroup.detailEmotions.firstOrNull { it.id == detailId }?.name
+    if (detailName == null) {
+        // 서버와 클라이언트 간 세부 감정 데이터 불일치 시 발생 가능
+        return@forEach
+    }
     ReedRemovableChip(
         label = detailName,
         chipSizeStyle = smallChipStyle,
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUiState.kt (1)

22-25: 네이밍 일관성 검토 필요: committedEmotion vs committedEmotionCode

selectedEmotionCodeEmotionCode임을 명시하고 있으나, committedEmotion은 같은 타입(EmotionCode?)이면서 이름에 Code가 빠져 있습니다. Presenter에서는 committedEmotionCode로 명명되어 있어 혼란을 줄 수 있습니다.

♻️ 일관된 네이밍 제안
 val selectedEmotionCode: EmotionCode? = null,
 val selectedEmotionMap: Map<EmotionCode, ImmutableList<String>> = emptyMap(),
-val committedEmotion: EmotionCode? = null,
+val committedEmotionCode: EmotionCode? = null,
 val committedEmotionMap: Map<EmotionCode, ImmutableList<String>> = emptyMap(),

참고: 이 변경 시 EmotionStepV2.kt Line 86의 state.committedEmotion도 함께 수정 필요합니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21c99a4 and ffcebad.

📒 Files selected for processing (59)
  • core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/EmotionRepository.kt
  • core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/RecordRepository.kt
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/DataGraph.kt
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/mapper/ResponseToModel.kt
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultEmotionRepository.kt
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt
  • core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/Emotion.kt
  • core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/RecordStep.kt
  • core/designsystem/src/main/res/drawable/img_category_sadness.webp
  • core/designsystem/src/main/res/drawable/img_category_warmth.webp
  • core/designsystem/src/main/res/drawable/img_emotion_insight.webp
  • core/designsystem/src/main/res/drawable/img_emotion_joy.webp
  • core/designsystem/src/main/res/drawable/img_emotion_sadness.webp
  • core/designsystem/src/main/res/drawable/img_emotion_warmth.webp
  • core/designsystem/src/main/res/drawable/img_insight.webp
  • core/designsystem/src/main/res/drawable/img_joy.webp
  • core/designsystem/src/main/res/drawable/img_other.webp
  • core/designsystem/src/main/res/drawable/img_sadness.webp
  • core/designsystem/src/main/res/drawable/img_warmth.webp
  • core/designsystem/src/main/res/values/strings.xml
  • core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt
  • core/model/src/main/kotlin/com/ninecraft/booket/core/model/ReadingRecordsModel.kt
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/request/RecordRegisterRequest.kt
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/EmotionGroupsResponse.kt
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/ReadingRecordsResponse.kt
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/service/ReedService.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardUi.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardUiState.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/component/RecordCard.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUiState.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewItem.kt
  • feature/detail/src/main/res/drawable/img_record_card_other.webp
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUiState.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/component/EmotionDetailBottomSheet.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/component/EmotionItem.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditUi.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/component/EmotionItem.kt
  • feature/edit/src/main/res/values/strings.xml
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/component/CustomTooltipBox.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/component/ImpressionGuideBottomSheet.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/component/ImpressionGuideBox.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUiState.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/EmotionStep.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/ImpressionStep.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/QuoteStep.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionDetailBottomSheet.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionItem.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionStepV2.kt
  • feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt
  • feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/arguments/RecordEditArgs.kt
💤 Files with no reviewable changes (7)
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/EmotionStep.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/QuoteStep.kt
  • core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/RecordStep.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/component/CustomTooltipBox.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/component/ImpressionGuideBottomSheet.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/component/ImpressionGuideBox.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/ImpressionStep.kt
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: seoyoon513
Repo: YAPP-Github/Reed-Android PR: 46
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/InfiniteLazyColumn.kt:83-95
Timestamp: 2025-07-14T00:46:03.843Z
Learning: seoyoon513과 팀은 한국어 주석을 선호하며, 한국어 주석을 영어로 번역하라는 제안을 하지 않아야 함
📚 Learning: 2025-07-31T23:17:40.054Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt:29-37
Timestamp: 2025-07-31T23:17:40.054Z
Learning: Reed-Android 프로젝트에서는 API가 준비되지 않은 상황에서 UI를 먼저 구현하고, API 연동 시점에 하드코딩된 데이터를 실제 데이터로 교체하는 개발 방식을 사용한다. RecordItem 컴포넌트의 emotionTags 매개변수도 API 연동 시점에 `text = emotionTags.joinToString(separator = "·") { "#$it" }`로 적용될 예정이다.

Applied to files:

  • feature/edit/src/main/res/values/strings.xml
  • core/designsystem/src/main/res/values/strings.xml
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardUiState.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionItem.kt
  • feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/arguments/RecordEditArgs.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/component/EmotionItem.kt
  • core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/ReadingRecordsResponse.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditUi.kt
  • feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewItem.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionDetailBottomSheet.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/component/EmotionItem.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/request/RecordRegisterRequest.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUiState.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardUi.kt
  • core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/RecordRepository.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUiState.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/component/RecordCard.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt
  • core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/Emotion.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/service/ReedService.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionStepV2.kt
📚 Learning: 2025-07-31T23:30:37.547Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt:18-47
Timestamp: 2025-07-31T23:30:37.547Z
Learning: In Circuit architecture, presenters receive the Screen object directly as a constructor parameter (e.g., Assisted private val screen: RecordDetailScreen), and screen parameters are accessed through this screen object (e.g., screen.recordId). Screen parameters should not be added as separate constructor parameters.

Applied to files:

  • feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
📚 Learning: 2025-07-20T12:34:23.786Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 61
File: feature/webview/build.gradle.kts:17-21
Timestamp: 2025-07-20T12:34:23.786Z
Learning: Reed-Android 프로젝트에서는 `booket.android.feature` convention plugin을 사용하여 feature 모듈들의 공통 의존성을 관리한다. 이 plugin은 Circuit, Compose, 그리고 core 모듈들의 의존성을 자동으로 포함하므로, 각 feature 모듈의 build.gradle.kts에서는 특별한 의존성(예: libs.logger, libs.kakao.auth)만 별도로 선언하면 된다.

Applied to files:

  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt
📚 Learning: 2025-08-28T12:25:54.058Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 174
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt:128-133
Timestamp: 2025-08-28T12:25:54.058Z
Learning: In BookSearchPresenter.kt, when a guest user tries to register a book and is redirected to login, the bottom sheet (isBookRegisterBottomSheetVisible) and selection state (selectedBookIsbn, selectedBookStatus) are intentionally kept open/preserved so that when the user returns from login, they can continue from where they left off without losing context.

Applied to files:

  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
📚 Learning: 2025-09-01T10:41:12.447Z
Learnt from: seoyoon513
Repo: YAPP-Github/Reed-Android PR: 175
File: feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/EmotionStep.kt:112-116
Timestamp: 2025-09-01T10:41:12.447Z
Learning: Reed Android 프로젝트에서 multipleEventsCutterEnabled 설정은 API 호출 여부에 따라 결정됨. ImpressionStep에서만 API 호출이 발생하므로 해당 스텝에서만 다중탭 방지를 활성화하고, 다른 스텝(EmotionStep, QuoteStep)에서는 비활성화하는 것이 올바른 설계임.

Applied to files:

  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
🧬 Code graph analysis (14)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionItem.kt (3)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/chip/ReedRemovableChip.kt (1)
  • ReedRemovableChip (25-63)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/theme/Theme.kt (1)
  • ReedTheme (14-21)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/component/EmotionItem.kt (1)
  • EmotionItem (36-59)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/component/EmotionDetailBottomSheet.kt (4)
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedBottomSheet.kt (1)
  • ReedBottomSheet (21-40)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/chip/ReedSelectableChip.kt (1)
  • ReedSelectableChip (20-54)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/button/ReedButton.kt (1)
  • ReedButton (34-114)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/theme/Theme.kt (1)
  • ReedTheme (14-21)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditUi.kt (1)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/component/EmotionItem.kt (1)
  • EmotionItem (36-59)
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt (1)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/RunSuspendCatching.kt (1)
  • runSuspendCatching (16-30)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt (2)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/QuoteStepV2.kt (1)
  • QuoteStepV2 (57-233)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionStepV2.kt (1)
  • EmotionStepV2 (37-144)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewItem.kt (2)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/component/EmotionItem.kt (1)
  • EmotionContent (61-121)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/theme/Theme.kt (1)
  • ReedTheme (14-21)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionDetailBottomSheet.kt (1)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/chip/ReedSelectableChip.kt (1)
  • ReedSelectableChip (20-54)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt (2)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt (1)
  • handleException (15-48)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt (1)
  • getEmotionGroups (154-174)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/component/EmotionItem.kt (3)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/chip/ReedRemovableChip.kt (1)
  • ReedRemovableChip (25-63)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/theme/Theme.kt (1)
  • ReedTheme (14-21)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionItem.kt (1)
  • EmotionItem (43-130)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt (2)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/component/EmotionItem.kt (1)
  • EmotionItem (43-130)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/component/EmotionDetailBottomSheet.kt (1)
  • EmotionDetailBottomSheet (41-148)
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultEmotionRepository.kt (1)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/RunSuspendCatching.kt (1)
  • runSuspendCatching (16-30)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt (2)
core/network/src/main/kotlin/com/ninecraft/booket/core/network/service/ReedService.kt (1)
  • editRecord (137-141)
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt (1)
  • editRecord (41-50)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt (3)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt (1)
  • handleException (15-48)
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt (1)
  • postRecord (17-26)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt (1)
  • getEmotionGroups (169-188)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionStepV2.kt (3)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionItem.kt (1)
  • EmotionItem (43-130)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionDetailBottomSheet.kt (1)
  • EmotionDetailBottomSheet (41-148)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/theme/Theme.kt (1)
  • ReedTheme (14-21)
⏰ 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). (2)
  • GitHub Check: ci-build
  • GitHub Check: Compose Stability Check
🔇 Additional comments (67)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewItem.kt (2)

33-39: 함수 시그니처 변경 LGTM!

V2 API 스펙에 맞춰 emotion: String에서 PrimaryEmotionModelImmutableList<DetailEmotionModel>로 변경된 것이 적절합니다. 타입 안전성과 구조화된 데이터 처리가 개선되었습니다.


130-219: Preview 함수들 LGTM!

다양한 UI 상태를 잘 커버하고 있습니다:

  • 리뷰 있음 + 세부 감정 있음
  • 리뷰 없음 + 세부 감정 있음
  • 리뷰 없음 + 세부 감정 없음

V2 모델을 활용한 테스트 데이터가 적절합니다.

feature/edit/src/main/res/values/strings.xml (1)

18-19: LGTM!

건너뛰기 및 선택 완료 버튼 문구가 명확하고 적절합니다.

feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/component/EmotionItem.kt (4)

1-34: 임포트 구조가 깔끔합니다.

필요한 의존성만 포함되어 있고, ImmutableList 사용으로 Compose 재구성 최적화가 적용되었습니다. EmotionCode 기반 리팩토링과 잘 통합되어 있습니다.


36-59: 컴포저블 구조가 잘 설계되었습니다.

clickable modifier 순서가 올바르고, internal 가시성이 적절하게 적용되었습니다. EmotionCode 기반 데이터 모델과 잘 통합되어 있습니다.


61-121: EmotionCode 기반 UI 로직이 잘 구현되었습니다.

EmotionCode.OTHER에 대한 색상 분기 처리가 PR 목표와 일치하며, 감정 그래픽 및 세부 감정 표시 로직이 명확합니다.


123-147: 프리뷰 함수가 적절합니다.

실제 사용 케이스를 잘 반영한 샘플 데이터로 컴포넌트를 미리 볼 수 있습니다. EmotionCode.WARMTH를 사용하여 일반적인 감정 케이스를 테스트하고 있습니다.

feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/component/EmotionItem.kt (2)

1-41: 임포트 및 패키지 선언이 적절합니다.

V2 모델(EmotionGroupModel, EmotionCode, DetailEmotionModel)과 디자인 시스템 컴포넌트를 올바르게 임포트하고 있으며, 불필요한 임포트가 없습니다.


43-75: 함수 시그니처와 상태 처리가 잘 구현되어 있습니다.

선택 상태에 따른 border와 icon 변경 로직이 명확하고, Modifier chain도 적절하게 구성되어 있습니다.

feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardUiState.kt (1)

5-5: 감정 모델 마이그레이션이 올바르게 적용되었습니다.

String 기반 감정 표현에서 EmotionCode enum 기반으로 변경되어 타입 안정성이 향상되었습니다. 기본값으로 EmotionCode.OTHER를 사용하는 것도 적절합니다.

Also applies to: 15-15

core/network/src/main/kotlin/com/ninecraft/booket/core/network/request/RecordRegisterRequest.kt (1)

14-17: V2 API 스펙에 맞춰 요청 모델이 올바르게 업데이트되었습니다.

emotionTags에서 primaryEmotiondetailEmotionTagIds로 분리되어 대표 감정과 세부 감정을 명확히 구분할 수 있게 되었습니다. 직렬화 어노테이션도 적절하게 설정되어 있습니다.

feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUiState.kt (2)

10-19: 감정 선택 상태 관리가 체계적으로 설계되었습니다.

서버 기반 감정 그룹 데이터를 활용하도록 상태 구조가 잘 설계되었습니다:

  • emotionGroups: 서버에서 제공하는 감정 그룹 목록 저장
  • selectedEmotionCode/committedEmotion: 임시 선택과 확정된 선택을 분리하여 사용자가 취소할 수 있도록 지원
  • selectedEmotionMap/committedEmotionMap: 각 감정별 세부 감정 선택 상태를 독립적으로 관리

이 구조는 PR 목표인 "GET /api/v2/emotions 응답 기반 감정 표시"를 효과적으로 지원합니다.


21-30: 이벤트 구조가 감정 선택 플로우를 명확하게 표현합니다.

OnSelectEmotionCode를 통한 대표 감정 선택, OnEmotionDetailToggled/OnEmotionDetailRemoved를 통한 세부 감정 토글, OnEmotionDetailCommitted/OnEmotionDetailSkipped를 통한 확정/취소 플로우가 직관적으로 구성되어 있습니다.

core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/EmotionRepository.kt (1)

1-7: 감정 데이터 저장소 인터페이스가 명확하게 정의되었습니다.

서버에서 감정 그룹 데이터를 가져오는 단일 책임을 가진 간결한 인터페이스입니다. Result 타입을 사용한 에러 처리와 suspend 함수를 통한 비동기 지원이 적절하게 구성되어 있습니다.

feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt (1)

314-323: 감정 데이터는 ReadingRecordModel의 제한으로 인한 의도된 임시 조치입니다.

Line 320의 EmotionCode.OTHER 하드코딩과 Line 335-338의 primaryEmotion, detailEmotions 고정값은 현재 ReadingRecordModelemotionTags: List<String>만 포함하고 구조화된 감정 데이터가 없기 때문입니다. ReadingRecordModelV2primaryEmotion: PrimaryEmotionModeldetailEmotions: List<DetailEmotionModel>을 포함하고 있어, API 연동 시점에 모델을 전환할 때 함께 업데이트될 예정입니다. TODO 주석이 이미 임시 조치임을 명시하고 있으므로, 추가 조치는 불필요합니다.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt (1)

30-31: V2 컴포넌트로의 마이그레이션이 올바르게 적용되었습니다.

step 패키지에서 step_v2 패키지로의 import 변경과 실제 컴포넌트 사용이 일관되게 처리되었습니다. ImpressionStep 제거도 PR 목표와 일치합니다.

Also applies to: 74-74, 78-78

feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardUi.kt (1)

86-86: RecordCard 컴포넌트의 emotionCode 파라미터 변경 사항 검증 필요

emotion에서 emotionCode로의 변경이 breaking change이므로, RecordCard 컴포넌트의 모든 호출 지점이 일관되게 업데이트되었는지 확인이 필요합니다. 다음 사항을 수동으로 검증해주세요:

  • RecordCard 컴포넌트를 사용하는 모든 호출 지점에서 emotionCode 파라미터 사용 확인
  • emotion 파라미터를 여전히 사용하는 지점이 있는지 확인
  • 특히 RecordCardUi 외의 다른 파일에서의 사용 패턴 검증
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt (1)

84-84: RecordCardScreen의 emotionCode 속성이 올바르게 정의되어 있습니다.

RecordCardScreen 클래스에서 emotionCode: EmotionCode 속성이 정상적으로 정의되어 있으며, RecordCardPresenter.kt 라인 84에서 올바르게 참조되고 있습니다. 또한 RecordCardUiState, RecordCardUi, RecordCard 컴포넌트 전반에서 emotionCode가 일관되게 사용되고 있으며, 이전 emotion 속성의 남은 부분도 없습니다.

core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/DataGraph.kt (1)

5-5: EmotionRepository DI 바인딩이 올바르게 추가되었으며, 구현체가 정상 존재합니다.

DefaultEmotionRepository 구현체가 존재하며, EmotionRepository 인터페이스를 올바르게 구현하고 있습니다. DI 바인딩은 프로젝트의 Metro 패턴과 일관되게 적용되어 있습니다.

feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUiState.kt (1)

4-4: 마이그레이션이 올바르게 적용되었습니다.

ReadingRecordModelV2로의 타입 변경이 정확하며, 기본값으로 ReadingRecordModelV2()를 사용하는 것도 안전합니다. 모든 필드가 적절한 기본값을 가지고 있습니다.

feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/component/EmotionDetailBottomSheet.kt (2)

93-101: FlowRow의 중복 패딩 확인 필요

상위 Column에서 이미 startendspacing5 패딩이 적용되어 있는데, FlowRow에서도 동일한 패딩을 추가하고 있습니다. 의도된 디자인인지 확인이 필요합니다.


41-148: 컴포넌트 구조가 잘 설계되었습니다.

  • EmotionGroupModelselectedEmotionDetailIds를 분리하여 상태 관리가 명확합니다.
  • 확인 버튼의 enabled 조건(selectedEmotionDetailIds.isNotEmpty())이 적절합니다.
  • 콜백 분리가 잘 되어 있어 단방향 데이터 흐름을 유지합니다.
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultEmotionRepository.kt (1)

12-20: 구현이 간결하고 적절합니다.

  • runSuspendCatching을 사용하여 CancellationException을 적절히 처리합니다.
  • DI 스코프(@SingleIn(DataScope::class))가 올바르게 설정되어 있습니다.
  • 네트워크 응답을 도메인 모델로 매핑하는 책임이 명확합니다.
core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt (2)

5-21: 모델 정의가 적절합니다.

  • @Stable 어노테이션으로 Compose 재구성 최적화가 잘 되어 있습니다.
  • 도메인 모델 구조가 네트워크 응답 구조와 일관성 있게 설계되었습니다.

23-30: fromCode null 처리는 이미 적절히 구현됨

모든 호출 지점에서 Elvis 연산자(?:)를 사용하여 EmotionCode.OTHER를 기본값으로 제공하고 있습니다. 서버에서 새로운 감정 코드가 추가되더라도 안전하게 처리됩니다.

feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt (2)

58-67: V2 감정 모델로의 마이그레이션이 잘 반영되었습니다.

  • String 기반에서 EmotionCode 타입 기반으로 변경하여 타입 안전성이 향상되었습니다.
  • Result 클래스가 구조화된 PrimaryEmotionArgDetailEmotionArg를 사용하여 데이터 무결성이 개선되었습니다.

88-92: RecordCardScreen의 emotion 타입 변경 확인

emotion: String에서 emotionCode: EmotionCode로 변경되어 모든 호출 측이 업데이트되어야 합니다.

feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt (2)

144-170: RecordEditArgs 생성 로직이 새 모델에 맞게 잘 업데이트되었습니다.

PrimaryEmotionArgDetailEmotionArg 매핑이 ReadingRecordModelV2의 구조와 일관성 있게 작성되었습니다.


55-55: 코드 검증 결과, ReadingRecordModelV2()PrimaryEmotionModel()은 모두 안전한 기본값으로 초기화되어 있습니다:

  • primaryEmotion: PrimaryEmotionModel = PrimaryEmotionModel()
  • code: EmotionCode = EmotionCode.OTHER (기본값 설정됨)

따라서 primaryEmotion.code 접근 시 예외가 발생할 우려는 없습니다.

feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/arguments/RecordEditArgs.kt (1)

22-35: 새로운 Arg 클래스들이 잘 정의되었습니다.

  • @Immutable@Parcelize 조합이 적절합니다.
  • PrimaryEmotionArgDetailEmotionArg의 분리로 타입 안전성과 명확성이 향상되었습니다.
  • Kotlin enum은 기본적으로 Parcelable을 지원하므로 EmotionCode 사용에 문제가 없습니다.
core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/EmotionGroupsResponse.kt (1)

6-28: 네트워크 응답 모델이 잘 정의되었습니다.

  • @SerialName 명시적 사용으로 API 계약이 명확합니다.
  • 구조가 도메인 모델(EmotionGroupsModel, EmotionGroupModel, DetailEmotionModel)과 일관성 있게 설계되었습니다.

API에서 필드가 누락될 가능성이 있다면 기본값 추가를 고려해 주세요 (예: val emotions: List<EmotionGroup> = emptyList()).

feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt (5)

46-49: 중복 상수 값 확인 필요

RECORD_EDITRECORD_EDIT_SAVE 상수가 동일한 값 "record_edit_save"를 가지고 있습니다. Line 168에서 RECORD_EDIT가 사용되고 Line 113에서 RECORD_EDIT_SAVE가 사용되는데, 의도된 동작인지 확인이 필요합니다.

만약 화면 조회와 저장 이벤트를 구분하려면 서로 다른 값을 사용해야 할 것 같습니다.


69-76: 감정 변경 감지 로직 LGTM

Set 비교를 사용하여 순서에 관계없이 감정 변경을 정확하게 감지합니다. screen.recordInfo의 원본 값과 현재 recordInfo를 비교하는 로직이 올바르게 구현되었습니다.


88-93: 감정 수정 결과 처리 LGTM

EmotionEditScreen.Result에서 반환된 primaryEmotiondetailEmotionsrecordInfo에 올바르게 복사하여 업데이트합니다.


95-130: editRecord 함수 LGTM

새로운 V2 API 스펙에 맞게 primaryEmotiondetailEmotionIds 파라미터를 올바르게 전달하고 있습니다. repository.editRecord 호출 시그니처와 정확히 일치합니다.


151-163: 저장 버튼 클릭 처리 LGTM

primaryEmotion.code.name으로 EmotionCode enum을 서버가 기대하는 문자열로 올바르게 변환하고, detailEmotions.map { it.id }로 세부 감정 ID 목록을 추출합니다.

core/model/src/main/kotlin/com/ninecraft/booket/core/model/ReadingRecordsModel.kt (2)

29-44: ReadingRecordModelV2 모델 LGTM

V2 API 스펙에 맞게 primaryEmotiondetailEmotions 필드가 올바르게 구조화되었습니다. @Stable 어노테이션으로 Compose 리컴포지션 최적화에도 적합합니다.


46-50: PrimaryEmotionModel 기본값 LGTM

EmotionCode.OTHER"기타"를 기본값으로 사용하여 알 수 없는 감정 코드에 대한 안전한 폴백을 제공합니다.

feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditUi.kt (2)

170-183: EmotionItem 컴포넌트 사용 LGTM

새로운 감정 모델 구조에 맞게 EmotionItem을 올바르게 호출합니다. toPersistentList()를 사용하여 불변 리스트로 변환하는 것은 Compose 안정성에 좋은 패턴입니다.


215-228: Preview 데이터 LGTM

실제 사용 시나리오에 맞는 감정 데이터로 프리뷰가 업데이트되었습니다. PrimaryEmotionArgDetailEmotionArg 구조가 잘 반영되어 있습니다.

core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/ReadingRecordsResponse.kt (3)

32-33: V1 API 응답과의 호환성 확인 필요

emotionTags 필드에서 기본값 = emptyList()가 제거되었습니다. 만약 V1 API가 해당 필드를 누락하거나 null로 반환하는 경우, 역직렬화 시 예외가 발생할 수 있습니다.

서버에서 항상 이 필드가 반환되는지 확인하거나, 안전을 위해 기본값을 유지하는 것을 고려해주세요.


38-45: Nullable 필드 변경 LGTM

bookTitle, bookPublisher, bookCoverImageUrl, author 필드를 nullable로 변경하여 API 응답의 유연성을 높였습니다. 다운스트림 코드에서 null 처리가 적절히 이루어지는지 확인해주세요.


48-84: ReadingRecordV2 및 PrimaryEmotion 모델 LGTM

V2 API 스펙에 맞게 구조화된 감정 데이터(primaryEmotion, detailEmotions)를 사용합니다. PrimaryEmotioncodedisplayName을 포함하여 서버 응답을 올바르게 매핑합니다.

core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/Emotion.kt (2)

31-47: EmotionCode 확장 프로퍼티 LGTM

EmotionCode에 대한 graphicRescategoryGraphicRes 매핑이 완전합니다. OTHER 케이스에서 categoryGraphicResnull을 반환하는 것은 해당 감정에 카테고리 그래픽이 없음을 적절히 표현합니다.


49-56: descriptionRes 매핑 LGTM

모든 EmotionCode 케이스에 대한 설명 문자열 리소스 매핑이 완료되었습니다. emotion_other_description 리소스가 strings.xml에 추가되었는지 확인해주세요.

feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt (1)

166-171: ReviewItem 호출 업데이트 LGTM

primaryEmotiondetailEmotions를 사용하는 새로운 V2 모델 구조에 맞게 ReviewItem 호출이 업데이트되었습니다. toPersistentList()로 불변성을 보장합니다.

feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/component/RecordCard.kt (2)

26-35: EmotionCode 타입 마이그레이션 LGTM

emotion: String에서 emotionCode: EmotionCode로 변경하여 타입 안전성이 향상되었습니다. 문자열 비교 대신 enum을 사용하여 컴파일 타임에 오류를 방지할 수 있습니다.


77-85: getEmotionCardImage 함수 LGTM

EmotionCode enum의 모든 케이스를 exhaustive하게 처리합니다. OTHER 케이스에 대한 전용 이미지 리소스(img_record_card_other)가 포함되어 있어 완전한 매핑이 이루어졌습니다.

feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt (1)

77-111: LGTM!

OnEditButtonClick 이벤트 핸들링이 잘 구현되어 있습니다. emotionGroups에서 해당하는 감정을 찾지 못할 경우에 대한 fallback 처리(EmotionCode.OTHER)가 적절합니다.

core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/mapper/ResponseToModel.kt (2)

198-218: LGTM!

EmotionGroupsResponse, EmotionGroup, DetailEmotion 매핑 함수들이 잘 구현되어 있습니다. EmotionCode.fromCode에서 null 반환 시 EmotionCode.OTHER로 fallback 처리하는 방어적 코딩이 적절합니다.


260-284: LGTM!

ReadingRecordV2.toModel()PrimaryEmotion.toModel() 매핑이 V2 스펙에 맞게 잘 구현되어 있습니다. nullable 필드들에 대한 기본값 처리도 일관성 있게 적용되었습니다.

feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt (2)

143-174: BottomSheet 조기 반환 시 UI 상태 불일치 가능성

Line 144에서 selectedEmotionGroupnull일 경우 return으로 조기 종료하고 있습니다. 그러나 isEmotionDetailBottomSheetVisibletrue인 상태에서 emotionGroups가 비어있거나 매칭되는 감정이 없으면 BottomSheet가 표시되지 않으면서도 상태는 true로 유지되어 UI 상태 불일치가 발생할 수 있습니다.

selectedEmotionCode가 설정될 때 항상 유효한 emotionGroups 내의 코드인지 확인이 필요합니다. Presenter에서 OnSelectEmotionCode 이벤트 처리 시 유효성 검증이 되어 있는지 확인해 주세요.


81-125: LGTM!

LazyColumn을 사용한 감정 목록 렌더링과 EmotionItem 컴포넌트 통합이 잘 구현되어 있습니다. 각 아이템의 선택 상태 및 세부 감정 표시 로직이 적절합니다.

core/network/src/main/kotlin/com/ninecraft/booket/core/network/service/ReedService.kt (3)

108-117: LGTM!

V2 감정 API 엔드포인트(GET api/v2/emotions)와 기록 생성 엔드포인트가 올바르게 추가되었습니다. ReadingRecordV2 반환 타입으로의 마이그레이션이 적절합니다.


119-125: V1 엔드포인트 유지 확인

getReadingRecords가 여전히 api/v1/reading-records를 사용하고 있습니다. RecordRepository의 TODO 주석에 따라 V2로 변경이 필요한 것으로 보이며, 이는 의도된 단계적 마이그레이션인지 확인 부탁드립니다.


137-141: 백엔드 API 명세와의 HTTP 메서드 일치 확인 필요

editRecord 엔드포인트의 HTTP 메서드가 PATCH에서 PUT으로 변경되었습니다. REST API 모범 사례상 PUT은 전체 리소스 교체(idempotent)를, PATCH는 부분 수정을 의미합니다. 이 변경이 백엔드 API 스펙 및 의도와 일치하는지 확인이 필요합니다.

core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/RecordRepository.kt (1)

6-34: LGTM!

인터페이스가 V2 모델에 맞게 잘 업데이트되었습니다. primaryEmotiondetailEmotionTagIds 파라미터 추가 및 ReadingRecordModelV2 반환 타입 적용이 적절합니다. getReadingRecords에 대한 TODO 주석으로 추후 작업 범위도 명확히 표시되어 있습니다.

core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt (2)

17-26: LGTM!

postRecord 구현이 새로운 감정 모델 파라미터를 올바르게 처리하고 있습니다. RecordRegisterRequest 생성 및 toModel() 매핑이 적절합니다.


41-50: LGTM!

editRecord 구현이 인터페이스 변경사항에 맞게 잘 업데이트되었습니다. runSuspendCatching 패턴을 일관되게 사용하고 있습니다.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionDetailBottomSheet.kt (2)

41-51: LGTM!

컴포넌트 시그니처가 새로운 EmotionGroupModel 기반 데이터 모델에 맞게 잘 업데이트되었습니다. selectedEmotionDetailIds를 통한 ID 기반 선택 상태 관리가 적절합니다.


108-117: LGTM!

emotionGroup.detailEmotions 순회 및 detail.id 기반 선택 상태 확인 로직이 올바르게 구현되었습니다.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt (2)

154-174: LGTM! 감정 그룹 로딩 로직이 올바르게 구현되었습니다.

EmotionEditPresenter의 패턴과 일관되게 에러 처리 및 로그인 필요 시 리다이렉션이 잘 구현되어 있습니다.


273-276: committedEmotionCode가 null일 경우 빈 문자열 전송 확인

primaryEmotion이 빈 문자열로 전송될 수 있는데, 서버 API가 이 케이스를 올바르게 처리하는지 확인이 필요합니다. 현재 로직상 isNextButtonEnabledcommittedEmotionCode != null을 체크하므로 실제로는 발생하지 않겠지만, 방어적 코딩 관점에서 확인해주세요.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionItem.kt (1)

78-88: LGTM! OTHER 감정 코드의 이미지 없는 케이스를 잘 처리했습니다.

categoryGraphicRes가 null인 경우(예: OTHER 감정)에 이미지와 스페이서를 조건부로 렌더링하여 깔끔하게 처리되었습니다.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionStepV2.kt (2)

112-113: Composable 함수 내 early return 사용 주의

?: return을 사용하면 selectedEmotionGroup을 찾지 못할 경우 Composable 함수가 조기 종료됩니다. 이는 조건부 UI 로직에서 예상치 못한 recomposition 동작을 유발할 수 있습니다. isEmotionDetailBottomSheetVisible이 true일 때 selectedEmotionCode에 해당하는 그룹이 반드시 존재해야 하는지 확인해주세요.

♻️ 안전한 처리를 위한 대안
 if (state.isEmotionDetailBottomSheetVisible) {
-    val selectedEmotionGroup = state.emotionGroups.firstOrNull { it.code == state.selectedEmotionCode } ?: return
+    val selectedEmotionGroup = state.emotionGroups.firstOrNull { it.code == state.selectedEmotionCode }
+    if (selectedEmotionGroup != null) {
         EmotionDetailBottomSheet(
             emotionGroup = selectedEmotionGroup,
             selectedEmotionDetailIds = state.selectedEmotionMap[state.selectedEmotionCode] ?: persistentListOf(),
             // ... rest of the parameters
         )
+    }
 }

79-92: LGTM! 감정 그룹 렌더링 및 이벤트 처리가 올바르게 구현되었습니다.

emotionGroups 순회, committedEmotionMap을 통한 선택된 세부 감정 표시, emotion.code 기반 선택 상태 비교가 V2 모델에 맞게 잘 구현되었습니다.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUiState.kt (1)

14-33: LGTM! V2 감정 모델 기반 UI 상태가 잘 정의되었습니다.

EmotionCode 기반의 선택/확정 상태 분리, ImmutableList 사용으로 안정적인 상태 관리, Circuit 패턴 준수가 잘 되어 있습니다.

Comment on lines 178 to 186
}.onFailure { exception ->
handleException(
exception = exception,
onError = {},
onLoginRequired = {
navigator.resetRoot(LoginScreen())
},
)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

에러 발생 시 사용자에게 피드백이 없습니다.

onError 콜백이 빈 람다({})로 설정되어 있어 API 호출 실패 시 사용자에게 어떠한 피드백도 제공되지 않습니다. RecordRegisterPresenter에서는 Toast를 통해 에러 메시지를 표시하는 패턴을 사용하고 있습니다.

제안된 수정
+                        val handleErrorMessage = { message: String ->
+                            // TODO: sideEffect를 통해 Toast 표시 또는 에러 상태 관리
+                        }
+
                         handleException(
                             exception = exception,
-                            onError = {},
+                            onError = handleErrorMessage,
                             onLoginRequired = {
                                 navigator.resetRoot(LoginScreen())
                             },
                         )
🤖 Prompt for AI Agents
In
@feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt
around lines 178 - 186, The failure path in EmotionEditPresenter uses
handleException with an empty onError lambda, so users receive no feedback on
API errors; update the onFailure block to pass a non-empty onError that shows a
user-visible message (e.g., use the same Toast pattern as
RecordRegisterPresenter) by supplying an onError lambda that calls the toast
helper or UI feedback function used elsewhere in the module, while keeping
onLoginRequired behavior (navigator.resetRoot(LoginScreen())) and leaving
handleException handling intact.

<string name="edit_emotion_title">문장에 대해 어떤 감정이 드셨나요?</string>
<string name="edit_emotion_description">대표 감정을 한 가지 선택해주세요</string>
<string name="edit_emotion_edit">수정하기</string>
<string name="edit_emotion_detail_title">어떤 %1$s을 느꼈나요?</string>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

한국어 조사 문법 오류 가능성이 있습니다.

문자열에 사용된 조사 "을"은 받침이 있는 단어 뒤에 사용됩니다. 하지만 감정 코드 중 "기타"(OTHER)는 모음으로 끝나므로 "어떤 기타을"이 아닌 "어떤 기타를"이 되어야 합니다.

다음 중 한 가지 해결 방법을 고려해주세요:

  1. 조사 없이 구성: "어떤 %1$s 감정을 느꼈나요?"
  2. 코드에서 동적으로 조사 처리
  3. 다른 표현 사용: "%1$s에 대해 더 자세히 알려주세요"
제안하는 수정안
-    <string name="edit_emotion_detail_title">어떤 %1$s을 느꼈나요?</string>
+    <string name="edit_emotion_detail_title">어떤 %1$s 감정을 느꼈나요?</string>
📝 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
<string name="edit_emotion_detail_title">어떤 %1$s을 느꼈나요?</string>
<string name="edit_emotion_detail_title">어떤 %1$s 감정을 느꼈나요?</string>
🤖 Prompt for AI Agents
In @feature/edit/src/main/res/values/strings.xml at line 16, The Korean
postposition in the string resource edit_emotion_detail_title is incorrect for
items like "기타" (OTHER) because "을" doesn't work after a vowel; update the
string to avoid hardcoded 조사 by either replacing it with a variant without the
postposition (e.g., "어떤 %1$s 감정을 느꼈나요?"), switch to a neutral alternative (e.g.,
"%1$s에 대해 더 자세히 알려주세요"), or implement dynamic 조사 handling in code and keep a
neutral placeholder in the resource; locate and update the string resource named
edit_emotion_detail_title accordingly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt (1)

54-57: modifier 파라미터가 두 번 적용되어 레이아웃 문제가 발생할 수 있습니다.

외부에서 전달된 modifierReedScaffold (라인 51)와 내부 Column (라인 55)에 모두 적용됩니다. 내부 Column에는 Modifier (companion object)를 사용해야 합니다.

🐛 제안된 수정
         Column(
-            modifier = modifier
+            modifier = Modifier
                 .fillMaxSize()
                 .padding(innerPadding),
         ) {
🤖 Fix all issues with AI agents
In
@core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/ReadingRecordsResponse.kt:
- Around line 48-84: ReadingRecordV2 references DetailEmotion but the type isn't
imported; add the missing import for DetailEmotion (defined in
EmotionGroupsResponse.kt) to the top of the file so ReadingRecordV2 and
PrimaryEmotion compile correctly—import
com.ninecraft.booket.core.network.response.DetailEmotion.
- Line 33: The response model's emotionTags property currently has no default
and will cause deserialization failures if the API omits that field; update the
ReadingRecordsResponse data class to make emotionTags optional by adding a
default (e.g., val emotionTags: List<String> = emptyList()) so it safely maps to
the core ReadingRecordsModel which expects an emptyList by default and prevents
errors when the V1 API response omits emotionTags.

In
@feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt:
- Around line 235-242: In OnEmotionDetailRemoved the code reads from
committedEmotionMap using selectedEmotionCode but should use the
committedEmotionCode to remove details from the committed set; change the lookup
and subsequent updates to use committedEmotionCode (keep selectedEmotionMap
updates as appropriate) so you compute currentDetails =
committedEmotionMap[committedEmotionCode].orEmpty(), build updatedDetails, then
set committedEmotionMap = committedEmotionMap + (committedEmotionCode to
updatedDetails.toPersistentList()) and update selectedEmotionMap only if that
key matches the currently selectedEmotionCode.
- Around line 213-221: When handling RecordRegisterUiEvent.OnSelectEmotionCode
in RecordRegisterPresenter you set selectedEmotionCode and committedEmotionCode
for EmotionCode.OTHER but forgot to clear committedEmotionMap and
selectedEmotionMap; update the branch for EmotionCode.OTHER to also reset
committedEmotionMap and selectedEmotionMap (clear or assign empty maps) so
leftover detail-emotion entries aren't sent when postRecord is called, leaving
the existing behavior for non-OTHER (setting isEmotionDetailBottomSheetVisible =
true) unchanged.
🧹 Nitpick comments (4)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt (1)

167-174: OnEmotionDetailRemoved에서 selectedEmotionCode를 사용하면 의도치 않은 감정에서 세부감정이 삭제될 수 있습니다.

현재 이벤트는 detailId만 전달받고 selectedEmotionCode를 사용하여 삭제 대상을 결정합니다. 하지만 사용자가 감정 A를 선택 후 confirm하고, 감정 B를 클릭하여 바텀시트가 열린 상태에서 배경의 감정 A 칩을 삭제하려 하면 selectedEmotionCode는 B이므로 잘못된 감정에서 삭제가 시도됩니다.

이벤트에 해당 감정의 코드를 함께 전달하는 것이 더 안전합니다.

♻️ 제안된 수정

EmotionEditUiState.kt에서 이벤트 수정:

-data class OnEmotionDetailRemoved(val detailId: String) : EmotionEditUiEvent
+data class OnEmotionDetailRemoved(val emotionCode: EmotionCode, val detailId: String) : EmotionEditUiEvent

Presenter에서 핸들러 수정:

 is EmotionEditUiEvent.OnEmotionDetailRemoved -> {
-    val emotionKey = selectedEmotionCode ?: return
-    val currentDetails = committedEmotionMap[selectedEmotionCode].orEmpty()
+    val emotionKey = event.emotionCode
+    val currentDetails = committedEmotionMap[emotionKey].orEmpty()
     val updatedDetails = currentDetails - event.detailId

     committedEmotionMap = committedEmotionMap + (emotionKey to updatedDetails.toPersistentList())
     selectedEmotionMap = selectedEmotionMap + (emotionKey to updatedDetails.toPersistentList())
 }
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUiState.kt (1)

11-17: idle이 Kotlin 명명 규칙과 일치하지 않습니다.

data object는 타입처럼 동작하므로 PascalCase를 사용해야 합니다. 다른 상태들(Loading, Success, Error)과의 일관성을 위해 Idle로 변경하는 것을 권장합니다.

♻️ 제안된 수정
 @Immutable
 sealed interface EmotionUiState {
-    data object idle : EmotionUiState
+    data object Idle : EmotionUiState
     data object Loading : EmotionUiState
     data object Success : EmotionUiState
     data class Error(val exception: Throwable) : EmotionUiState
 }

관련 파일들도 함께 수정 필요:

  • EmotionEditPresenter.kt: EmotionUiState.idleEmotionUiState.Idle
  • EmotionEditUi.kt: is EmotionUiState.idleis EmotionUiState.Idle
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUiState.kt (2)

14-20: idleIdle로 변경하여 Kotlin naming convention을 따르세요.

data object는 타입/클래스이므로 PascalCase를 사용해야 합니다. 같은 sealed interface 내의 Loading, Success와 일관성을 유지하기 위해 Idle로 변경하는 것이 좋습니다.

♻️ 제안된 수정
 @Immutable
 sealed interface EmotionUiState {
-    data object idle : EmotionUiState
+    data object Idle : EmotionUiState
     data object Loading : EmotionUiState
     data object Success : EmotionUiState
     data class Error(val exception: Throwable) : EmotionUiState
 }

참고: 이 변경 시 RecordRegisterUi.ktRecordRegisterPresenter.kt에서 EmotionUiState.idle 참조도 함께 업데이트해야 합니다.


30-34: 필드 네이밍 일관성 개선을 권장합니다.

selectedEmotionCode와 대응되는 필드가 committedEmotion으로 되어 있어 일관성이 떨어집니다. Presenter에서도 committedEmotionCode로 사용하고 있으므로 state 필드명도 committedEmotionCode로 변경하면 코드 가독성이 향상됩니다.

♻️ 제안된 수정
     val selectedEmotionCode: EmotionCode? = null,
     val selectedEmotionMap: Map<EmotionCode, ImmutableList<String>> = emptyMap(),
-    val committedEmotion: EmotionCode? = null,
+    val committedEmotionCode: EmotionCode? = null,
     val committedEmotionMap: Map<EmotionCode, ImmutableList<String>> = emptyMap(),
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc9ff9f and 927604f.

📒 Files selected for processing (15)
  • core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/RecordRepository.kt
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt
  • core/model/src/main/kotlin/com/ninecraft/booket/core/model/ReadingRecordsModel.kt
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/request/RecordRegisterRequest.kt
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/ReadingRecordsResponse.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/QuoteItem.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewItem.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUiState.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUiState.kt
  • feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/arguments/RecordEditArgs.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/arguments/RecordEditArgs.kt
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: seoyoon513
Repo: YAPP-Github/Reed-Android PR: 46
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/InfiniteLazyColumn.kt:83-95
Timestamp: 2025-07-14T00:46:03.843Z
Learning: seoyoon513과 팀은 한국어 주석을 선호하며, 한국어 주석을 영어로 번역하라는 제안을 하지 않아야 함
📚 Learning: 2025-07-31T23:17:40.054Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt:29-37
Timestamp: 2025-07-31T23:17:40.054Z
Learning: Reed-Android 프로젝트에서는 API가 준비되지 않은 상황에서 UI를 먼저 구현하고, API 연동 시점에 하드코딩된 데이터를 실제 데이터로 교체하는 개발 방식을 사용한다. RecordItem 컴포넌트의 emotionTags 매개변수도 API 연동 시점에 `text = emotionTags.joinToString(separator = "·") { "#$it" }`로 적용될 예정이다.

Applied to files:

  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewItem.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/ReadingRecordsResponse.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUiState.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUiState.kt
📚 Learning: 2025-08-28T12:25:54.058Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 174
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt:128-133
Timestamp: 2025-08-28T12:25:54.058Z
Learning: In BookSearchPresenter.kt, when a guest user tries to register a book and is redirected to login, the bottom sheet (isBookRegisterBottomSheetVisible) and selection state (selectedBookIsbn, selectedBookStatus) are intentionally kept open/preserved so that when the user returns from login, they can continue from where they left off without losing context.

Applied to files:

  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
📚 Learning: 2025-07-31T23:30:37.547Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt:18-47
Timestamp: 2025-07-31T23:30:37.547Z
Learning: In Circuit architecture, presenters receive the Screen object directly as a constructor parameter (e.g., Assisted private val screen: RecordDetailScreen), and screen parameters are accessed through this screen object (e.g., screen.recordId). Screen parameters should not be added as separate constructor parameters.

Applied to files:

  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt
📚 Learning: 2025-07-28T18:22:00.618Z
Learnt from: seoyoon513
Repo: YAPP-Github/Reed-Android PR: 72
File: feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt:70-70
Timestamp: 2025-07-28T18:22:00.618Z
Learning: LiveTextAnalyzer는 ML Kit 에러를 addOnFailureListener로 처리하지만 로그만 남기고 presenter에 실패를 알리지 않아서, UI에서 텍스트 인식 실패 상태를 표시할 수 없는 문제가 있음. StillTextAnalyzer처럼 실패 콜백을 추가하는 것이 더 나은 해결책임

Applied to files:

  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt
📚 Learning: 2025-07-29T07:02:18.885Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 77
File: feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingUi.kt:73-73
Timestamp: 2025-07-29T07:02:18.885Z
Learning: Kotlin에서 같은 패키지 내의 파일들은 패키지 레벨에 정의된 const val 상수를 import 없이 직접 접근할 수 있습니다. OnboardingPresenter.kt와 OnboardingUi.kt 모두 com.ninecraft.booket.feature.onboarding 패키지에 속해 있어서 ONBOARDING_STEPS_COUNT 상수를 공유할 수 있습니다.

Applied to files:

  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt
🧬 Code graph analysis (6)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt (3)
core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/RecordRepository.kt (1)
  • editRecord (27-34)
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt (1)
  • editRecord (41-50)
core/network/src/main/kotlin/com/ninecraft/booket/core/network/service/ReedService.kt (1)
  • editRecord (137-141)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewItem.kt (1)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/component/EmotionItem.kt (1)
  • EmotionContent (61-121)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt (4)
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedLoadingIndicator.kt (1)
  • ReedLoadingIndicator (16-36)
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.kt (1)
  • ReedErrorUi (22-52)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/component/EmotionItem.kt (1)
  • EmotionItem (43-130)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/component/EmotionDetailBottomSheet.kt (1)
  • EmotionDetailBottomSheet (41-148)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt (4)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt (1)
  • handleException (15-48)
core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/RecordRepository.kt (2)
  • postRecord (6-39)
  • postRecord (7-14)
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt (1)
  • postRecord (17-26)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt (1)
  • getEmotionGroups (73-100)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt (1)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt (1)
  • handleException (15-48)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt (4)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/QuoteStepV2.kt (1)
  • QuoteStepV2 (57-233)
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedLoadingIndicator.kt (1)
  • ReedLoadingIndicator (16-36)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step_v2/EmotionStepV2.kt (1)
  • EmotionStepV2 (37-144)
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.kt (1)
  • ReedErrorUi (22-52)
⏰ 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). (2)
  • GitHub Check: Compose Stability Check
  • GitHub Check: ci-build
🔇 Additional comments (25)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt (1)

73-100: 에러 핸들링 패턴이 일관성 있게 구현되었습니다.

RecordRegisterPresentergetEmotionGroups() 패턴을 따르고 있으며, handleException을 통한 401 에러 처리 및 LoginScreen 리다이렉트가 올바르게 구현되어 있습니다.

feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt (1)

162-193: 바텀시트 상호작용 패턴이 잘 구현되었습니다.

coroutineScope.launch를 사용하여 emotionDetailBottomSheetState.hide()를 먼저 호출한 후 이벤트를 발행하는 패턴은 부드러운 애니메이션을 보장하는 올바른 접근입니다.

feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUiState.kt (1)

19-29: 상태 구조가 잘 설계되었습니다.

선택 상태(selectedEmotionCode, selectedEmotionMap)와 확정 상태(committedEmotion, committedEmotionMap)를 분리하여 바텀시트 취소 시 원래 상태로 복원할 수 있도록 한 설계가 좋습니다.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt (2)

80-96: EmotionUiState 분기 처리가 적절합니다.

에러 상태에서 toErrorType()을 사용한 에러 타입 변환과 재시도 콜백 연결이 잘 구현되어 있습니다.

다만 idle 상태에서 빈 블록 {}은 의도적인 것으로 보이지만, 초기 로딩 트리거가 Presenter의 LaunchedEffect(Unit)에서 발생하므로 문제없습니다.


74-77: LGTM!

V2 컴포넌트(QuoteStepV2, EmotionStepV2)로의 전환이 적절하게 이루어졌습니다.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt (3)

305-307: LGTM!

LaunchedEffect(Unit)을 사용한 초기 감정 데이터 프리로딩은 EMOTION 스텝 진입 시 로딩 지연을 줄여 UX를 개선합니다. rememberRetained 사용으로 configuration change 시에도 데이터가 유지되어 불필요한 재요청을 방지합니다.


112-129: LGTM!

postRecord 함수가 V2 API 스펙에 맞게 업데이트되었습니다. pageNumberInt?로 변경되어 선택적 입력을 지원하고, 새로운 감정 파라미터(primaryEmotion, detailEmotionTagIds, review)가 올바르게 매핑되었습니다.


155-178: LGTM!

getEmotionGroups 함수의 에러 핸들링이 EmotionEditPresenter와 일관되게 구현되었습니다. 에러 발생 시 EmotionUiState.Error로 상태를 전환하여 UI에서 ReedErrorUi를 표시하고, 401 에러 시 로그인 화면으로 리다이렉트하는 흐름이 적절합니다.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUiState.kt (1)

52-68: LGTM!

V2 감정 모델에 맞게 이벤트가 적절히 업데이트되었습니다. OnSelectEmotionCodeEmotionCode 기반 선택을 지원하고, OnRetryGetEmotions로 에러 복구 기능을 제공합니다.

feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt (3)

70-74: 감정 변경 감지 로직이 올바르게 구현되었습니다.

Set 비교를 통해 순서와 무관하게 세부 감정의 변경을 정확히 감지하고 있으며, 대표 감정 코드 비교도 적절합니다.


86-89: EmotionEditScreen 결과 처리가 올바르게 구현되었습니다.

감정 수정 화면에서 반환된 구조화된 감정 데이터를 recordInfo에 정확히 반영하고 있습니다.


151-154: 페이지 번호와 감정 데이터가 올바르게 전달됩니다.

nullable pageNumber 처리와 EmotionCode enum을 문자열로 변환하는 로직이 적절하며, detailEmotionIds 매핑도 정확합니다.

feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/QuoteItem.kt (1)

21-25: nullable 페이지 번호 처리가 올바르게 구현되었습니다.

페이지 번호가 없는 경우 "-"로 표시하는 fallback 처리가 적절하며, UI에서 명확하게 표현됩니다.

feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewItem.kt (3)

35-40: 구조화된 감정 모델로 signature가 올바르게 업데이트되었습니다.

PrimaryEmotionModel과 ImmutableList을 사용하여 타입 안전성이 개선되었으며, V2 API 스펙과 일치합니다.


65-127: EmotionContent 구현이 올바르며 유사 코드와 일관성을 유지합니다.

EmotionItem.kt의 EmotionContent와 유사한 패턴을 따르고 있으며, OTHER 감정 코드에 대한 색상 처리, FlowRow를 통한 세부 감정 표시, 날짜 정렬 로직이 모두 적절합니다.

Based on learnings, 이 컴포넌트는 API 연동을 위해 구조화된 데이터 모델을 사용하도록 업데이트되었습니다.


131-218: Preview 함수들이 다양한 케이스를 잘 커버합니다.

리뷰가 있는 경우, 리뷰가 없는 경우, 세부 감정이 없는 경우 등 주요 UI 상태를 모두 미리보기로 확인할 수 있어 개발 및 디버깅에 유용합니다.

core/network/src/main/kotlin/com/ninecraft/booket/core/network/request/RecordRegisterRequest.kt (1)

9-17: V2 API 스펙에 맞게 요청 모델이 올바르게 업데이트되었습니다.

RecordRegisterRequest의 변경사항이 확인되었습니다:

  • pageNumber nullable 처리 (Int?)
  • emotionTags 필드를 primaryEmotiondetailEmotionTagIds로 구조화
  • @SerialName 직렬화 설정 정확

ReedService의 postRecord()editRecord() 메서드에서 v2 API 엔드포인트로 정상 사용 중이며, DefaultRecordRepository에서도 파라미터 매핑이 일관되게 처리되고 있습니다.

core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt (2)

17-26: V2 API 스펙에 맞게 잘 업데이트되었습니다.

postRecord 메서드가 nullable pageNumberprimaryEmotion, detailEmotionTagIds 파라미터를 사용하도록 변경되어 V2 요구사항을 충족하고 있습니다. 페이로드 구성도 올바르게 수정되었습니다.


41-50: V2 API 스펙에 맞게 일관되게 업데이트되었습니다.

editRecord 메서드도 postRecord와 동일한 방식으로 V2 API 스펙을 적용하여 일관성이 유지되고 있습니다.

core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/RecordRepository.kt (4)

7-14: postRecord 메서드가 V2 스펙에 맞게 업데이트되었습니다.

nullable pageNumberprimaryEmotion, detailEmotionTagIds 파라미터를 사용하며, ReadingRecordModelV2를 반환하도록 변경되어 V2 API 요구사항을 충족합니다.


23-25: getRecordDetail 메서드가 V2 모델을 반환하도록 업데이트되었습니다.

메서드 시그니처가 ReadingRecordModelV2를 반환하도록 변경되어 V2 API와 일관성을 유지합니다.


27-34: editRecord 메서드가 V2 스펙에 맞게 업데이트되었습니다.

postRecord와 동일한 파라미터 구조(nullable pageNumber, primaryEmotion, detailEmotionTagIds)를 사용하며 ReadingRecordModelV2를 반환하여 일관성이 유지됩니다.


16-21: [Your rewritten review comment text here]
[Exactly ONE classification tag]

core/model/src/main/kotlin/com/ninecraft/booket/core/model/ReadingRecordsModel.kt (2)

29-44: 기본값 설정은 UI 상태 관리 패턴으로 적절합니다.

ReadingRecordModelV2의 기본값 설정은 Compose 상태 관리의 표준 패턴이며, 실제 데이터는 API 응답으로부터 ResponseToModel.ktReadingRecordV2.toModel() 함수를 통해 적절히 매핑됩니다. 필수 필드(id, userBookId 등)는 네트워크 응답 단계에서 서버로부터 전달되어 모두 채워지므로 추가 검증이 필요하지 않습니다.

Likely an incorrect or invalid review comment.


46-50: EmotionCode enum이 제대로 정의되어 있으며, 기본값은 개발 단계의 폴백입니다.

EmotionCode enum은 core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt에 명확히 정의되어 있고 (WARMTH, JOY, SADNESS, INSIGHT, OTHER), 안전한 변환을 위한 fromCode() 함수도 포함되어 있습니다.

displayName의 기본값 "기타"는 개발 단계의 폴백이며, 실제 운영 시 데이터는 API 응답을 통해 매핑됩니다. ResponseToModel.kt의 mapper 함수에서 displayName = displayName (API 응답에서 옴)으로 설정되므로 하드코딩된 기본값은 빈 모델 생성 시에만 사용됩니다. 이는 프로젝트의 개발 패턴(API 준비 전 하드코딩, 준비 후 교체)과 일치합니다.

val review: String?,
@SerialName("emotionTags")
val emotionTags: List<String> = emptyList(),
val emotionTags: List<String>,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if V1 reading records API is still in use

# Search for V1 API endpoint usage
echo "=== Searching for V1 API endpoint usage ==="
rg -n "api/v1/reading-records" --type=kt

# Search for ReadingRecord (non-V2) model usage
echo -e "\n=== Searching for ReadingRecord (non-V2) model usage ==="
rg -n "ReadingRecord[^V2]" --type=kt

# Search for emotionTags usage in the codebase
echo -e "\n=== Searching for emotionTags field usage ==="
rg -n "emotionTags" --type=kt -A 2 -B 2

Repository: YAPP-Github/Reed-Android

Length of output: 314


🏁 Script executed:

#!/bin/bash
# Description: Check if V1 reading records API is still in use - corrected

# Search for V1 API endpoint usage in Kotlin files
echo "=== Searching for V1 API endpoint usage ==="
rg "api/v1/reading-records" -g "*.kt" 

# Search for ReadingRecord model usage
echo -e "\n=== Searching for ReadingRecord usage (non-V2) ==="
rg "ReadingRecord" -g "*.kt" | grep -v "ReadingRecordV2" | head -20

# Search for emotionTags field usage
echo -e "\n=== Searching for emotionTags field usage ==="
rg "emotionTags" -g "*.kt" -A 2 -B 2

# View ReadingRecordsResponse.kt full context
echo -e "\n=== Viewing ReadingRecordsResponse.kt ==="
find . -name "ReadingRecordsResponse.kt" -exec cat {} \;

Repository: YAPP-Github/Reed-Android

Length of output: 13424


emotionTags 필드에 기본값이 없으면 API 응답 누락 시 deserialization 오류가 발생할 수 있습니다.

emotionTags: List<String> (기본값 제거)는 JSON 응답에서 이 필드가 반드시 포함되어야 한다는 의미입니다. V1 API 엔드포인트(api/v1/reading-records/{userBookId})가 여전히 활성 상태인데, 만약 API 응답에서 emotionTags 필드를 누락하거나 선택 사항으로 취급한다면 deserialization이 실패합니다. 코어 모델 레이어(ReadingRecordsModel.kt)는 emotionTags: List<String> = emptyList()로 기본값을 유지하지만, 응답 모델에 기본값이 없으면 그 사이의 매핑에서 오류가 발생합니다.

백엔드 API 문서에서 V1 응답이 항상 emotionTags를 반환하는지 확인하거나, 필드를 선택 사항으로 처리해야 할 경우 val emotionTags: List<String> = emptyList()로 기본값을 추가하세요.

🤖 Prompt for AI Agents
In
@core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/ReadingRecordsResponse.kt
at line 33, The response model's emotionTags property currently has no default
and will cause deserialization failures if the API omits that field; update the
ReadingRecordsResponse data class to make emotionTags optional by adding a
default (e.g., val emotionTags: List<String> = emptyList()) so it safely maps to
the core ReadingRecordsModel which expects an emptyList by default and prevents
errors when the V1 API response omits emotionTags.

Comment on lines +48 to 84
@Serializable
data class ReadingRecordV2(
@SerialName("id")
val id: String,
@SerialName("userBookId")
val userBookId: String,
@SerialName("pageNumber")
val pageNumber: Int?,
@SerialName("quote")
val quote: String,
@SerialName("review")
val review: String?,
@SerialName("primaryEmotion")
val primaryEmotion: PrimaryEmotion,
@SerialName("detailEmotions")
val detailEmotions: List<DetailEmotion>,
@SerialName("createdAt")
val createdAt: String,
@SerialName("updatedAt")
val updatedAt: String,
@SerialName("bookTitle")
val bookTitle: String?,
@SerialName("bookPublisher")
val bookPublisher: String?,
@SerialName("bookCoverImageUrl")
val bookCoverImageUrl: String?,
@SerialName("author")
val author: String?,
)

@Serializable
data class PrimaryEmotion(
@SerialName("code")
val code: String,
@SerialName("displayName")
val displayName: String,
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Search for DetailEmotion in the ReadingRecordsResponse.kt file
rg -n "DetailEmotion" core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/ReadingRecordsResponse.kt

Repository: YAPP-Github/Reed-Android

Length of output: 115


🏁 Script executed:

# Check the full file to see all content after line 84
cat -n core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/ReadingRecordsResponse.kt

Repository: YAPP-Github/Reed-Android

Length of output: 2830


🏁 Script executed:

# Search for DetailEmotion definition across the codebase
rg -n "data class DetailEmotion|class DetailEmotion"

Repository: YAPP-Github/Reed-Android

Length of output: 431


🏁 Script executed:

# Also check for any imports of DetailEmotion in the current file
rg -n "import.*DetailEmotion" core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/ReadingRecordsResponse.kt

Repository: YAPP-Github/Reed-Android

Length of output: 50


🏁 Script executed:

# Check the DetailEmotion definition in EmotionGroupsResponse.kt
sed -n '20,40p' core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/EmotionGroupsResponse.kt

Repository: YAPP-Github/Reed-Android

Length of output: 200


🏁 Script executed:

# Also check if there are any imports needed in ReadingRecordsResponse.kt
rg -n "^import" core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/ReadingRecordsResponse.kt

Repository: YAPP-Github/Reed-Android

Length of output: 153


DetailEmotion 임포트가 누락되어 있습니다.

ReadingRecordV2와 PrimaryEmotion 모델은 올바르게 정의되었으나, 63번 라인에서 사용되는 DetailEmotion이 임포트되지 않았습니다. DetailEmotion은 EmotionGroupsResponse.kt에 정의되어 있으므로 다음 임포트를 추가해야 합니다:

import com.ninecraft.booket.core.network.response.DetailEmotion

이 임포트가 없으면 코드가 컴파일되지 않습니다.

🤖 Prompt for AI Agents
In
@core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/ReadingRecordsResponse.kt
around lines 48 - 84, ReadingRecordV2 references DetailEmotion but the type
isn't imported; add the missing import for DetailEmotion (defined in
EmotionGroupsResponse.kt) to the top of the file so ReadingRecordV2 and
PrimaryEmotion compile correctly—import
com.ninecraft.booket.core.network.response.DetailEmotion.

}
val hasChanges by remember {
derivedStateOf {
val pageChanged = recordPageState.text.toString() != recordInfo.pageNumber.toString()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

nullable pageNumber 비교 로직 검증 필요

recordPageState.text.toString()이 빈 문자열일 때 toIntOrNull()null을 반환하지만, recordInfo.pageNumber.toString()null일 때 "null" 문자열을 반환합니다. 이로 인해 페이지 번호가 입력되지 않은 경우(null) 변경 감지가 정확하지 않을 수 있습니다.

🔧 제안하는 수정 사항
-                val pageChanged = recordPageState.text.toString() != recordInfo.pageNumber.toString()
+                val pageChanged = recordPageState.text.toString().toIntOrNull() != recordInfo.pageNumber
📝 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
val pageChanged = recordPageState.text.toString() != recordInfo.pageNumber.toString()
val pageChanged = recordPageState.text.toString().toIntOrNull() != recordInfo.pageNumber

Comment on lines +213 to 221
is RecordRegisterUiEvent.OnSelectEmotionCode -> {
selectedEmotionCode = event.emotionCode

is RecordRegisterUiEvent.OnSelectEmotionV2 -> {
selectedEmotion = event.emotion
emotionDetails = getEmotionDetails(event.emotion).toPersistentList()
isEmotionDetailBottomSheetVisible = true
if (selectedEmotionCode == EmotionCode.OTHER) {
committedEmotionCode = selectedEmotionCode
} else {
isEmotionDetailBottomSheetVisible = true
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

OTHER 감정 선택 시 세부 감정 맵 초기화가 누락되었습니다.

EmotionCode.OTHER 선택 시 committedEmotionMapselectedEmotionMap을 초기화하지 않으면, 이전에 다른 감정에서 선택한 세부 감정이 남아있어 postRecord 호출 시 잘못된 데이터가 전송될 수 있습니다.

🐛 제안된 수정
 is RecordRegisterUiEvent.OnSelectEmotionCode -> {
     selectedEmotionCode = event.emotionCode

     if (selectedEmotionCode == EmotionCode.OTHER) {
         committedEmotionCode = selectedEmotionCode
+        committedEmotionMap = persistentMapOf()
+        selectedEmotionMap = persistentMapOf()
     } else {
         isEmotionDetailBottomSheetVisible = true
     }
 }
🤖 Prompt for AI Agents
In
@feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
around lines 213 - 221, When handling RecordRegisterUiEvent.OnSelectEmotionCode
in RecordRegisterPresenter you set selectedEmotionCode and committedEmotionCode
for EmotionCode.OTHER but forgot to clear committedEmotionMap and
selectedEmotionMap; update the branch for EmotionCode.OTHER to also reset
committedEmotionMap and selectedEmotionMap (clear or assign empty maps) so
leftover detail-emotion entries aren't sent when postRecord is called, leaving
the existing behavior for non-OTHER (setting isEmotionDetailBottomSheetVisible =
true) unchanged.

Comment on lines 235 to 242
is RecordRegisterUiEvent.OnEmotionDetailRemoved -> {
val emotionKey = selectedEmotion ?: return
val currentDetails = committedEmotionDetails[selectedEmotion].orEmpty()
val emotionKey = selectedEmotionCode ?: return
val currentDetails = committedEmotionMap[selectedEmotionCode].orEmpty()
val updatedDetails = currentDetails - event.detail

committedEmotionDetails = committedEmotionDetails + (emotionKey to updatedDetails.toPersistentList())
selectedEmotionDetails = selectedEmotionDetails + (emotionKey to updatedDetails.toPersistentList())
committedEmotionMap = committedEmotionMap + (emotionKey to updatedDetails.toPersistentList())
selectedEmotionMap = selectedEmotionMap + (emotionKey to updatedDetails.toPersistentList())
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

OnEmotionDetailRemoved에서 잠재적 로직 오류가 있습니다.

committedEmotionMap[selectedEmotionCode]에서 selectedEmotionCode를 키로 사용하고 있지만, 실제로는 committedEmotionCode를 사용해야 할 것 같습니다. 현재 구현에서는 사용자가 다른 감정을 선택한 후 이전에 커밋된 세부 감정을 제거하려고 할 때 잘못된 맵에서 조회할 수 있습니다.

🐛 제안된 수정
 is RecordRegisterUiEvent.OnEmotionDetailRemoved -> {
-    val emotionKey = selectedEmotionCode ?: return
-    val currentDetails = committedEmotionMap[selectedEmotionCode].orEmpty()
+    val emotionKey = committedEmotionCode ?: return
+    val currentDetails = committedEmotionMap[emotionKey].orEmpty()
     val updatedDetails = currentDetails - event.detail

     committedEmotionMap = committedEmotionMap + (emotionKey to updatedDetails.toPersistentList())
     selectedEmotionMap = selectedEmotionMap + (emotionKey to updatedDetails.toPersistentList())
 }
🤖 Prompt for AI Agents
In
@feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
around lines 235 - 242, In OnEmotionDetailRemoved the code reads from
committedEmotionMap using selectedEmotionCode but should use the
committedEmotionCode to remove details from the committed set; change the lookup
and subsequent updates to use committedEmotionCode (keep selectedEmotionMap
updates as appropriate) so you compute currentDetails =
committedEmotionMap[committedEmotionCode].orEmpty(), build updatedDetails, then
set committedEmotionMap = committedEmotionMap + (committedEmotionCode to
updatedDetails.toPersistentList()) and update selectedEmotionMap only if that
key matches the currently selectedEmotionCode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BOOK-482/feat] 독서 기록 플로우 API 스펙 변경 적용

2 participants