[Feat] 탐색 화면 셀프 QA#168
Conversation
- `MainNavigator` 및 `navigateToCollectionDetail`에 `targetImageUrl` 파라미터 추가 - `CollectionDetailRoute`에 `targetImageUrl` 전달 로직 구현 - `ExploreScreen`에서 컬렉션 상세 이동 시 이미지 URL을 함께 전달하도록 수정
- `CollectionDetailScreen`에 `targetImageUrl` 파라미터 추가 - `onGloballyPositioned`를 사용하여 각 컨텐츠의 위치(y좌표) 저장 - `LaunchedEffect`를 통해 화면 진입 시 `targetImageUrl`에 해당하는 위치로 애니메이션 스크롤 동작 구현
📝 WalkthroughWalkthrough컬렉션 상세로 이동 시 대상 이미지 URL을 네비게이션 경로로 전파하고, 상세 화면에서 각 콘텐츠의 Y 좌표를 기록해 주어진 targetImageUrl로 자동 스크롤하는 로직을 추가했습니다. 관련 시그니처와 Content 컴포저블에 modifier 파라미터가 도입되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Explore as ExploreScreen
participant Nav as Navigation
participant Detail as CollectionDetailScreen
participant UI as LazyColumn
participant Tracker as PositionTracker
Explore->>Nav: navigateToCollectionDetail(collectionId, imageUrl)
Nav->>Detail: CollectionDetailRoute(targetImageUrl=imageUrl)
Detail->>Tracker: 초기화 contentPositions Map
Detail->>UI: 렌더링 시작 (Content 항목들)
UI->>Tracker: onGloballyPositioned -> y 좌표 전송
Tracker-->>Detail: contentPositions 업데이트
Detail->>Detail: LaunchedEffect(targetImageUrl, contentPositions)
Detail->>UI: animateScrollToItem(target y position)
UI-->>Detail: 스크롤 완료 이벤트
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
시
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (1 warning, 2 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt`:
- Line 46: contentPositions is a plain mutableMapOf so updates in
onGloballyPositioned aren't observed by Compose; replace its declaration with a
state-backed map using mutableStateMapOf (from androidx.compose.runtime) so
changes are tracked, keep onGloballyPositioned updating contentPositions as
before, and ensure the LaunchedEffect that depends on contentPositions.size now
observes changes; also add the necessary import for mutableStateMapOf.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt`:
- Around line 327-330: The code stores scroll positions using content.imageUrl
as the map key (in the Modifier.onGloballyPositioned block assigning
contentPositions[content.imageUrl] = coordinates.positionInParent().y.toInt()),
which can collide when multiple ContentModelNew items share the same imageUrl;
change the key to the unique identifier content.id (e.g.,
contentPositions[content.id] = ...) and update any related navigation/scroll
logic to pass and consume targetContentId instead of targetImageUrl so lookups
and scroll targets use the unique content id.
♻️ Duplicate comments (1)
app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt (1)
239-255: 자동 스크롤이 동작하지 않는 타이밍 버그두 가지 문제가 있습니다:
contentPositions가mutableMapOf()로 선언되어 Compose 상태로 추적되지 않습니다.LaunchedEffect(Unit)은 초기 컴포지션 시 한 번만 실행되는데, 이 시점에는onGloballyPositioned콜백이 아직 호출되지 않아contentPositions가 비어있습니다. 따라서contentPositions[targetImageUrl]은 항상null을 반환하고 스크롤이 실행되지 않습니다.
mutableStateMapOf를 사용하고LaunchedEffect의 key를 변경하여 위치 정보가 기록된 후 스크롤이 실행되도록 수정해야 합니다.🐛 수정 제안
+import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.snapshotFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first ... - val contentPositions: MutableMap<String, Int> = remember { mutableMapOf() } + val contentPositions = remember { mutableStateMapOf<String, Int>() } ... - LaunchedEffect(Unit) { - if (targetImageUrl == null) return@LaunchedEffect - val targetPosition: Int = contentPositions[targetImageUrl] ?: return@LaunchedEffect - - scrollState.animateScrollTo(targetPosition) - } + LaunchedEffect(targetImageUrl) { + if (targetImageUrl == null) return@LaunchedEffect + snapshotFlow { contentPositions[targetImageUrl] } + .filter { it != null } + .first() + .let { targetPosition -> + scrollState.animateScrollTo(targetPosition) + } + }
| modifier = Modifier.onGloballyPositioned { coordinates -> | ||
| contentPositions[content.imageUrl] = | ||
| coordinates.positionInParent().y.toInt() | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find the ContentModelNew data class definition
rg -n "data class ContentModelNew" --type kotlin -A 20Repository: imflint/Flint-Android
Length of output: 2040
🏁 Script executed:
#!/bin/bash
# Search for how targetImageUrl is used and validated
rg -n "targetImageUrl" --type kotlin -B 3 -A 3Repository: imflint/Flint-Android
Length of output: 8111
🏁 Script executed:
#!/bin/bash
# Search for contentPositions map definition and usage
rg -n "contentPositions" --type kotlin -B 2 -A 2Repository: imflint/Flint-Android
Length of output: 2208
🏁 Script executed:
#!/bin/bash
# Check the full context of CollectionDetailScreen.kt around lines 327-330
cat app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt | sed -n '310,345p'Repository: imflint/Flint-Android
Length of output: 1612
🏁 Script executed:
#!/bin/bash
# Check if there's any validation or documentation about imageUrl uniqueness
rg -i "imageurl.*unique|unique.*imageurl" --type kotlinRepository: imflint/Flint-Android
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Check how content is loaded and if there's any deduplication
rg -n "ContentModelNew" app/src/main/java/com/flint/presentation/collectiondetail/ --type kotlin -B 2 -A 2Repository: imflint/Flint-Android
Length of output: 9813
🏁 Script executed:
#!/bin/bash
# Check the Content composable to see if imageUrl is used anywhere else critical
rg -n "fun Content" app/src/main/java/com/flint/presentation/ --type kotlin -A 15 | head -40Repository: imflint/Flint-Android
Length of output: 3739
스크롤 대상 위치 저장 시 고유한 식별자 사용 필요
content.imageUrl을 키로 사용하는 것보다 content.id를 사용하는 것이 더 안전합니다. ContentModelNew에서 id는 각 콘텐츠의 고유한 식별자이고, imageUrl은 URL 자산일 뿐입니다.
동일한 imageUrl을 가진 여러 콘텐츠가 있을 경우 마지막 항목의 위치만 저장되어 의도치 않은 스크롤 동작이 발생할 수 있습니다. 다음과 같이 수정하세요:
수정 예시
contentPositions[content.id] =
coordinates.positionInParent().y.toInt()그리고 네비게이션 레이어도 targetImageUrl 대신 targetContentId를 전달하도록 조정해야 합니다.
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt`
around lines 327 - 330, The code stores scroll positions using content.imageUrl
as the map key (in the Modifier.onGloballyPositioned block assigning
contentPositions[content.imageUrl] = coordinates.positionInParent().y.toInt()),
which can collide when multiple ContentModelNew items share the same imageUrl;
change the key to the unique identifier content.id (e.g.,
contentPositions[content.id] = ...) and update any related navigation/scroll
logic to pass and consume targetContentId instead of targetImageUrl so lookups
and scroll targets use the unique content id.
There was a problem hiding this comment.
이전 화면 ExploreScreen에서 작품에 대한 정보로 줄 수 잇는게 content.imageUrl 뿐이야.
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the ExploreScreen to see what data structure is used for collections
cat app/src/main/java/com/flint/presentation/explore/ExploreScreen.kt
Length of output: 9913
@giovannijunseokim, 이해했습니다! ExploreScreen에서 CollectionsModel.Collection이 가진 데이터가 collectionId와 imageUrl뿐이니, 컬렉션의 썸네일 이미지 URL을 넘겨서 해당 작품으로 스크롤하는 방식이 현재 구조에서는 합리적인 선택이네요.
컬렉션의 imageUrl이 실제로 컬렉션 내 특정 작품의 imageUrl과 일치한다는 가정 하에 잘 동작할 것 같습니다.
다만, 나중에 코드를 보는 다른 개발자를 위해 간단한 주석을 추가하면 좋을 것 같습니다:
val contentPositions: MutableMap<String, Int> = remember { mutableMapOf() }
LaunchedEffect(Unit) {
if (targetImageUrl == null) return@LaunchedEffect
// ExploreScreen에서 컬렉션 썸네일 imageUrl을 전달받아 해당 작품으로 스크롤
val targetPosition: Int = contentPositions[targetImageUrl] ?: return@LaunchedEffect
scrollState.animateScrollTo(targetPosition)
}이 정도면 충분할 것 같습니다!
🧬 Code Graph Analysis Results
-
File: app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt
- Snippet: Content(...) (the composable that renders each content item)
- Description:
- Parameters: content: ContentModelNew, onBookmarkIconClick: (contentId: String) -> Unit, onSpoilClick: (contentId: String) -> Unit, modifier: Modifier = Modifier
- Returns: Unit (Composable)
- Behavior: Displays a content item, using NetworkImage to load the image via content.imageUrl (imageUrl field), with content-scale Crop, followed by metadata (title, year, director), bookmark UI, spoiler handling (Spoiler component when isSpoiler is true), and bookmark/spoil action callbacks. This is the UI surface that relies on a URL image for each piece of content.
-
File: app/src/main/java/com/flint/core/designsystem/component/image/NetworkImage.kt
- Snippet: NetworkImage(...) implementation
- Description:
- Parameters: imageUrl: Any?, modifier: Modifier = Modifier, shape: Shape = RectangleShape, placePainter, errorPainter, contentScale: ContentScale = ContentScale.Crop, contentDescription: String? = null
- Returns: Unit (Composable)
- Behavior: Renders an image from a network source. In preview mode, shows a dummy image; otherwise uses AsyncImage with model = imageUrl, placeholder and error painters, appropriate contentScale, and clipping to the provided shape. This is the core mechanism by which content.imageUrl is visualized.
-
File: app/src/main/java/com/flint/core/designsystem/component/image/ProfileImage.kt
- Snippet: ProfileImage(...) implementation
- Description:
- Parameters: imageUrl: String?, modifier: Modifier = Modifier, contentDescription: String? = null
- Returns: Unit (Composable)
- Behavior: Displays a profile image; if imageUrl is null or blank, shows a default avatar resource; otherwise loads the image with AsyncImage (including placeholder and error images) and clips to a circle. Relevant for how author/profile images could be presented if content includes user avatars derived from URLs.
📮 관련 이슈
📌 작업 내용
feat: 작품 제목은 글자 수 기준이 아닌, 텍스트 박스 width 기준으로 최대 2줄까지 표시한다
feat: 작품 소개는 8줄까지만 노출된다. 텍스트가 width 기준으로 줄바꿈될 경우, 해당 줄에 딱 걸리는 음절 단위에서…
컬렉션 상세 페이지 진입 시 자동 스크롤 처리를 통해 해당 작품 섹션의 위치로 바로 노출한다
📸 스크린샷
auto-scroll.mp4
Summary by CodeRabbit
릴리스 노트
새 기능
개선사항
✏️ Tip: You can customize this high-level summary in your review settings.