Conversation
package명 review -> record로 변경 BookStatus common 모듈로 이동, model 모듈은 android 플랫폼 의존성이 없는 관계로
Button Trailing Icon이 작아지는 문제를 해결해야함
Walkthrough도서 상세 화면의 UI가 대폭 구현되었습니다. 새로운 상태 관리, 다양한 컴포저블 UI 컴포넌트(예: 바텀시트, 감정 분석, 기록 아이템 등), 상태 및 이벤트 모델, 문자열 리소스 추가, 그리고 감정·도서 상태·정렬 기능이 반영되었습니다. 일부 공통 enum 및 모델, 불변 컬렉션 의존성, 패키지 리팩토링도 포함됩니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant BookDetailUi
participant BookDetailPresenter
participant BookUpdateBottomSheet
participant RecordSortBottomSheet
User->>BookDetailUi: 도서 상세 화면 진입
BookDetailUi->>BookDetailPresenter: present() 호출
BookDetailPresenter-->>BookDetailUi: UI 상태 반환
User->>BookDetailUi: 도서 상태 버튼 클릭
BookDetailUi->>BookUpdateBottomSheet: 바텀시트 표시
User->>BookUpdateBottomSheet: 상태 선택 및 변경하기 클릭
BookUpdateBottomSheet->>BookDetailUi: 상태 변경 이벤트 전달
BookDetailUi->>BookDetailPresenter: 상태 변경 처리
User->>BookDetailUi: 기록 정렬 버튼 클릭
BookDetailUi->>RecordSortBottomSheet: 바텀시트 표시
User->>RecordSortBottomSheet: 정렬 방식 선택
RecordSortBottomSheet->>BookDetailUi: 정렬 변경 이벤트 전달
BookDetailUi->>BookDetailPresenter: 정렬 방식 변경 처리
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 10
🔭 Outside diff range comments (1)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt (1)
50-50: 리소스 키review_detail_*→record_detail_*로 변경 필요RecordDetailUi.kt(…/feature/detail/record)에서 제목·라벨을
R.string.review_detail_*로 참조하고 있지만, 해당 기능은 “record(독서 기록)” 컨텍스트이므로 리소스 키를 일관되게 변경해야 합니다.수정 대상:
feature/detail/src/main/res/values/strings.xml
<string name="review_detail_title">…</string><string name="review_detail_quote_label">…</string><string name="review_detail_impression_label">…</string>
→ 각각record_detail_title,record_detail_quote_label,record_detail_impression_label로 변경feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt
- stringResource(R.string.review_detail_title)
- stringResource(R.string.review_detail_quote_label)
- stringResource(R.string.review_detail_impression_label)
→R.string.record_detail_*로 업데이트예시 diff:
--- a/feature/detail/src/main/res/values/strings.xml +++ b/feature/detail/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - <string name="review_detail_title">독서 기록</string> - <string name="review_detail_quote_label">수집한 문장</string> - <string name="review_detail_impression_label">감상평 기록</string> + <string name="record_detail_title">독서 기록</string> + <string name="record_detail_quote_label">수집한 문장</string> + <string name="record_detail_impression_label">감상평 기록</string> --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt @@ -50,7 +50,7 @@ // 기존 코드 - title = stringResource(R.string.review_detail_title), + title = stringResource(R.string.record_detail_title), // … - quoteLabel = stringResource(R.string.review_detail_quote_label), + quoteLabel = stringResource(R.string.record_detail_quote_label), - impressionLabel = stringResource(R.string.review_detail_impression_label) + impressionLabel = stringResource(R.string.record_detail_impression_label)
🧹 Nitpick comments (19)
feature/detail/build.gradle.kts (1)
19-20: 의존성 중복 여부 확인(선택 사항)
detail 모듈에도 동일한kotlinx-collections-immutable를 추가하셨는데, core 모듈에 이미 포함되어 있다면 Gradle configuration cache 측면에서 불필요한 중복이 될 수 있습니다. 멀티-플랫폼/멀티-모듈에서 동일 라이브러리를 여러 모듈에 선언해도 큰 문제는 없지만, 가능하면 공통 모듈에만 선언하여 의존성 트리를 단순화하는 것도 방법입니다.feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/SearchUi.kt (1)
26-27: toTypedArray() → toImmutableList() 직접 변환으로 불필요한 객체 생성 제거bookStatuses = BookStatus.entries.toTypedArray().toImmutableList()중간에 배열을 만들 필요 없이 다음처럼 바로 immutable 리스트로 변환 가능합니다.
-bookStatuses = BookStatus.entries.toTypedArray().toImmutableList() +bookStatuses = BookStatus.entries.toImmutableList()불변 리스트에서 요소 수가 많지 않더라도, Compose recomposition 시 객체 수 최소화를 위해 간단히 개선할 수 있습니다.
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/BookRegisterBottomSheet.kt (1)
94-104: 선택 항목에 Role 지정으로 접근성 개선
BookStatusItem에서.selectable(..., role = null)로 두셨는데, BottomSheet 내 라디오 버튼과 유사한 단일 선택 UX를 의도하신 것이라면role = Role.RadioButton(또는Role.Tab)을 지정해 주면 TalkBack/ScreenReader 사용자가 현재 선택 상태를 보다 명확히 인식할 수 있습니다.- .selectable( - selected = selected, - indication = null, - role = null, + .selectable( + selected = selected, + indication = null, + role = Role.RadioButton, interactionSource = remember { MutableInteractionSource() },작은 수정으로 접근성을 크게 높일 수 있으니 고려해 보시면 좋겠습니다.
core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/BookStatus.kt (2)
11-17: 메서드명 개선을 고려해보세요.현재
getDisplayNameRes()메서드는 string resource ID를 반환하는데, 메서드명이 이를 명확히 나타내지 못합니다.getDisplayNameResId()또는getStringRes()와 같이 더 명확한 이름을 사용하는 것을 고려해보세요.- fun getDisplayNameRes(): Int { + fun getDisplayNameResId(): Int {
19-23: companion object 이름 생략을 고려해보세요.Kotlin에서는 companion object의 기본 이름이
Companion이므로 명시적으로 지정할 필요가 없습니다.- companion object Companion { + companion object {feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt (1)
65-112: 이벤트 처리 로직이 잘 구현되었지만, 메서드 분리를 고려해보세요.각 이벤트에 대한 처리 로직이 적절하게 구현되어 있습니다. 하지만
handleEvent메서드가 상당히 길어졌으므로, 관련된 이벤트들을 그룹화하여 별도 함수로 분리하는 것을 고려해보세요.예를 들어, bottom sheet 관련 이벤트들을 분리할 수 있습니다:
private fun handleBottomSheetEvents(event: BookDetailUiEvent) { when (event) { is BookDetailUiEvent.OnBookUpdateBottomSheetDismiss -> { isBookUpdateBottomSheetVisible = false } is BookDetailUiEvent.OnRecordSortBottomSheetDismiss -> { isRecordSortBottomSheetVisible = false } // 기타 bottom sheet 관련 이벤트들 } }feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/CollectedSeed.kt (1)
90-100: 프리뷰에 샘플 데이터 추가 권장더 의미 있는 프리뷰를 위해 샘플 감정 데이터를 포함하는 것을 고려해보세요.
@ComponentPreview @Composable private fun CollectedSeedPreview() { ReedTheme { CollectedSeed( state = BookDetailUiState( eventSink = {}, + emotionList = persistentListOf( + EmotionModel(Emotion.WARM, 5), + EmotionModel(Emotion.EXCITING, 3), + EmotionModel(Emotion.TOUCHING, 2) + ) ), ) } }feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/SeedItem.kt (1)
26-64: 잘 구현된 감정 시드 아이템!SeedItem 컴포넌트가 깔끔하게 구현되었습니다:
- 감정별 색상 확장 함수 활용이 우수함
- 논리적인 레이아웃 구조와 적절한 간격 설정
- ReedTheme 일관성 있는 적용
이미지 크기의 일관성을 위해 하드코딩된 50.dp를 테마 스페이싱으로 교체하는 것을 고려해보세요:
ResourceImage( imageRes = designR.drawable.ic_placeholder, contentDescription = "Seed Graphic Image", - modifier = Modifier.size(50.dp), + modifier = Modifier.size(ReedTheme.spacing.spacing12), // 또는 적절한 테마 값 )feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordsCollection.kt (1)
49-71: 이벤트 처리 및 프리뷰 구현 양호다음 부분들이 잘 구현되었습니다:
toImmutableList()변환 적절히 사용OnRecordItemClick이벤트 처리가 올바름- 키 사용으로 LazyColumn 성능 최적화
프리뷰에 샘플 데이터를 추가하면 더 의미 있는 미리보기가 가능합니다:
private fun RecordsCollectionPreview() { ReedTheme { RecordsCollection( state = BookDetailUiState( eventSink = {}, + recordCollections = listOf( + // 샘플 RecordCollection 데이터 추가 + ) ), ) } }feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordSortBottomSheet.kt (2)
47-52: 불필요한 람다 래핑 제거
onDismissRequest람다를 단순히 다시 호출하는 것은 불필요합니다.- onDismissRequest = { - onDismissRequest() - }, + onDismissRequest = onDismissRequest,
72-78: 접근성 개선을 위해 IconButton 사용 권장Icon에 clickable modifier를 직접 사용하는 대신 IconButton을 사용하면 더 나은 접근성을 제공할 수 있습니다.
- Icon( - imageVector = ImageVector.vectorResource(designR.drawable.ic_close), - contentDescription = "Close Icon", - modifier = Modifier.clickable { - onCloseButtonClick() - }, - ) + IconButton(onClick = onCloseButtonClick) { + Icon( + imageVector = ImageVector.vectorResource(designR.drawable.ic_close), + contentDescription = "닫기", + ) + }추가로 필요한 import:
import androidx.compose.material3.IconButtonfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/BookUpdateBottomSheet.kt (3)
55-59: 불필요한 람다 래핑 제거
onDismissRequest람다를 단순히 다시 호출하는 것은 불필요합니다.- onDismissRequest = { - onDismissRequest() - }, + onDismissRequest = onDismissRequest,
80-86: 접근성 개선을 위해 IconButton 사용 권장Icon에 clickable modifier를 직접 사용하는 대신 IconButton을 사용하면 더 나은 접근성을 제공할 수 있습니다.
- Icon( - imageVector = ImageVector.vectorResource(designR.drawable.ic_close), - contentDescription = "Close Icon", - modifier = Modifier.clickable { - onCloseButtonClick() - }, - ) + IconButton(onClick = onCloseButtonClick) { + Icon( + imageVector = ImageVector.vectorResource(designR.drawable.ic_close), + contentDescription = "닫기", + ) + }추가로 필요한 import:
import androidx.compose.material3.IconButton
106-107: 중복된 Spacer 통합연속된 두 개의 Spacer를 하나로 통합할 수 있습니다.
- Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing3)) - Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing4)) + Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing3 + ReedTheme.spacing.spacing4))feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt (1)
62-68: TODO: 실제 감정 그래픽 구현 필요현재 placeholder 이미지를 사용하고 있습니다. 실제 감정 그래픽이 준비되면 교체가 필요합니다.
감정 타입에 따른 실제 이미지 리소스가 준비되면 구현을 도와드릴 수 있습니다. 이 작업을 추적하기 위한 이슈를 생성할까요?
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt (1)
193-194: 중복된 Spacer 통합연속된 두 개의 Spacer를 하나로 통합할 수 있습니다.
- Spacer(Modifier.height(ReedTheme.spacing.spacing3)) - Spacer(Modifier.height(ReedTheme.spacing.spacing4)) + Spacer(Modifier.height(ReedTheme.spacing.spacing3 + ReedTheme.spacing.spacing4))feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt (1)
17-62: UI State에 샘플 데이터가 하드코딩됨
BookDetailUiState의 기본값으로 샘플 데이터가 포함되어 있습니다. 실제 운영 환경에서는 빈 리스트로 초기화하는 것이 더 적절할 수 있습니다.PR 설명에 따르면 API가 준비되지 않아 임시로 구현한 것으로 보입니다. TODO 주석을 추가하여 나중에 제거할 부분임을 명확히 하는 것이 좋겠습니다:
data class BookDetailUiState( val isBookUpdateBottomSheetVisible: Boolean = false, val isRecordSortBottomSheetVisible: Boolean = false, + // TODO: API 연동 시 빈 리스트로 초기화하도록 수정 val emotionList: ImmutableList<EmotionModel> = persistentListOf(feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewBox.kt (1)
1-1: 패키지 변경과 함께 컴포넌트명도 일관성 있게 변경 고려패키지가
record.component로 변경되었지만 컴포넌트명은 여전히ReviewBox입니다. 전체적인 리팩토링의 일관성을 위해RecordBox또는RecordReviewBox로 이름 변경을 고려해보세요.feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt (1)
57-57: 함수명 일관성을 위해 수정이 필요합니다.
ReviewDetailContent함수명이RecordDetailContent로 변경되지 않았습니다. 일관된 네이밍을 위해 수정해주세요.- ReviewDetailContent(modifier) + RecordDetailContent(modifier)그리고 함수 정의도 함께 변경:
-private fun ReviewDetailContent(modifier: Modifier = Modifier) { +private fun RecordDetailContent(modifier: Modifier = Modifier) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (37)
core/common/build.gradle.kts(1 hunks)core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/BookStatus.kt(1 hunks)core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Emotion.kt(1 hunks)core/common/src/main/kotlin/com/ninecraft/booket/core/common/util/EmotionAnalyzer.kt(1 hunks)core/common/src/main/kotlin/com/ninecraft/booket/core/common/util/EmotionTextBuilder.kt(1 hunks)core/common/src/main/res/values/strings.xml(1 hunks)core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt(1 hunks)core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkModule.kt(1 hunks)feature/detail/build.gradle.kts(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt(3 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt(2 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/BookUpdateBottomSheet.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/CollectedSeed.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordSortBottomSheet.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordsCollection.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordsCollectionHeader.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/SeedItem.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/HandleRecordDetailSideEffect.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt(2 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt(4 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUiState.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/QuoteBox.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewBox.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/review/ReviewDetailUiState.kt(0 hunks)feature/detail/src/main/res/values/strings.xml(1 hunks)feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/component/LibraryBookItem.kt(1 hunks)feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt(2 hunks)feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt(1 hunks)feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUiState.kt(1 hunks)feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt(1 hunks)feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/SearchPresenter.kt(1 hunks)feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/SearchUi.kt(1 hunks)feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/SearchUiState.kt(1 hunks)feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/BookRegisterBottomSheet.kt(1 hunks)feature/search/src/main/res/values/strings.xml(0 hunks)
💤 Files with no reviewable changes (2)
- feature/search/src/main/res/values/strings.xml
- feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/review/ReviewDetailUiState.kt
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: reed-android 프로젝트에서는 `booket.android.feature` convention plugin을 사용하여 feature 모듈들의 공통 의존성을 관리한다. 이 p...
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#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/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewBox.ktfeature/detail/build.gradle.ktsfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/QuoteBox.ktfeature/search/src/main/kotlin/com/ninecraft/booket/feature/search/SearchUi.ktcore/common/build.gradle.ktsfeature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/BookRegisterBottomSheet.ktfeature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
📚 Learning: the project uses kotlinx serialization with json configuration that includes `ignoreunknownkeys = tr...
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:33-44
Timestamp: 2025-07-14T00:51:38.952Z
Learning: The project uses Kotlinx Serialization with JSON configuration that includes `ignoreUnknownKeys = true`, `encodeDefaults = true`, `isLenient = true`, and `prettyPrint = true` to handle API response parsing robustly, making additional null safety measures less critical if the API consistently provides all expected fields.
Applied to files:
feature/detail/build.gradle.ktscore/common/build.gradle.ktscore/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkModule.kt
📚 Learning: kotlin에서 같은 패키지 내의 파일들은 패키지 레벨에 정의된 const val 상수를 import 없이 직접 접근할 수 있습니다. onboardingpresenter.kt와 o...
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#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/search/src/main/kotlin/com/ninecraft/booket/feature/search/SearchUi.ktfeature/search/src/main/kotlin/com/ninecraft/booket/feature/search/SearchPresenter.ktfeature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/BookRegisterBottomSheet.ktfeature/search/src/main/kotlin/com/ninecraft/booket/feature/search/SearchUiState.ktfeature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.ktcore/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/BookStatus.kt
📚 Learning: in the reed-android project's termsagreementscreen.kt, the ontermdetailclick event is intentionally ...
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.
Applied to files:
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.ktfeature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.ktfeature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
📚 Learning: reed android 프로젝트에서 타이포그래피 사용 규칙: 톱 앱바(top app bar)에서는 `headline2semibold`를 사용하고, 바텀시트(bottom sheet)...
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#45
File: core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/appbar/ReedTopAppBar.kt:65-65
Timestamp: 2025-07-12T01:33:57.101Z
Learning: Reed Android 프로젝트에서 타이포그래피 사용 규칙: 톱 앱바(Top App Bar)에서는 `headline2SemiBold`를 사용하고, 바텀시트(Bottom Sheet)에서는 `heading2SemiBold`를 사용한다. 이는 의도적인 디자인 시스템 차별화이다.
Applied to files:
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/BookRegisterBottomSheet.kt
📚 Learning: in the reed-android project, delegatingnavigator was removed because it caused confusion and added u...
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#52
File: feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/screens/DelegatingNavigator.kt:0-0
Timestamp: 2025-07-16T06:08:40.883Z
Learning: In the Reed-Android project, DelegatingNavigator was removed because it caused confusion and added unnecessary complexity. The simplified approach uses childNavigator only within bottom navigation screens and rootNavigator for all other navigation, which is more intuitive and maintainable.
Applied to files:
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.ktfeature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
📚 Learning: in the reed-android project using circuit architecture, delegatingnavigator was restored because nav...
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#52
File: feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/screens/DelegatingNavigator.kt:0-0
Timestamp: 2025-07-16T15:54:19.322Z
Learning: In the Reed-Android project using Circuit architecture, DelegatingNavigator was restored because NavigableCircuitContent can only inject a single navigator, but the dual-navigator architecture (childNavigator for bottom navigation screens, rootNavigator for full-screen screens) requires a delegating component to work within Circuit's constraint.
Applied to files:
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
📚 Learning: 사용자는 kotlinx.serialization에서 json { ignoreunknownkeys = true, islenient = true, encodedefaults = tru...
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:7-30
Timestamp: 2025-07-14T00:52:03.723Z
Learning: 사용자는 kotlinx.serialization에서 Json { ignoreUnknownKeys = true, isLenient = true, encodeDefaults = true, prettyPrint = true } 설정을 사용하여 API 응답의 견고성을 높이는 접근 방식을 취하고 있다.
Applied to files:
core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkModule.kt
🧬 Code Graph Analysis (6)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/BookUpdateBottomSheet.kt (2)
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/button/ReedButton.kt (1)
ReedButton(33-107)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/util/EmotionTextBuilder.kt (1)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/util/EmotionAnalyzer.kt (1)
analyzeEmotions(16-31)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordsCollection.kt (3)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordsCollectionHeader.kt (1)
RecordsCollectionHeader(23-62)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt (1)
RecordItem(29-90)core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/theme/Theme.kt (1)
ReedTheme(14-21)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt (1)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/HandleRecordDetailSideEffect.kt (1)
HandleRecordDetailSideEffects(8-23)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/SeedItem.kt (2)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/ResourceImage.kt (1)
ResourceImage(18-40)core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/theme/Theme.kt (1)
ReedTheme(14-21)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt (2)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/ResourceImage.kt (1)
ResourceImage(18-40)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). (1)
- GitHub Check: ci-build
🔇 Additional comments (24)
core/common/build.gradle.kts (1)
19-20: 불변 컬렉션 의존성 alias 정의 여부 한 번 더 확인해 주세요
libs.kotlinx.collections.immutable을 core 공통 모듈에 추가한 결정은 👍입니다. 다만libs.versions.toml또는 버전 카탈로그에 해당 alias 가 실제로 선언되어 있는지, 그리고 동일한 의존성이 이미 상위 모듈(예: feature/detail)을 통해 transitively 포함되어 중복되지 않는지 한번 더 검증해 주시면 좋겠습니다.feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/SearchPresenter.kt (1)
11-11: 중앙화된 BookStatus import 👍 – API 매핑 값 재확인 필요
BookStatus를 공통 상수로 통일한 점 훌륭합니다. 다만upsertBook()호출 시bookStatus.value문자열을 넘기는데, value 프로퍼티가 실제 API 스펙과 1:1 매핑되는지(예: 서버 enum 이름/코드) 다시 한번 확인해 주세요.feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/component/LibraryBookItem.kt (1)
102-102: 기존toString()→ 문자열 보간으로 변경 OK표현식만 변경되었고 기능·퍼포먼스 영향이 없습니다.
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/SearchUiState.kt (1)
4-4: BookStatus 전역 모듈로 이동 확인 완료전체 프로젝트 검색 결과,
enum class BookStatus선언은 아래 한 곳에만 존재하여 로컬 중복 선언이 없습니다.
– core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/BookStatus.kt따라서 추가 조치 없이 변경을 승인합니다.
core/common/src/main/res/values/strings.xml (1)
1-8: 공통 String 리소스 추가 적절중복 제거 및 다국어 대응 준비 측면에서 바람직합니다.
향후 다국어 번역 시 values-ko/values-en 등으로 나눠 관리하면 좋겠습니다.core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkModule.kt (1)
27-34: 주석 보강으로 직렬화 규칙 가독성 향상동작 변경 없이 의도 설명이 명확해졌습니다. 👍
core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/BookStatus.kt (1)
5-9: 잘 구현된 enum 정의입니다.도서 상태를 명확하게 표현하는 enum 정의가 잘 되어 있고, 각 상수의 value와 이름이 일치하여 가독성이 좋습니다.
feature/detail/src/main/res/values/strings.xml (1)
6-10: 새로운 문자열 리소스가 잘 추가되었습니다.도서 상세 화면의 새로운 기능들을 위한 문자열 리소스들이 적절하게 추가되었고, 리소스 키 네이밍과 한국어 번역이 자연스럽습니다.
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt (3)
36-39: 상태 관리가 잘 구현되었습니다.bottom sheet 가시성과 현재 선택된 값들을 관리하는 상태 변수들이
rememberRetained를 사용하여 적절하게 정의되었습니다.
42-63: API 호출과 에러 핸들링이 잘 구현되었습니다.
upsertBook함수에서 API 호출, success/failure 케이스 처리, 그리고handleException을 통한 일관된 에러 핸들링이 잘 구현되어 있습니다.
114-122: UI 상태 반환이 적절합니다.새로 추가된 상태 프로퍼티들이
BookDetailUiState에 올바르게 전달되고 있습니다.feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/CollectedSeed.kt (1)
27-88: 깔끔한 구현과 일관성 있는 테마 적용!CollectedSeed 컴포넌트가 잘 구현되었습니다:
- ReedTheme을 일관성 있게 사용하여 색상, 간격, 타이포그래피 적용
- 논리적인 레이아웃 구조 (제목 → SeedItem 목록 → 감정 텍스트 박스)
- buildEmotionText 유틸리티와의 깔끔한 통합
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/SeedItem.kt (1)
66-77: 의미 있는 샘플 데이터가 포함된 좋은 프리뷰!EmotionModel 샘플 데이터를 포함하여 컴포넌트의 실제 모습을 잘 보여주는 프리뷰입니다.
core/common/src/main/kotlin/com/ninecraft/booket/core/common/util/EmotionTextBuilder.kt (1)
14-68: 감정 텍스트 빌더의 우수한 구현!buildEmotionText 함수가 매우 잘 구현되었습니다:
remember를 사용한 성능 최적화- 세 가지 감정 표시 타입에 대한 적절한 처리
- 자연스러운 한국어 텍스트와 일관성 있는 스타일링
- ImmutableList 사용으로 타입 안전성 확보
DUAL 케이스의 쉼표 처리 로직도 정확합니다.
core/common/src/main/kotlin/com/ninecraft/booket/core/common/util/EmotionAnalyzer.kt (2)
5-14: 명확한 데이터 구조 설계!EmotionAnalysisResult와 EmotionDisplayType이 잘 설계되었습니다:
- 명확한 역할 분담과 간결한 구조
- 한국어 주석으로 카테고리 분류 로직이 명확함
16-31: 올바른 감정 분석 로직!analyzeEmotions 함수가 정확하게 구현되었습니다:
- 빈 리스트 예외 케이스 적절히 처리
- 최고 카운트 감정들을 올바르게 필터링
- 감정 개수에 따른 표시 타입 분류 로직이 정확함
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/QuoteBox.kt (1)
1-1: 패키지 변경 적절함
QuoteBox컴포넌트의 패키지 변경이 적절하며, 컴포넌트명도 record 컨텍스트에서 여전히 의미가 명확합니다.feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt (1)
110-110: 빈 문자열 recordId 전달 확인 필요
OnRecordSavedDialogConfirm이벤트에 빈 문자열을 전달하고 있습니다. 이는 실제 recordId가 없는 상태로 레코드 상세 화면으로 네비게이션할 때 문제가 될 수 있습니다. API 응답에서 받은 실제 recordId를 전달하도록 수정이 필요한지 확인해주세요.feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt (1)
18-18: import 변경 적절함
ReviewDetailScreen에서RecordDetailScreen으로의 import 변경이 리팩토링과 일치합니다.feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUiState.kt (1)
48-48: 이벤트 시그니처 변경 적절함
OnRecordSavedDialogConfirm을 data object에서recordId파라미터를 가진 data class로 변경한 것이 적절합니다. 이를 통해 저장된 레코드의 ID를 전달하여 상세 화면으로 올바른 네비게이션이 가능합니다.feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt (1)
45-45: 매개변수화된 네비게이션으로의 개선이 적절합니다.
ReviewDetailScreen에서RecordDetailScreen으로의 변경과recordId매개변수 추가는 특정 레코드에 대한 네비게이션을 가능하게 하는 좋은 설계입니다. 싱글톤 객체에서 매개변수를 받는 데이터 클래스로의 변경이 적절합니다.feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/HandleRecordDetailSideEffect.kt (1)
1-23: 일관된 리팩토링으로 잘 변경되었습니다.Review에서 Record로의 패키지 및 타입 변경이 일관되게 적용되었습니다. 사이드 이펙트 처리 로직은 그대로 유지하면서 타입만 적절히 변경되었습니다.
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUiState.kt (1)
1-22: Circuit 아키텍처 패턴을 잘 따른 UI 상태 정의입니다.UI 상태, 사이드 이펙트, 이벤트의 구조가 깔끔하게 정의되었습니다.
ShowToast에서 UUID를 사용한 키 생성은 사이드 이펙트 중복 제거를 위한 좋은 패턴입니다.OnCloseClicked이벤트 네이밍도 적절합니다.feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt (1)
77-124: 초기 UI 구현을 위한 하드코딩된 데이터가 적절합니다.도서 제목("여름은 오래 그곳에 남아"), 저자("미쓰이에 마사시"), 출판사("비채") 등의 하드코딩된 샘플 데이터는 API 연동 전 UI 구현 단계에서 적절한 접근입니다. PR 목표와 일치합니다.
| enum class Emotion( | ||
| val displayName: String, | ||
| ) { | ||
| WARM("따뜻함"), | ||
| JOY("즐거움"), | ||
| TENSION("긴장감"), | ||
| SADNESS("슬픔"), | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
모델 계층에 하드코딩된 한글 문자열 → 리소스화 필요
Emotion.displayName을 직접 한글로 보관하면 다국어 지원이 불가능합니다.
모델에는 @StringRes ID(또는 별도 매핑 함수)만 보관하고, UI 계층에서 stringResource()로 해석하도록 바꾸는 편이 좋습니다.
-enum class Emotion(
- val displayName: String,
-) {
- WARM("따뜻함"),
- JOY("즐거움"),
- TENSION("긴장감"),
- SADNESS("슬픔"),
-}
+enum class Emotion(@StringRes val labelResId: Int) {
+ WARM(R.string.emotion_warm),
+ JOY(R.string.emotion_joy),
+ TENSION(R.string.emotion_tension),
+ SADNESS(R.string.emotion_sadness),
+}리소스 파일(core/common/.../strings.xml)에 emotion_warm 등 키를 추가해 주세요.
📝 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.
| enum class Emotion( | |
| val displayName: String, | |
| ) { | |
| WARM("따뜻함"), | |
| JOY("즐거움"), | |
| TENSION("긴장감"), | |
| SADNESS("슬픔"), | |
| } | |
| import androidx.annotation.StringRes | |
| enum class Emotion(@StringRes val labelResId: Int) { | |
| WARM(R.string.emotion_warm), | |
| JOY(R.string.emotion_joy), | |
| TENSION(R.string.emotion_tension), | |
| SADNESS(R.string.emotion_sadness), | |
| } |
🤖 Prompt for AI Agents
In core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt
around lines 11 to 18, the Emotion enum currently hardcodes Korean strings in
displayName, which prevents multilingual support. Remove the direct Korean
strings and instead store string resource IDs (e.g., @StringRes Int) in the
enum. Then, add corresponding keys like emotion_warm, emotion_joy, etc., to the
strings.xml resource file under core/common. Update the UI layer to resolve
these resource IDs using stringResource() or equivalent for proper localization.
There was a problem hiding this comment.
core:model 은 kotlin 모듈이라 R 참조 불가능, 그리고 일단은 다국어 지원 예정은 없어
There was a problem hiding this comment.
@easyhooon 말씀하신대로 맞습니다! core:model이 순수 Kotlin 모듈이라면 Android 리소스 접근이 불가능하고, 다국어 지원 계획이 없다면 현재 구조가 적절하네요. 제안을 철회하겠습니다.
✏️ Learnings added
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt:11-18
Timestamp: 2025-07-31T16:58:59.378Z
Learning: Reed-Android 프로젝트에서 core:model 모듈은 순수 Kotlin 모듈이므로 Android 리소스(R.string 등)에 접근할 수 없다.
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt:11-18
Timestamp: 2025-07-31T16:58:59.378Z
Learning: Reed-Android 프로젝트는 현재 다국어 지원 계획이 없어서 모델에 한글 문자열을 직접 포함하는 것이 허용된다.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#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)만 별도로 선언하면 된다.
| NetworkImage( | ||
| imageUrl = "", | ||
| contentDescription = "Book CoverImage", | ||
| modifier = Modifier | ||
| .padding(end = ReedTheme.spacing.spacing4) | ||
| .width(70.dp) | ||
| .height(99.dp) | ||
| .clip(RoundedCornerShape(size = ReedTheme.radius.xs)), | ||
| placeholder = painterResource(designR.drawable.ic_placeholder), | ||
| ) | ||
| Column(modifier = Modifier.weight(1f)) { | ||
| Text( | ||
| text = "여름은 오래 그곳에 남아", | ||
| color = ReedTheme.colors.contentPrimary, | ||
| overflow = TextOverflow.Ellipsis, | ||
| maxLines = 2, | ||
| style = ReedTheme.typography.headline1SemiBold, | ||
| ) | ||
| ReedButton( | ||
| onClick = { | ||
| state.eventSink(BookDetailUiEvent.OnReadingClick) | ||
| }, | ||
| sizeStyle = largeButtonStyle, | ||
| colorStyle = ReedButtonColorStyle.PRIMARY, | ||
| text = "읽는 중", | ||
| ) | ||
| ReedButton( | ||
| onClick = { | ||
| state.eventSink(BookDetailUiEvent.OnCompletedClick) | ||
| }, | ||
| sizeStyle = largeButtonStyle, | ||
| colorStyle = ReedButtonColorStyle.PRIMARY, | ||
| text = "독서 완료", | ||
| Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing2)) | ||
| Row( | ||
| modifier = Modifier.fillMaxWidth(), | ||
| verticalAlignment = Alignment.CenterVertically, | ||
| ) { | ||
| Text( | ||
| text = "미쓰이에 마사시", | ||
| color = ReedTheme.colors.contentTertiary, | ||
| overflow = TextOverflow.Ellipsis, | ||
| maxLines = 1, | ||
| style = ReedTheme.typography.label2Regular, | ||
| modifier = Modifier.weight(0.7f, fill = false), | ||
| ) | ||
| Spacer(Modifier.width(ReedTheme.spacing.spacing1)) | ||
| VerticalDivider( | ||
| modifier = Modifier.height(14.dp), | ||
| thickness = 1.dp, | ||
| color = ReedTheme.colors.contentTertiary, | ||
| ) | ||
| Spacer(Modifier.width(ReedTheme.spacing.spacing1)) | ||
| Text( | ||
| text = "비채", | ||
| color = ReedTheme.colors.contentTertiary, | ||
| overflow = TextOverflow.Ellipsis, | ||
| maxLines = 1, | ||
| style = ReedTheme.typography.label2Regular, | ||
| modifier = Modifier.weight(0.3f, fill = false), | ||
| ) | ||
| } | ||
| Spacer(Modifier.width(ReedTheme.spacing.spacing05)) | ||
| Text( | ||
| text = "2024년", | ||
| color = ReedTheme.colors.contentTertiary, | ||
| overflow = TextOverflow.Ellipsis, | ||
| maxLines = 1, | ||
| style = ReedTheme.typography.label2Regular, | ||
| ) |
There was a problem hiding this comment.
하드코딩된 도서 정보 사용
도서 제목, 저자, 출판사, 연도 등이 하드코딩되어 있습니다. 또한 도서 상태 버튼의 텍스트도 "읽는 중"으로 하드코딩되어 있습니다. 이러한 정보는 state에서 가져와야 합니다.
PR 설명에서 API가 아직 준비되지 않아 임시로 구현했다고 명시되어 있으므로, TODO 주석을 추가하여 나중에 수정할 부분임을 명확히 하는 것이 좋겠습니다:
+ // TODO: API 연동 시 state에서 도서 정보를 가져오도록 수정
NetworkImage(
- imageUrl = "",
+ imageUrl = "", // TODO: state.book.coverImageUrl
contentDescription = "Book CoverImage",+ // TODO: state.book.title
Text(
text = "여름은 오래 그곳에 남아",+ // TODO: state.currentBookStatus?.getDisplayNameRes() ?: "상태 없음"
text = "읽는 중",Also applies to: 204-204
🤖 Prompt for AI Agents
In
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
around lines 136 to 190 and line 204, the book information such as title,
author, publisher, year, and the reading status button text are hardcoded.
Replace these hardcoded strings with values sourced from the component's state
or view model to reflect dynamic data. Additionally, add TODO comments near
these placeholders to clearly indicate that these are temporary implementations
pending API integration.
| @Suppress("unused") | ||
| @Composable | ||
| internal fun RecordItem( | ||
| quote: String, | ||
| emotionTags: ImmutableList<String>, | ||
| pageNumber: Int, | ||
| createdAt: String, | ||
| modifier: Modifier = Modifier, | ||
| ) { |
There was a problem hiding this comment.
사용하지 않는 매개변수와 하드코딩된 텍스트
emotionTags 매개변수가 전달되지만 사용되지 않고, 대신 하드코딩된 "#감동·공감" 텍스트가 표시됩니다. @Suppress("unused") 주석도 이를 나타냅니다.
-@Suppress("unused")
@Composable
internal fun RecordItem(
quote: String,
emotionTags: ImmutableList<String>,
pageNumber: Int,
createdAt: String,
modifier: Modifier = Modifier,
) {그리고 실제 emotionTags를 사용하도록 수정:
- Text(
- text = "#감동·공감",
- color = ReedTheme.colors.contentBrand,
- style = ReedTheme.typography.label1SemiBold,
- )
+ Text(
+ text = emotionTags.joinToString(separator = "·") { "#$it" },
+ color = ReedTheme.colors.contentBrand,
+ style = ReedTheme.typography.label1SemiBold,
+ )Also applies to: 72-75
🤖 Prompt for AI Agents
In
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt
around lines 29 to 37 and also lines 72 to 75, the emotionTags parameter is
passed but not used, and instead a hardcoded string "#감동·공감" is displayed.
Remove the @Suppress("unused") annotation and replace the hardcoded text with
code that dynamically displays the emotionTags content, ensuring the UI reflects
the actual passed emotion tags.
There was a problem hiding this comment.
이것도 API 연동할때 text = emotionTags.joinToString(separator = "·") { "#$it" }, 적용하는걸로 할게
There was a problem hiding this comment.
@easyhooon 네, 이해했습니다! API 연동 시점에 emotionTags.joinToString(separator = "·") { "#$it" }로 적용하시는 것이 좋겠네요. 현재는 UI 구현이 우선이고 API가 준비되면 실제 데이터를 연결하는 방식이 합리적입니다.
✏️ Learnings added
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt:29-37
Timestamp: 2025-07-31T23:17:40.025Z
Learning: Reed-Android 프로젝트에서는 API가 준비되지 않은 상황에서 UI를 먼저 구현하고, API 연동 시점에 하드코딩된 데이터를 실제 데이터로 교체하는 개발 방식을 사용한다. RecordItem 컴포넌트의 emotionTags 매개변수도 API 연동 시점에 `text = emotionTags.joinToString(separator = "·") { "#$it" }`로 적용될 예정이다.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt:11-18
Timestamp: 2025-07-31T16:58:59.404Z
Learning: Reed-Android 프로젝트는 현재 다국어 지원 계획이 없어서 모델에 한글 문자열을 직접 포함하는 것이 허용된다.
| @Composable | ||
| internal fun RecordsCollectionHeader( | ||
| state: BookDetailUiState, | ||
| modifier: Modifier = Modifier, | ||
| ) { | ||
| Row( | ||
| modifier = modifier.fillMaxWidth(), | ||
| verticalAlignment = Alignment.CenterVertically, | ||
| horizontalArrangement = Arrangement.SpaceBetween, | ||
| ) { | ||
| Row { | ||
| Text( | ||
| text = stringResource(R.string.record_collection), | ||
| color = ReedTheme.colors.contentPrimary, | ||
| style = ReedTheme.typography.headline2SemiBold, | ||
| ) | ||
| Spacer(modifier = Modifier.width(ReedTheme.spacing.spacing1)) | ||
| Text( | ||
| text = "${state.recordCollections.size}", | ||
| color = ReedTheme.colors.contentBrand, | ||
| style = ReedTheme.typography.headline2SemiBold, | ||
| ) | ||
| } | ||
| Row( | ||
| modifier = Modifier.clickable { | ||
| state.eventSink(BookDetailUiEvent.OnRecordSortButtonClick) | ||
| }, | ||
| ) { | ||
| Text( | ||
| text = stringResource(state.currentRecordSort.getDisplayNameRes()), | ||
| color = ReedTheme.colors.contentSecondary, | ||
| style = ReedTheme.typography.label1Medium, | ||
| ) | ||
| Icon( | ||
| imageVector = ImageVector.vectorResource(designR.drawable.ic_chevron_down), | ||
| contentDescription = "Dropdown Icon", | ||
| ) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
UI 구현이 잘 되어 있지만 몇 가지 개선사항이 있습니다.
전반적으로 잘 구현된 composable이지만 다음 사항들을 개선할 수 있습니다:
contentDescription이 하드코딩되어 있어 다국어 지원에 문제가 있습니다.Icon에tint속성이 설정되지 않아 테마 색상을 따르지 않을 수 있습니다.
Icon(
imageVector = ImageVector.vectorResource(designR.drawable.ic_chevron_down),
- contentDescription = "Dropdown Icon",
+ contentDescription = stringResource(R.string.dropdown_icon_description), // strings.xml에 추가 필요
+ tint = ReedTheme.colors.contentSecondary,
)🤖 Prompt for AI Agents
In
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordsCollectionHeader.kt
between lines 23 and 62, the Icon's contentDescription is hardcoded and the tint
attribute is missing. To fix this, replace the hardcoded contentDescription with
a string resource for localization support, and add a tint property to the Icon
using the appropriate theme color to ensure it follows the app's color scheme.
There was a problem hiding this comment.
@easyhooon 네, 맞습니다! 학습된 내용을 보니 Reed Android 프로젝트에서는 아이콘의 tint 속성을 Color.Unspecified로 설정하여 아이콘 고유의 색상을 보존하는 정책을 사용하고 계시는군요. 제가 tint 속성 추가를 제안한 것은 부적절했습니다.
그렇다면 contentDescription의 하드코딩 이슈만 남아있는 것 같습니다. 다국어 지원을 위해 "Dropdown Icon" 대신 string resource를 사용하는 것을 고려해보시겠어요?
contentDescription = stringResource(R.string.dropdown_icon_description)🧠 Learnings used
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordsCollection.kt:25-59
Timestamp: 2025-07-31T23:22:02.687Z
Learning: BookDetailScreen의 RecordsCollection 컴포넌트는 상위 Column의 verticalScroll과 LazyColumn의 무한 스크롤을 동시에 지원해야 하는 중첩 스크롤 시나리오입니다. LazyColumn에 고정 높이를 설정하지 않으면 앱이 충돌하는 이슈가 있어서, 현재는 높이를 계산하여 설정하고 userScrollEnabled = false로 구현되어 있습니다. 향후 InfiniteLazyColumn과 nestedScrollConnection을 도입하여 전체 화면 스크롤 + LazyColumn 무한 스크롤을 지원할 예정입니다.
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#55
File: core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/ReedTextField.kt:0-0
Timestamp: 2025-07-17T23:43:38.687Z
Learning: Reed Android 프로젝트에서 아이콘의 tint 속성을 Color.Unspecified로 설정하는 이유는 theme 색상이 아이콘에 적용되어 아이콘 고유의 색상이 변경되는 것을 방지하기 위함이다. 이는 아이콘의 원본 색상을 보존하려는 디자인 시스템 정책과 일치한다.
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#45
File: core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/appbar/ReedTopAppBar.kt:65-65
Timestamp: 2025-07-12T01:33:57.101Z
Learning: Reed Android 프로젝트에서 타이포그래피 사용 규칙: 톱 앱바(Top App Bar)에서는 `headline2SemiBold`를 사용하고, 바텀시트(Bottom Sheet)에서는 `heading2SemiBold`를 사용한다. 이는 의도적인 디자인 시스템 차별화이다.
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt:29-37
Timestamp: 2025-07-31T23:17:40.025Z
Learning: Reed-Android 프로젝트에서는 API가 준비되지 않은 상황에서 UI를 먼저 구현하고, API 연동 시점에 하드코딩된 데이터를 실제 데이터로 교체하는 개발 방식을 사용한다. RecordItem 컴포넌트의 emotionTags 매개변수도 API 연동 시점에 `text = emotionTags.joinToString(separator = "·") { "#$it" }`로 적용될 예정이다.
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#55
File: core/designsystem/src/main/res/drawable/ic_x_circle.xml:6-11
Timestamp: 2025-07-17T23:41:33.929Z
Learning: Reed Android 프로젝트에서는 SVG에서 vector drawable로 변환시 색상 매핑 대신 모드별 별도 아이콘 파일을 사용하는 방식을 선호한다. Figma에서 SVG export한 형태를 그대로 사용하며, 다크모드 대응이 필요할 경우 drawable-night 폴더에 별도 아이콘을 배치하는 방식을 사용한다.
| class RecordDetailPresenter @AssistedInject constructor( | ||
| @Assisted private val navigator: Navigator, | ||
| ) : Presenter<ReviewDetailUiState> { | ||
| ) : Presenter<RecordDetailUiState> { | ||
|
|
||
| @Suppress("unused") | ||
| @Composable | ||
| override fun present(): ReviewDetailUiState { | ||
| override fun present(): RecordDetailUiState { | ||
| val scope = rememberCoroutineScope() | ||
|
|
||
| var sideEffect by rememberRetained { mutableStateOf<ReviewDetailSideEffect?>(null) } | ||
| var sideEffect by rememberRetained { mutableStateOf<RecordDetailSideEffect?>(null) } | ||
|
|
||
| fun handleEvent(event: ReviewDetailUiEvent) { | ||
| fun handleEvent(event: RecordDetailUiEvent) { | ||
| when (event) { | ||
| ReviewDetailUiEvent.OnBackClicked -> { | ||
| RecordDetailUiEvent.OnCloseClicked -> { | ||
| navigator.pop() | ||
| } | ||
| } | ||
| } | ||
| return ReviewDetailUiState( | ||
| return RecordDetailUiState( | ||
| sideEffect = sideEffect, | ||
| eventSink = ::handleEvent, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| @CircuitInject(ReviewDetailScreen::class, ActivityRetainedComponent::class) | ||
| @CircuitInject(RecordDetailScreen::class, ActivityRetainedComponent::class) | ||
| @AssistedFactory | ||
| fun interface Factory { | ||
| fun create(navigator: Navigator): ReviewDetailPresenter | ||
| fun create(navigator: Navigator): RecordDetailPresenter | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
recordId 매개변수 활용 확인이 필요합니다.
Presenter가 Circuit 패턴을 잘 따르고 있지만, RecordDetailScreen의 recordId 매개변수가 현재 사용되지 않고 있습니다. 실제 레코드 데이터를 로드하는 로직이 아직 구현되지 않은 것으로 보입니다.
다음 스크립트로 recordId 사용 현황을 확인해보겠습니다:
🏁 Script executed:
#!/bin/bash
# Description: RecordDetailScreen의 recordId 매개변수가 어떻게 사용되는지 확인
# RecordDetailScreen 사용처 검색
rg -A 5 "RecordDetailScreen" --type kotlin
# recordId 사용처 검색
rg -A 3 -B 3 "recordId" feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/Length of output: 7243
RecordDetailPresenter에 recordId 매개변수 전달 및 활용 필요
RecordDetailScreen에 선언된 recordId가 RecordDetailPresenter 생성자와 Factory에 누락되어 실제 데이터 로딩에 사용되지 않고 있습니다. 다음과 같이 수정해주세요:
RecordDetailPresenter생성자에@Assisted private val recordId: String추가- Factory의
create메서드 시그니처에recordId: String인자 추가 present()내부에서recordId를 활용한 레코드 로딩 로직 구현
예시:
class RecordDetailPresenter @AssistedInject constructor(
- @Assisted private val navigator: Navigator,
+ @Assisted private val navigator: Navigator,
+ @Assisted private val recordId: String,
) : Presenter<RecordDetailUiState> {
@Composable
override fun present(): RecordDetailUiState {
+ // TODO: recordId를 이용해 레코드 상세 로드
…
}
}
@AssistedFactory
fun interface Factory {
- fun create(navigator: Navigator): RecordDetailPresenter
+ fun create(navigator: Navigator, recordId: String): RecordDetailPresenter
}📝 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.
| class RecordDetailPresenter @AssistedInject constructor( | |
| @Assisted private val navigator: Navigator, | |
| ) : Presenter<ReviewDetailUiState> { | |
| ) : Presenter<RecordDetailUiState> { | |
| @Suppress("unused") | |
| @Composable | |
| override fun present(): ReviewDetailUiState { | |
| override fun present(): RecordDetailUiState { | |
| val scope = rememberCoroutineScope() | |
| var sideEffect by rememberRetained { mutableStateOf<ReviewDetailSideEffect?>(null) } | |
| var sideEffect by rememberRetained { mutableStateOf<RecordDetailSideEffect?>(null) } | |
| fun handleEvent(event: ReviewDetailUiEvent) { | |
| fun handleEvent(event: RecordDetailUiEvent) { | |
| when (event) { | |
| ReviewDetailUiEvent.OnBackClicked -> { | |
| RecordDetailUiEvent.OnCloseClicked -> { | |
| navigator.pop() | |
| } | |
| } | |
| } | |
| return ReviewDetailUiState( | |
| return RecordDetailUiState( | |
| sideEffect = sideEffect, | |
| eventSink = ::handleEvent, | |
| ) | |
| } | |
| } | |
| @CircuitInject(ReviewDetailScreen::class, ActivityRetainedComponent::class) | |
| @CircuitInject(RecordDetailScreen::class, ActivityRetainedComponent::class) | |
| @AssistedFactory | |
| fun interface Factory { | |
| fun create(navigator: Navigator): ReviewDetailPresenter | |
| fun create(navigator: Navigator): RecordDetailPresenter | |
| } | |
| class RecordDetailPresenter @AssistedInject constructor( | |
| @Assisted private val navigator: Navigator, | |
| @Assisted private val recordId: String, | |
| ) : Presenter<RecordDetailUiState> { | |
| @Suppress("unused") | |
| @Composable | |
| override fun present(): RecordDetailUiState { | |
| val scope = rememberCoroutineScope() | |
| var sideEffect by rememberRetained { mutableStateOf<RecordDetailSideEffect?>(null) } | |
| // TODO: recordId를 이용해 레코드 상세 로드 | |
| fun handleEvent(event: RecordDetailUiEvent) { | |
| when (event) { | |
| RecordDetailUiEvent.OnCloseClicked -> { | |
| navigator.pop() | |
| } | |
| } | |
| } | |
| return RecordDetailUiState( | |
| sideEffect = sideEffect, | |
| eventSink = ::handleEvent, | |
| ) | |
| } | |
| } | |
| @CircuitInject(RecordDetailScreen::class, ActivityRetainedComponent::class) | |
| @AssistedFactory | |
| fun interface Factory { | |
| fun create(navigator: Navigator, recordId: String): RecordDetailPresenter | |
| } |
🤖 Prompt for AI Agents
In
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
lines 18 to 47, the RecordDetailPresenter constructor and Factory are missing
the recordId parameter, which is declared in RecordDetailScreen but not passed
through, preventing data loading. Fix this by adding @Assisted private val
recordId: String to the constructor, updating the Factory's create method to
accept recordId: String, and then use this recordId inside the present()
function to implement the record loading logic.
There was a problem hiding this comment.
🧩 Analysis chain
@easyhooon 맞습니다, Circuit 패턴에 대한 제 이해가 부족했네요! 😅
Circuit에서는 @CircuitInject를 통해 Screen 객체 전체가 자동으로 주입되므로, Screen의 매개변수들을 별도로 생성자에 추가할 필요가 없군요. RecordDetailScreen(recordId) 객체 자체가 Presenter에 전달되어 접근할 수 있을 것 같습니다.
올바른 Circuit 패턴 사용법에 대해 알려주시면 감사하겠습니다! 🙏
🌐 Web query:
Slack Circuit Android architecture screen parameters presenter injection
💡 Result:
It looks like you're asking about implementing screen parameters and presenter injection in an Android app using the architecture style inspired by Slack's Circuit framework. Circuit is a Kotlin Multiplatform UI framework developed by Slack, intended for building UIs declaratively with separation of concerns such as screens, presenters, and navigation.
In Circuit, the architecture usually involves:
- Screens: Represent the UI and hold the screen parameters/data.
- Presenters: Handle business logic and provide data to the UI.
- Injection: Presenters are injected or created with dependencies using dependency injection.
Here's a basic outline of how to manage screen parameters and presenter injection in Circuit-style architecture on Android:
1. Define Screen with Parameters
You define a screen class with parameters needed for that screen.
import com.slack.circuit.runtime.Screen
data class MyScreen(
val userId: String
) : Screen2. Define Presenter for the Screen
Create a presenter that receives the screen parameters and other dependencies via constructor injection.
import com.slack.circuit.runtime.Presenter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
class MyScreenPresenter(
private val userRepository: UserRepository,
private val screen: MyScreen
) : Presenter<MyScreen, MyScreenState> {
override fun present(): Flow<MyScreenState> {
val user = userRepository.getUser(screen.userId)
return flowOf(MyScreenState(user))
}
}
data class MyScreenState(
val user: User
)3. Inject Presenter Using a Dependency Injection Framework
You would typically use a DI framework like Dagger, Hilt, Koin, or manual injection to provide the presenter and its dependencies.
For example, in Hilt:
@Module
@InstallIn(SingletonComponent::class)
object MyModule {
@Provides
fun provideUserRepository(): UserRepository = UserRepositoryImpl()
@Provides
fun provideMyScreenPresenter(
userRepository: UserRepository,
screen: MyScreen // Screen can be passed in by the router or navigation controller
): MyScreenPresenter {
return MyScreenPresenter(userRepository, screen)
}
}4. Wiring it in the UI Layer
In your UI composable or Fragment, obtain the presenter and feed the screen parameters.
@Composable
fun MyScreenView(screen: MyScreen, presenter: MyScreenPresenter = remember { ... }) {
val state by presenter.present().collectAsState(initial = MyScreenState(User.EMPTY))
// Render UI with state.user
}Summary
- Define your screens as data classes with parameters.
- Create presenters with dependencies and a reference to the screen.
- Use DI to inject or create presenters with the correct screen parameters.
- In your UI, invoke the presenter and update the UI accordingly.
If you want, I can help you with more concrete code examples or integration with a specific DI framework. Would you like that?
@easyhooon 정말 죄송합니다! 웹 검색을 통해 Circuit 패턴을 다시 확인해보니 제가 완전히 잘못 이해하고 있었네요. 😅
올바른 Circuit 패턴에서는 Screen 객체 자체가 Presenter에 주입되어야 합니다:
class RecordDetailPresenter @AssistedInject constructor(
@Assisted private val navigator: Navigator,
@Assisted private val screen: RecordDetailScreen, // Screen 객체 직접 주입
) : Presenter<RecordDetailUiState> {
@Composable
override fun present(): RecordDetailUiState {
val recordId = screen.recordId // Screen 객체를 통해 매개변수 접근
// TODO: recordId를 이용해 레코드 상세 로드
// ... 나머지 로직
}
}
@AssistedFactory
fun interface Factory {
fun create(navigator: Navigator, screen: RecordDetailScreen): RecordDetailPresenter
}Circuit에서는 @CircuitInject가 Screen 객체 전체를 자동으로 처리해주는 방식이군요. 잘못된 조언을 드려서 정말 죄송합니다! 🙏
✏️ Learnings added
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt:18-47
Timestamp: 2025-07-31T23:30:37.524Z
Learning: In Circuit architecture, screen parameters are not manually passed to presenter constructors. The @CircuitInject annotation automatically handles the injection of the entire Screen object, making screen parameters accessible without explicitly adding them to the constructor or factory methods.
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt:18-47
Timestamp: 2025-07-31T23:30:37.524Z
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.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
⛔ Learnings removed
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#52
File: feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/screens/DelegatingNavigator.kt:0-0
Timestamp: 2025-07-16T06:08:40.883Z
Learning: In the Reed-Android project, DelegatingNavigator was removed because it caused confusion and added unnecessary complexity. The simplified approach uses childNavigator only within bottom navigation screens and rootNavigator for all other navigation, which is more intuitive and maintainable.
🧠 Learnings used
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#52
File: feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/screens/DelegatingNavigator.kt:0-0
Timestamp: 2025-07-16T15:54:19.322Z
Learning: In the Reed-Android project using Circuit architecture, DelegatingNavigator was restored because NavigableCircuitContent can only inject a single navigator, but the dual-navigator architecture (childNavigator for bottom navigation screens, rootNavigator for full-screen screens) requires a delegating component to work within Circuit's constraint.
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#52
File: feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/screens/DelegatingNavigator.kt:0-0
Timestamp: 2025-07-16T06:08:40.883Z
Learning: In the Reed-Android project, DelegatingNavigator was removed because it caused confusion and added unnecessary complexity. The simplified approach uses childNavigator only within bottom navigation screens and rootNavigator for all other navigation, which is more intuitive and maintainable.
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.
| isRecordSavedDialogVisible = false | ||
| navigator.pop() | ||
| navigator.goTo(ReviewDetailScreen) | ||
| navigator.goTo(RecordDetailScreen(event.recordId)) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
recordId 설정 로직 누락 확인
RecordDetailScreen으로 네비게이션할 때 event.recordId를 사용하고 있지만, postRecord API 성공 콜백에서 실제 recordId를 설정하는 로직이 보이지 않습니다. API 응답에서 받은 recordId가 이벤트를 통해 전달되도록 수정이 필요할 수 있습니다.
다음 스크립트로 API 응답 구조를 확인해보세요:
🏁 Script executed:
#!/bin/bash
# Description: Check the postRecord API response structure to verify recordId handling
# Search for RecordRepository postRecord method implementation
ast-grep --pattern $'postRecord(
$$$
) {
$$$
}'
# Search for record API response models
rg -A 10 "data class.*Record.*Response"Length of output: 1476
다음 스크립트로 RecordRegisterPresenter.kt에서 postRecord 호출부 및 응답 매핑 로직을 확인해주세요:
🏁 Script executed:
#!/bin/bash
# RecordRegisterPresenter에서 postRecord 호출 위치 확인
rg -n "postRecord" feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
# RecordRegisterResponse.id를 사용하는 매핑 로직 확인
rg -n "RecordRegisterResponse" feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.ktLength of output: 368
다음 스크립트로 RecordRegisterPresenter.kt에서 repository.postRecord 호출부와 네비게이션 로직이 위치한 콘텍스트를 확인해주세요:
🏁 Script executed:
#!/bin/bash
rg -n -C5 "repository.postRecord" feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.ktLength of output: 561
다음 스크립트로 RecordRegisterPresenter.kt에서 repository.postRecord 호출부 전후 20줄을 확인해주세요:
🏁 Script executed:
#!/bin/bash
rg -n -C20 "repository.postRecord" feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.ktLength of output: 1689
Record 저장 성공 후 recordId 설정 로직 추가 필요
postRecord()의 onSuccess 콜백에서 API 응답인 RecordRegisterResponse.id를 상태에 저장하고, 다이얼로그 확인 시 해당 recordId로 네비게이션하도록 수정해야 합니다.
현재는 단순히 isRecordSavedDialogVisible = true만 설정되어 있어, 이후 호출되는
navigator.goTo(RecordDetailScreen(event.recordId))에서 event.recordId가 할당되지 않은 상태입니다.
- 파일:
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt - 수정 위치:
postRecord()호출부의onSuccess블록 (약 103행)
예시 수정안:
- ).onSuccess {
- isRecordSavedDialogVisible = true
- }
+ ).onSuccess { response ->
+ // API 응답에서 받은 id를 상태에 저장
+ savedRecordId = response.id
+ isRecordSavedDialogVisible = true
+ }이후 다이얼로그 확인 액션에서:
navigator.goTo(RecordDetailScreen(savedRecordId))🤖 Prompt for AI Agents
In
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
around line 103, the onSuccess callback of postRecord() does not save the
returned RecordRegisterResponse.id to the state. Modify the onSuccess block to
store the received recordId in the state, then update the dialog confirmation
logic to navigate using this saved recordId instead of event.recordId, ensuring
the navigator.goTo call uses the correct, assigned recordId.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt (1)
88-105: 이전 리뷰 코멘트의 이슈가 해결되었습니다.
RecordSortenum의 value가 이제 enum 이름과 일치하도록 수정되어 이전에 지적되었던 혼란 요소가 해결되었습니다.getDisplayNameRes()와fromValue()메서드도 적절하게 구현되어 있습니다.
🧹 Nitpick comments (1)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt (1)
37-62: 샘플 데이터의 다양성을 개선해보세요.
recordCollections의 샘플 데이터가 모두 동일한 인용문을 사용하고 있어 UI 테스트 시 다양한 시나리오를 확인하기 어려울 수 있습니다. 서로 다른 길이의 텍스트나 다양한 내용을 사용하면 레이아웃 테스트에 더 유용할 것입니다.val recordCollections: ImmutableList<RecordRegisterModel> = persistentListOf( RecordRegisterModel( id = "0", pageNumber = 12, - quote = ""책을 읽으면 차분해지며 숲으로 둘러싸인 여름 별장 속으로 간 것 같은 기분이 든다. 그 곳에서 그들이 품은 건축에 대한 이상과 삶을 구경하는 것만으로도 충분했다.책을 읽으면 차분해지며 숲으로 둘러싸인 여름 별장 속으로 간 것 같은 기분이 든다. 그 곳에서 그들이 품은 건축에 대한 이상과 삶을 구경하는 것만으로도 충분했다."", + quote = ""짧은 인용문 예시"", createdAt = "2025.06.25", ), RecordRegisterModel( id = "1", pageNumber = 13, - quote = ""책을 읽으면 차분해지며 숲으로 둘러싸인 여름 별장 속으로 간 것 같은 기분이 든다. 그 곳에서 그들이 품은 건축에 대한 이상과 삶을 구경하는 것만으로도 충분했다.책을 읽으면 차분해지며 숲으로 둘러싸인 여름 별장 속으로 간 것 같은 기분이 든다. 그 곳에서 그들이 품은 건축에 대한 이상과 삶을 구경하는 것만으로도 충분했다."", + quote = ""중간 길이의 인용문으로 여러 줄에 걸쳐 표시될 수 있는 텍스트입니다."", createdAt = "2025.06.25", ),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Emotion.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Emotion.kt
🔇 Additional comments (3)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt (3)
3-12: 새로운 import 문들이 적절하게 구성되었습니다.추가된 import 문들이 모두 코드에서 사용되고 있으며, 모듈 구조에 맞게 잘 정리되어 있습니다.
67-72: 사이드 이펙트 구현이 적절합니다.UUID를 사용한 고유 키 생성과 표준적인 sealed interface 패턴이 잘 적용되어 있습니다.
74-86: UI 이벤트 구조가 잘 설계되었습니다.이벤트들이 세분화되어 있고 각각의 목적이 명확하며, 일관된 네이밍 패턴을 따르고 있어 유지보수에 유리합니다.
| val count: Int, | ||
| ) | ||
|
|
||
| enum class Emotion( |
There was a problem hiding this comment.
아아.. 만드셨군요... 편히 쓰라고 core:designsystem에 저도 만들어놨는데 ^_ㅠ
EmotionTag.kt
enum class EmotionTag(val label: String, val bgColor: Color, val textColor: Color, val graphic: Int) {
WARMTH("따뜻함", WarmthBgColor, WarmthTextColor, 0),
JOY("즐거움", JoyBgColor, JoyTextColor, 0),
TENSION("긴장감", TensionBgColor, TensionTextColor, 0),
SADNESS("슬픔", SadnessBgColor, SadnessTextColor, 0),
}compose-metrics, report 활성화
|
|
||
| import com.ninecraft.booket.core.model.EmotionModel | ||
|
|
||
| data class EmotionAnalysisResult( |
There was a problem hiding this comment.
감정 분석 클라에서 하는거였나요...????????
There was a problem hiding this comment.
API 가 어떻게 나올지 몰라서 일단 구현했는데, API 연동 이후 필요없으면 제거하면 될것 같아여
There was a problem hiding this comment.
아깝네요...
굳이 지울 필요 없이, 각 상황에 대한 프리뷰를 구성하는데 사용할 수 있을 듯 합니다.
적용완료 bc78d8c
| state: BookDetailUiState, | ||
| modifier: Modifier = Modifier, | ||
| ) { | ||
| LazyColumn( |
There was a problem hiding this comment.
여기 데이터 페이징으로 가져와서 API 연동할 때 수정이 필요할 것 같아요
https://api.qweasdzxc.shop/swagger-ui/index.html#/Reading%20Records/getReadingRecords
There was a problem hiding this comment.
아 여기 페이징 붙으면 사곤데;
중첩스크롤 구현해야겠군여
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt (1)
15-66: UI 상태 구조가 잘 설계되어 있습니다.불변 컬렉션 사용과 상태 관리 구조가 Compose 모범 사례를 따르고 있습니다. API 통합 전 단계에서 사용하는 목업 데이터도 적절합니다.
다만, 샘플 데이터의 quote 필드에 동일한 긴 텍스트가 반복되고 있는데, 테스트 목적이라면 다양한 길이의 샘플 텍스트를 사용하는 것을 고려해보세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/LoadStateFooter.kt(2 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt(2 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUiState.kt(1 hunks)feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUiState.kt(2 hunks)feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUiState.kt(2 hunks)feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementUiState.kt(2 hunks)feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUiState.kt(3 hunks)feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/SearchUiState.kt(2 hunks)feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUiState.kt(2 hunks)gradle.properties(1 hunks)
✅ Files skipped from review due to trivial changes (6)
- feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUiState.kt
- feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementUiState.kt
- gradle.properties
- feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUiState.kt
- core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/LoadStateFooter.kt
- feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUiState.kt
🚧 Files skipped from review as they are similar to previous changes (3)
- feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/SearchUiState.kt
- feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUiState.kt
- feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUiState.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci-build
🔇 Additional comments (4)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt (4)
3-13: Import 구문이 적절하게 구성되어 있습니다.필요한 의존성들이 모두 포함되어 있고, 불변 컬렉션 사용을 위한 kotlinx.collections.immutable과 Compose의 @immutable 어노테이션이 적절히 import되어 있습니다.
68-74: 사이드 이펙트 구현이 모범 사례를 따르고 있습니다.@immutable 어노테이션과 sealed interface 패턴을 적절히 사용하고 있으며, UUID를 통한 고유 키 생성으로 토스트 메시지의 중복을 방지하고 있습니다.
76-88: UI 이벤트 인터페이스가 일관성 있게 잘 설계되어 있습니다.sealed interface 패턴을 사용하여 타입 안전성을 보장하고 있으며, 이벤트 네이밍이 일관되고 직관적입니다. 모든 필요한 사용자 상호작용을 적절히 커버하고 있습니다.
90-107: RecordSort enum이 올바르게 수정되었습니다.이전 리뷰에서 지적된 enum 값과 이름의 불일치 문제가 해결되었습니다. 현재 구현은 명확하고 일관성 있는 값을 사용하고 있으며, 디스플레이 이름 리소스 반환과 값 파싱 기능이 적절히 구현되어 있습니다.
seoyoon513
left a comment
There was a problem hiding this comment.
일단 자잘한 UI 이슈는 API 연동 때 같이 수정하는걸로!!
여기 UI 작업량 굉장히 많은데 너무 고생하셨어요 👍👍👍 LGTM
| sizeStyle = largeButtonStyle, | ||
| colorStyle = ReedButtonColorStyle.SECONDARY, | ||
| modifier = Modifier.weight(1f), | ||
| trailingIcon = { |
There was a problem hiding this comment.
@seoyoon513 죄송한데 해당 버튼 traillingIcon이 지정된 크기와 비교했을때, 실기기 환경에서 크기가 작아지는 이슈 한번 확인 부탁드림니다!
There was a problem hiding this comment.
@easyhooon 요거 이것 저것 테스트해보느라 좀 늦었습니다~
버튼 내부도 Row로 되어있기 때문에, Row로 테스트 진행했는데요. Row 내에 있는 자식 측정 방식이 첫 번째 자식 먼저 측정해서 배치하고 남은 공간을 두 번째 자식한테 제공합니다
아래 코드처럼 width 제약을 뒀을 때, 두 Box 모두 24.dp 고정 사이즈를 주었음에도 불구하고 첫 번째 Box(파란색)가 충분히 공간을 차지하고 남은 공간을 두 번째 Box(빨간색)가 차지합니다
Row(
modifier = Modifier
.width(80.dp) // 자식 컴포저블을 모두 그리기에 부족한 사이즈
.background(ReedTheme.colors.contentTertiary)
.padding(
horizontal = ReedTheme.spacing.spacing5,
vertical = ReedTheme.spacing.spacing3,
),
) {
Box(
modifier = Modifier
.size(24.dp) //사이즈 24.dp로 고정
.background(Color.Blue),
)
Box(
modifier = Modifier
.size(24.dp) //사이즈 24.dp로 고정
.background(Color.Red),
)
}그래서 ReedButton도 자식 컴포저블을 그리기 충분한 width를 확보하지 못하면,
leadingIcon이 들어갈 경우 Text 영역이 줄어들고 trailingIcon이 들어가면 icon영역이 줄어드는 현상이 발생합니다.


자식 컴포저블에 각각 weight(1f)를 주어 사이좋게 반반 차지할 수 있게 만들 수 있습니다만

Row(
modifier = Modifier
.width(80.dp) // 자식 컴포저블을 모두 그리기에 부족한 사이즈
.background(ReedTheme.colors.contentTertiary)
.padding(
horizontal = ReedTheme.spacing.spacing5,
vertical = ReedTheme.spacing.spacing3,
),
) {
Box(
modifier = Modifier
.size(24.dp) //사이즈 24.dp로 고정
.weight(1f) // weight 1f
.background(Color.Blue),
)
Box(
modifier = Modifier
.size(24.dp) //사이즈 24.dp로 고정
.weight(1f) // weight 1f
.background(Color.Red),
)
}그걸 적용해도 현재 weight로 정의한 비율로는 읽는중 버튼의 찌그러짐을 막을 수는 없습니다.(그냥 사이 좋게 찌그러짐) 어쨌든 찌그러지지 않기 위한 최소한의 너비는 보장해야하는 상황이에요!

따라서 아래 코드처럼 첫 번째 버튼에 within(min = 110.dp) 속성으로 최소한의 너비를 보장해주고, 나머지 버튼이 weight(1f)차지하는 방향으로 수정하는게 어떨까 합니다.
ReedButton(
onClick = {
state.eventSink(BookDetailUiEvent.OnBookStatusButtonClick)
},
text = "읽는 중",
sizeStyle = largeButtonStyle,
colorStyle = ReedButtonColorStyle.SECONDARY,
modifier = Modifier.widthIn(min = 110.dp), // 멀쩡한 모양을 할 수 있는 최소 너비 설정
trailingIcon = {
Icon(
imageVector = ImageVector.vectorResource(designR.drawable.ic_chevron_down),
contentDescription = "Dropdown Icon",
tint = ReedTheme.colors.contentPrimary,
)
},
)
Spacer(modifier = Modifier.width(ReedTheme.spacing.spacing2))
ReedButton(
onClick = {
state.eventSink(BookDetailUiEvent.OnRegisterRecordButtonClick)
},
text = "독서 기록 추가",
sizeStyle = largeButtonStyle,
colorStyle = ReedButtonColorStyle.PRIMARY,
modifier = Modifier.weight(1f), // 나머지 공간 차지
)시스템 글씨 크기를 키웠을 때, 완벽하지 않지만 첫 번째 버튼은 어느 정도 방어가 됩니다
[글씨 키우기 전]

[키운 후]

There was a problem hiding this comment.
따라서 아래 코드처럼 첫 번째 버튼에 within(min = 110.dp) 속성으로 최소한의 너비를 보장해주고, 나머지 버튼이 weight(1f)차지하는 방향으로 수정하는게 어떨까 합니다.
Row 가 자식의 길이를 측정하는 정책에 대해 예전에 공부했었던거같은데 복습할 수 있었네요bb
역시 내부 동작 방식을 아는게 중요하군요
이 방식이 Best Practices 인거같네여 그렇게 합시다~
There was a problem hiding this comment.
사실 글씨 크기도 시스템 폰트 크기에 영향받지않게 고정으로 막아버릴순있는데, Theme 내에 CompositionLocal 로 전역으로 설정 (xml 개발시 텍스트 크기 dp로 적용하는게 국룰이기도했고,, Android에서 권장하는 방식은 아니지만)
고려할 수 도있을것같네여
figma에 지정된 색상을 그대로 적용
현재 feature:detail 모듈에서만 사용함
|
@seoyoon513 누락된 내 기록 모음 EmptyView 까지 추가로 올렸슴다. merge 할게여~ |
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt (2)
136-190: 하드코딩된 도서 정보 사용과거 리뷰에서 이미 지적된 사항입니다. 도서 제목, 저자, 출판사, 연도 등이 여전히 하드코딩되어 있습니다.
195-227: 버튼 구현이 적절합니다이벤트 핸들링과 레이아웃이 올바르게 구성되어 있습니다. 도서 상태 텍스트 하드코딩은 과거 리뷰에서 이미 지적된 사항입니다.
🧹 Nitpick comments (1)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/CollectedSeed.kt (1)
59-86: 불필요한 border 스타일링을 제거하세요Box의 background color와 border color가 동일하므로 border가 시각적으로 불필요합니다. 코드를 단순화할 수 있습니다.
Box( modifier = Modifier .fillMaxWidth() .padding(horizontal = ReedTheme.spacing.spacing4) .clip(RoundedCornerShape(ReedTheme.radius.sm)) .background(ReedTheme.colors.basePrimary) - .padding(ReedTheme.spacing.spacing3) - .border( - width = 1.dp, - color = ReedTheme.colors.basePrimary, - shape = RoundedCornerShape(ReedTheme.radius.sm), - ), + .padding(ReedTheme.spacing.spacing3), ) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/CollectedSeed.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/EmotionAnalysisResultText.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordsCollection.kt(1 hunks)feature/detail/src/main/res/values/strings.xml(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- feature/detail/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (1)
- feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordsCollection.kt
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt:29-37
Timestamp: 2025-07-31T23:17:40.025Z
Learning: Reed-Android 프로젝트에서는 API가 준비되지 않은 상황에서 UI를 먼저 구현하고, API 연동 시점에 하드코딩된 데이터를 실제 데이터로 교체하는 개발 방식을 사용한다. RecordItem 컴포넌트의 emotionTags 매개변수도 API 연동 시점에 `text = emotionTags.joinToString(separator = "·") { "#$it" }`로 적용될 예정이다.
📚 Learning: reed-android 프로젝트에서는 api가 준비되지 않은 상황에서 ui를 먼저 구현하고, api 연동 시점에 하드코딩된 데이터를 실제 데이터로 교체하는 개발 방식을 사용한다. ...
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt:29-37
Timestamp: 2025-07-31T23:17:40.025Z
Learning: Reed-Android 프로젝트에서는 API가 준비되지 않은 상황에서 UI를 먼저 구현하고, API 연동 시점에 하드코딩된 데이터를 실제 데이터로 교체하는 개발 방식을 사용한다. RecordItem 컴포넌트의 emotionTags 매개변수도 API 연동 시점에 `text = emotionTags.joinToString(separator = "·") { "#$it" }`로 적용될 예정이다.
Applied to files:
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/EmotionAnalysisResultText.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
📚 Learning: jetpack compose에서 scale() 변환은 시각적 변환만 적용하며 레이아웃 좌표계는 변경하지 않는다. 따라서 scale(scalex = -1f, scaley = -1f)...
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#72
File: feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/component/CameraFrame.kt:21-82
Timestamp: 2025-07-28T18:08:47.298Z
Learning: Jetpack Compose에서 scale() 변환은 시각적 변환만 적용하며 레이아웃 좌표계는 변경하지 않는다. 따라서 scale(scaleX = -1f, scaleY = -1f)로 반전된 아이콘에서 padding()은 원래 레이아웃 기준으로 동작하므로, 시각적으로 올바른 위치를 위해서는 변환 전 좌표계 기준으로 padding을 설정해야 한다.
Applied to files:
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
📚 Learning: bookdetailscreen의 recordscollection 컴포넌트는 상위 column의 verticalscroll과 lazycolumn의 무한 스크롤을 동시에 지원해야 하는...
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordsCollection.kt:25-59
Timestamp: 2025-07-31T23:22:02.687Z
Learning: BookDetailScreen의 RecordsCollection 컴포넌트는 상위 Column의 verticalScroll과 LazyColumn의 무한 스크롤을 동시에 지원해야 하는 중첩 스크롤 시나리오입니다. LazyColumn에 고정 높이를 설정하지 않으면 앱이 충돌하는 이슈가 있어서, 현재는 높이를 계산하여 설정하고 userScrollEnabled = false로 구현되어 있습니다. 향후 InfiniteLazyColumn과 nestedScrollConnection을 도입하여 전체 화면 스크롤 + LazyColumn 무한 스크롤을 지원할 예정입니다.
Applied to files:
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
🧬 Code Graph Analysis (2)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/EmotionAnalysisResultText.kt (2)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/util/EmotionAnalyzer.kt (1)
analyzeEmotions(16-31)core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/theme/Theme.kt (1)
ReedTheme(14-21)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/CollectedSeed.kt (2)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/SeedItem.kt (1)
SeedItem(26-64)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/EmotionAnalysisResultText.kt (1)
EmotionAnalysisResultText(27-86)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci-build
🔇 Additional comments (14)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/CollectedSeed.kt (5)
26-30: 함수 선언이 적절합니다internal 가시성과 매개변수 구조가 올바르게 정의되어 있습니다.
31-42: 레이아웃 구성이 올바릅니다ReedTheme의 spacing과 radius를 일관성 있게 사용하여 적절한 시각적 구조를 만들고 있습니다.
43-49: 제목 표시가 적절합니다문자열 리소스를 사용한 다국어 지원과 일관된 테마 적용이 올바릅니다.
50-58: 감정 아이템 표시가 적절합니다SpaceEvenly arrangement를 사용하여 감정 아이템들을 균등하게 배치하는 것이 좋은 접근입니다.
89-99: Preview 함수가 적절합니다ComponentPreview 애노테이션과 테마 적용이 올바르게 구성되어 있습니다.
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/EmotionAnalysisResultText.kt (6)
27-34: 함수 선언이 적절합니다AnnotatedString을 반환하여 스타일이 적용된 텍스트를 생성하는 접근이 올바릅니다.
35-35: remember 사용이 적절합니다emotions가 변경될 때만 분석을 재실행하여 성능을 최적화하는 좋은 접근입니다.
37-51: SINGLE 케이스 처리가 적절합니다buildAnnotatedString과 withStyle을 사용한 텍스트 스타일링이 올바르고, 한국어 문장 구성이 자연스럽습니다.
53-73: DUAL 케이스 처리가 적절합니다forEachIndexed를 사용하여 두 감정을 쉼표로 구분하는 로직이 자연스럽고 올바릅니다.
75-84: BALANCED 케이스 처리가 적절합니다여러 감정이 균형잡혀 있을 때 일반적인 메시지를 표시하는 접근이 합리적입니다.
88-142: 포괄적인 Preview 함수입니다모든 3가지 케이스(SINGLE, DUAL, BALANCED)를 테스트할 수 있도록 구성되어 있어 개발 시 매우 유용합니다.
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt (3)
51-61: Modal bottom sheet 상태 관리가 적절합니다두 개의 bottom sheet 상태와 coroutineScope를 올바르게 관리하고 있습니다.
121-125: 컴포넌트 분리가 좋은 접근입니다BookDetailContent를 별도 함수로 분리하여 코드 구조와 가독성을 개선했습니다.
229-236: 조건부 렌더링 로직이 적절합니다기록 컬렉션 유무에 따른 CollectedSeed 표시 조건이 합리적으로 구현되어 있습니다.
🔗 관련 이슈
📙 작업 설명
🧪 테스트 내역 (선택)
📸 스크린샷 또는 시연 영상 (선택)
KakaoTalk_Video_2025-08-01-01-48-27.mp4
💬 추가 설명 or 리뷰 포인트 (선택)
아직 API가 나오지 않아서 기존에 사용되었던 기록 관련 Model인 RecordRegisterModel을 사용해서 구현해봤습니다.
페이지,최신순 정렬같은 비즈니스 로직은 API 연동할때 같이 구현할게요(임시로 구현한거라 DTO 구조 달라지면 로직도 수정될것같아서)
각각의 Composable 함수가 각 화면의 UiState를 파라미터로 들고 있는데, UiState내에 포함된 sideEffect에 의해
restartable skippable 판정을 받지 못했었습니다.(그외 다른 요인으로는 compose 모듈외에 다른 모듈의 data class를 사용해서 unstable 판정을 받는다던지...) 따라서 sideEffect에 @immutable annotation들을 붙혀주었고(문제가 없다고 판단) restartable skippable 판정을 받을 수 있었습니다.@immutable 딱히 명시적으로 붙힐 필요가 없어진거같군요 Strong Skipping Mode 덕분에
Summary by CodeRabbit
신규 기능
UI/UX 개선
리팩터 및 구조 개선
문서 및 리소스
기타