[Feat] CollectionDetailScreen (컬렉션 상세 화면)#109
Conversation
- CollectionDetailScreen의 전체적인 레이아웃 및 구성 요소 구현 - 컬렉션 헤더(제목, 배경, 저장 버튼), 작성자 정보 및 설명 영역 추가 - 컬렉션 내 개별 콘텐츠 아이템(포스터, 제목, 정보, 북마크, 스포일러 방지 처리) 구현 - 컬렉션을 저장한 사용자 목록(프로필 이미지 리스트) 섹션 추가 - 각 UI 컴포넌트별 Preview 코드 작성
- `LazyColumn`을 `Column`과 `verticalScroll` 기반으로 변경하여 전체 스크롤 상태 관리 - 스크롤 위치에 따라 진행률을 계산하여 `UnderImageProgressBar`에 반영 - 썸네일 영역이 스크롤되어 사라질 때 `UnderImageProgressBar`가 상단에 고정(Sticky)되도록 구현 - 썸네일 높이 측정을 위해 `onGloballyPositioned` 적용 및 관련 상태 추가
- CollectionDetailScreen에서 사용하지 않는 navigateToCollectionList 파라미터 제거 - Preview 데이터의 콘텐츠 리스트에 스포일러 상태인 아이템 추가 (레이아웃 확인용)
📝 WalkthroughWalkthroughCollectionDetailScreen과 ExploreScreen 구현을 추가하고, 저장 상태용 Composable(FlintSaveDoneButton, FlintSaveNoneButton), ProfileImage의 contentDescription 파라미터 추가, Colors에 Changes
Sequence Diagram(s)(생성 조건 불충족 — 생략) Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/main/java/com/flint/core/designsystem/component/image/ProfileImage.kt (1)
15-28: NetworkImage 호출 시에도 contentDescription을 전달하세요.NetworkImage는 contentDescription 파라미터를 지원하므로, imageUrl이 null일 때와 동일하게 contentDescription을 전달하여 접근성 지원을 일관되게 해야 합니다.
else { NetworkImage( imageUrl = imageUrl, contentDescription = contentDescription, shape = CircleShape, modifier = modifier, ) }
🧹 Nitpick comments (6)
app/src/main/java/com/flint/core/designsystem/component/button/FlintSaveDoneButton.kt (1)
17-38: 접근성(Accessibility) 지원 추가를 고려해주세요.현재 버튼에 시맨틱 정보가 없어 스크린 리더 사용자가 버튼의 역할을 인식하기 어렵습니다.
semanticsmodifier나clickable의role파라미터를 활용하는 것이 좋습니다.또한, 하드코딩된 한국어 문자열 "저장된 컬렉션"을 string resource로 분리하면 향후 다국어 지원에 유리합니다.
♻️ 접근성 개선 제안
Row( modifier = modifier .padding(vertical = 4.dp) .heightIn(min = 40.dp) .clip(RoundedCornerShape(44.dp)) .background(FlintTheme.colors.gradient400) - .clickable(onClick = onClick) + .clickable(role = Role.Button, onClick = onClick) .padding(horizontal = 16.dp, vertical = 9.dp), ) {
Role.Button사용을 위해import androidx.compose.ui.semantics.Role추가가 필요합니다.app/src/main/java/com/flint/core/designsystem/component/button/FlintSaveNoneButton.kt (1)
18-40: FlintSaveDoneButton과의 코드 중복을 줄이기 위한 추상화를 고려해보세요.두 버튼 컴포넌트가 거의 동일한 구조를 가지고 있으며, 차이점은 배경색, 테두리 유무, 텍스트, 타이포그래피뿐입니다. 공통 베이스 컴포넌트로 추출하면 유지보수가 용이해집니다.
♻️ 공통 컴포넌트 추출 예시
`@Composable` private fun FlintSaveButtonBase( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, background: Brush, textStyle: TextStyle, border: BorderStroke? = null, ) { Row( modifier = modifier .padding(vertical = 4.dp) .heightIn(min = 40.dp) .clip(RoundedCornerShape(44.dp)) .background(background) .then( if (border != null) Modifier.border(border.width, border.brush, RoundedCornerShape(44.dp)) else Modifier ) .clickable(role = Role.Button, onClick = onClick) .padding(horizontal = 16.dp, vertical = 9.dp), ) { Text(text, color = FlintTheme.colors.white, style = textStyle) } }app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt (4)
69-75: 빈CollectionDetailRoute함수에 TODO 주석을 추가하세요.정적 분석 도구에서 빈 함수 블록으로 플래그되었습니다. 이 함수는 향후 ViewModel 연결을 위한 stub으로 보이므로, 의도를 명확히 하는 TODO 주석을 추가하거나
CollectionDetailScreen을 호출하는 최소한의 구현을 추가하는 것이 좋습니다.♻️ 제안하는 수정
`@Composable` fun CollectionDetailRoute( paddingValues: PaddingValues, collectionId: String, navigateToCollectionList: () -> Unit, ) { + // TODO: ViewModel 연결 및 상태 관리 구현 }
169-176: 컨텐츠 아이템에key를 추가하는 것을 고려하세요.
forEach로Content를 렌더링할 때key가 없으면 리스트 변경 시 불필요한 recomposition이 발생할 수 있습니다.contentId를 key로 사용하면 Compose가 아이템을 효율적으로 추적할 수 있습니다.♻️ 제안하는 수정
- contents.forEach { content: ContentModel -> - Content( - content = content, - onBookmarkIconClick = { contentId: Long -> - // TODO: Content 저장 - }, - ) - } + contents.forEach { content: ContentModel -> + key(content.contentId) { + Content( + content = content, + onBookmarkIconClick = { contentId: Long -> + // TODO: Content 저장 + }, + ) + } + }
keyimport 추가 필요:import androidx.compose.runtime.key
585-601: 스포일러 상태 관리 구현이 필요합니다.현재
isSpoiler가ContentModel에서 오는 불변 값이므로,spoil콜백이 호출되어도 실제로 스포일러가 해제되지 않습니다. 로컬 상태 또는 상위 컴포넌트로의 콜백이 필요합니다.스포일러 해제를 위한 로컬 상태 관리 구현을 도와드릴까요? 예를 들어,
revealedSpoilers: Set<Long>상태를CollectionDetailScreen에서 관리하고contentId를 기반으로 토글하는 방식을 제안드릴 수 있습니다.
217-226: "더보기" 아이콘에contentDescription을 추가하세요.접근성 향상을 위해 Line 219의
contentDescription에 적절한 설명을 추가하는 것이 좋습니다.♻️ 제안하는 수정
Icon( imageVector = ImageVector.vectorResource(R.drawable.ic_more), - contentDescription = null, + contentDescription = "이 컬렉션을 저장한 사람들 전체 보기", modifier = Modifier .size(48.dp) .clickable(onClick = onMoreClick) .padding(12.dp), tint = FlintTheme.colors.white, )
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
app/src/main/java/com/flint/core/designsystem/component/button/FlintSaveDoneButton.ktapp/src/main/java/com/flint/core/designsystem/component/button/FlintSaveNoneButton.ktapp/src/main/java/com/flint/core/designsystem/component/image/ProfileImage.ktapp/src/main/java/com/flint/core/designsystem/theme/Color.ktapp/src/main/java/com/flint/domain/model/ContentModel.ktapp/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-01-13T19:02:41.580Z
Learnt from: chanmi1125
Repo: imflint/Flint-Android PR: 77
File: app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateThumbnail.kt:45-72
Timestamp: 2026-01-13T19:02:41.580Z
Learning: In `app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateThumbnail.kt`, the height difference between CollectionCreateEmptyThumbnail (no aspectRatio) and CollectionCreateFillThumbnail (aspectRatio 1.5f / 1f) is intentional design - the empty state should not have the same aspectRatio as the filled state.
Applied to files:
app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt
📚 Learning: 2026-01-13T19:02:56.195Z
Learnt from: chanmi1125
Repo: imflint/Flint-Android PR: 77
File: app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateThumbnail.kt:45-72
Timestamp: 2026-01-13T19:02:56.195Z
Learning: In `app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateThumbnail.kt`, the aspect ratio difference between `CollectionCreateEmptyThumbnail` (no aspect ratio) and `CollectionCreateFillThumbnail` (1.5f / 1f) is intentional by design.
Applied to files:
app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt
🧬 Code graph analysis (3)
app/src/main/java/com/flint/core/designsystem/component/button/FlintSaveNoneButton.kt (1)
app/src/main/java/com/flint/core/designsystem/theme/Theme.kt (1)
FlintTheme(8-16)
app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt (9)
app/src/main/java/com/flint/core/designsystem/component/collection/PeopleBottomSheet.kt (1)
PeopleBottomSheet(44-106)app/src/main/java/com/flint/core/designsystem/component/topappbar/FlintBackTopAppbar.kt (1)
FlintBackTopAppbar(16-57)app/src/main/java/com/flint/core/designsystem/component/progressbar/UnderImageProgressBar.kt (1)
UnderImageProgressBar(18-38)app/src/main/java/com/flint/core/designsystem/component/image/ProfileImage.kt (1)
ProfileImage(11-30)app/src/main/java/com/flint/core/designsystem/theme/Theme.kt (1)
FlintTheme(8-16)app/src/main/java/com/flint/core/designsystem/component/button/FlintSaveDoneButton.kt (1)
FlintSaveDoneButton(17-38)app/src/main/java/com/flint/core/designsystem/component/button/FlintSaveNoneButton.kt (1)
FlintSaveNoneButton(18-40)app/src/main/java/com/flint/core/designsystem/component/image/NetworkImage.kt (1)
NetworkImage(22-46)app/src/main/java/com/flint/core/designsystem/component/collection/Spoiler.kt (1)
Spoiler(29-83)
app/src/main/java/com/flint/core/designsystem/component/button/FlintSaveDoneButton.kt (1)
app/src/main/java/com/flint/core/designsystem/theme/Theme.kt (1)
FlintTheme(8-16)
🪛 detekt (1.23.8)
app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt
[warning] 74-75: This empty block of code can be removed.
(detekt.empty-blocks.EmptyFunctionBlock)
⏰ 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: PR Build Check
- GitHub Check: PR Lint Check
🔇 Additional comments (6)
app/src/main/java/com/flint/domain/model/ContentModel.kt (1)
11-15: LGTM! 새 속성들이 적절한 기본값과 함께 추가되었습니다.기존 코드와의 호환성을 유지하면서 CollectionDetailScreen에 필요한 메타데이터를 지원합니다.
app/src/main/java/com/flint/core/designsystem/theme/Color.kt (2)
70-70: LGTM! 새로운 buttonStroke 브러시가 기존 테마 패턴을 따라 잘 추가되었습니다.FlintSaveNoneButton에서 사용되는 버튼 테두리 스타일을 위한 그라데이션 브러시입니다.
Also applies to: 142-145
386-390: 프리뷰에 새 브러시 추가 확인.buttonStroke 브러시의 시각적 확인을 위한 프리뷰 박스가 적절히 추가되었습니다.
app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt (3)
99-106: LGTM!스크롤 진행률 계산과 sticky 로직이 올바르게 구현되었습니다.
maxValue가 0인 경우에 대한 방어 코드가 적절하며,thumbnailHeight를onGloballyPositioned로 측정하여 sticky 전환 시점을 정확히 판단합니다.
451-463: LGTM!
UserRoleType에 따른 UI 분기가 명확합니다.FLINER에게는 인증 아이콘을, 다른 역할에는 구분자를 표시하는 로직이 적절합니다.
711-749: 프리뷰 데이터가 다양한 상태를 잘 커버하고 있습니다.
ContentPreviewProvider가 일반 콘텐츠, 스포일러 콘텐츠, 북마크된 콘텐츠 케이스를 모두 포함하여 UI 검증에 유용합니다.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| fun CollectionDetailScreen( | ||
| paddingValues: PaddingValues, | ||
| title: String, | ||
| authorId: Long, | ||
| userId: Long, | ||
| isBookmarked: Boolean, | ||
| authorNickname: String, | ||
| authorUserRoleType: UserRoleType, | ||
| createdAt: String, | ||
| collectionContent: String, | ||
| contents: ImmutableList<ContentModel>, | ||
| people: ImmutableList<AuthorModel>, | ||
| ) { |
There was a problem hiding this comment.
뒤로가기 콜백 파라미터가 누락되었습니다.
CollectionDetailScreen에서 FlintBackTopAppbar를 사용하지만 뒤로가기 네비게이션을 처리할 콜백 파라미터가 없습니다. 현재 Line 124의 onClick이 빈 람다로 되어 있어 뒤로가기 버튼이 동작하지 않습니다.
♻️ 제안하는 수정
`@Composable`
fun CollectionDetailScreen(
paddingValues: PaddingValues,
title: String,
authorId: Long,
userId: Long,
isBookmarked: Boolean,
authorNickname: String,
authorUserRoleType: UserRoleType,
createdAt: String,
collectionContent: String,
contents: ImmutableList<ContentModel>,
people: ImmutableList<AuthorModel>,
+ onBackClick: () -> Unit,
) {그리고 Line 124:
FlintBackTopAppbar(
- onClick = { },
+ onClick = onBackClick,
backgroundColor = Color.Transparent,
)- VerticalPager를 활용한 탐색 화면 레이아웃 구현 - 개별 콘텐츠를 보여주는 ExplorePageItem 컴포넌트 구현 (포스터, 제목, 설명, 이동 버튼) - 모든 콘텐츠 확인 후 표시되는 ExploreEndPage 컴포넌트 구현 - FlintLogoTopAppbar 및 배경 그라데이션 적용 - 각 컴포넌트 및 전체 화면에 대한 Preview 코드 추가
| val bookmarkCount: Int = 0, | ||
| val description: String = "", | ||
| val isSpoiler: Boolean = false, | ||
| ) |
There was a problem hiding this comment.
p1
ContentModel을 쓰고 있던 곳이 많던데, 각 Model을 사용하는 곳마다 필요한 화면에 보여줄 때 필요한 정보들이 다 다르더라구요.
그래서 기본값을 정의하신 것 같긴 하다만, 도메인 모델의 범위를 어떻게 가져가야 할지, 그리고 컴포넌트에서 해당 모델을 어떻게 활용할지에 대해 이야기를 해봐야 할 것 같아요. 로직 작업 전에 모여서 범위에 대해 한번 얘기해보면 좋을 것 같습니다.
저는 개념을 다르게 생각하고 CollectionModel과 CollectionDetailModel을 따로 뒀어서용
There was a problem hiding this comment.
Screen 단에서 도메인의 Model을 그대로 활용하게 되면 나중에 작업 시에 헷갈리는 부분들이 많이 생길 것 같아요.
DomainModel을 같이 정리하고, UI에서 좁게 가져가는 UiModel을 사용하는 방법을 고민중인데, 다같이 모인 자리에서 한번 얘기해보면 좋겠습니다.
nahy-512
left a comment
There was a problem hiding this comment.
컴포넌트 프리뷰를 굉장히 잘 만들어주셔서 보기 편했습니다ㅎㅎ
컴포넌트를 따로 파일로 구분하지 않으신 이유가 있을까요?? 한 화면에서 프리뷰를 다 모아볼 수 있어서 좋긴 했는데, 아무래도 파일 길이가 너무 길어서 '어떤 게 어떤 컴포넌트지?'라는 게 직관적으로 파악되지는 않았던 것 같아요.
그리고 뷰에서 수정사항이 조금 발생할 것 같은데, 이슈 새로 파서 대응해주시면 감사하겠습니다!
| var thumbnailHeight: Int by remember { mutableIntStateOf(0) } | ||
|
|
||
| val scrollProgress: Float = | ||
| if (scrollState.maxValue > 0) { | ||
| scrollState.value.toFloat() / scrollState.maxValue | ||
| } else { | ||
| 0f | ||
| } |
There was a problem hiding this comment.
스티키 헤더.. 구현 열심히 해주셨는데.. 저희 앱잼 범위 내에서 빼야할 것 같아 이 부분 머지 후 한번만 확인 부탁드릴게요..!
| if (people.size >= 6) { | ||
| Row { | ||
| Icon( | ||
| imageVector = ImageVector.vectorResource(R.drawable.ic_plus), | ||
| contentDescription = "그 외", | ||
| tint = FlintTheme.colors.white, | ||
| ) | ||
|
|
||
| Text( | ||
| text = (people.size - 5).toString(), | ||
| color = FlintTheme.colors.gray50, | ||
| style = FlintTheme.typography.head2M20, | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
이것도.. 정책 수정될 것 같은데 나중에 확인 부탁드리겠습니다🥲
| private data class ScreenPreviewData( | ||
| val title: String, | ||
| val authorId: Long, | ||
| val userId: Long, | ||
| val isBookmarked: Boolean, | ||
| val authorNickname: String, | ||
| val authorUserRoleType: UserRoleType, | ||
| val contents: ImmutableList<ContentModel>, | ||
| val people: ImmutableList<AuthorModel>, | ||
| ) |
There was a problem hiding this comment.
p1
이런 데이터들 Screen 밖으로 파일 분리 해주시면 좋을 것 같습니다
[Feat] ExploreScreen(탐색 화면)
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/flint/presentation/explore/ExploreScreen.kt`:
- Around line 41-49: ExploreRoute currently renders nothing because the
ExploreScreen call is commented out; uncomment the ExploreScreen invocation
inside ExploreRoute and pass the padding and callbacks through—call
ExploreScreen(modifier = Modifier.padding(paddingValues),
navigateToCollectionDetail = navigateToCollectionDetail,
navigateToCollectionCreate = navigateToCollectionCreate) so the UI renders when
ExploreRoute is entered.
- Around line 115-118: In ExploreScreen's NetworkImage call (the one passing
imageUrl and Modifier.fillMaxSize()), add a contentDescription argument and
provide an accessible description (for example the item's title or a short
phrase like "Main image for [title]") so screen readers can announce the image;
update the surrounding code to pass the appropriate title/label into
NetworkImage or compute a fallback description if the title isn't available.
| fun ExploreRoute( | ||
| paddingValues: PaddingValues, | ||
| navigateToCollectionDetail: (collectionId: String) -> Unit, | ||
| navigateToCollectionCreate: () -> Unit, | ||
| ) { | ||
| ExploreScreen( | ||
| modifier = Modifier.padding(paddingValues), | ||
| ) | ||
| // ExploreScreen( | ||
| // modifier = Modifier.padding(paddingValues), | ||
| // ) | ||
| } |
There was a problem hiding this comment.
ExploreRoute가 화면을 렌더링하지 않습니다.
현재 ExploreScreen 호출이 주석 처리되어 라우트 진입 시 빈 화면이 됩니다. paddingValues와 네비게이션 콜백을 전달해 실제 UI가 표시되도록 연결해주세요.
🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/explore/ExploreScreen.kt` around
lines 41 - 49, ExploreRoute currently renders nothing because the ExploreScreen
call is commented out; uncomment the ExploreScreen invocation inside
ExploreRoute and pass the padding and callbacks through—call
ExploreScreen(modifier = Modifier.padding(paddingValues),
navigateToCollectionDetail = navigateToCollectionDetail,
navigateToCollectionCreate = navigateToCollectionCreate) so the UI renders when
ExploreRoute is entered.
| NetworkImage( | ||
| imageUrl = imageUrl, | ||
| modifier = Modifier.fillMaxSize(), | ||
| ) |
There was a problem hiding this comment.
메인 이미지에 접근성 설명 추가를 권장합니다.
배경 이미지가 핵심 콘텐츠라면 스크린리더를 위해 contentDescription에 제목 등을 전달하는 게 안전합니다.
🛠️ 제안 변경
- NetworkImage(
- imageUrl = imageUrl,
- modifier = Modifier.fillMaxSize(),
- )
+ NetworkImage(
+ imageUrl = imageUrl,
+ contentDescription = title,
+ modifier = Modifier.fillMaxSize(),
+ )📝 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.
| NetworkImage( | |
| imageUrl = imageUrl, | |
| modifier = Modifier.fillMaxSize(), | |
| ) | |
| NetworkImage( | |
| imageUrl = imageUrl, | |
| contentDescription = title, | |
| modifier = Modifier.fillMaxSize(), | |
| ) |
🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/explore/ExploreScreen.kt` around
lines 115 - 118, In ExploreScreen's NetworkImage call (the one passing imageUrl
and Modifier.fillMaxSize()), add a contentDescription argument and provide an
accessible description (for example the item's title or a short phrase like
"Main image for [title]") so screen readers can announce the image; update the
surrounding code to pass the appropriate title/label into NetworkImage or
compute a fallback description if the title isn't available.
📮 관련 이슈
📌 작업 내용
📸 스크린샷
CollectionDetailScreen.mp4
Summary by CodeRabbit
새로운 기능
개선 사항
✏️ Tip: You can customize this high-level summary in your review settings.