[Feat] CollectionList 화면 유형 구분 및 컬렉션 조회 API 연동#149
Conversation
…tent-api' into feat/#148-collection-list-api
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthrough컬렉션 목록 라우트에 타입 파라미터(CollectionListRouteType)를 도입하고, 컬렉션 목록 UI와 뷰모델에 북마크 토글 및 사이드이펙트를 추가하며 관련 네비게이션/도메인 모델 시그니처를 갱신했습니다. 레거시 모델과 샘플 데이터 일부가 제거되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant UI as CollectionListScreen
participant VM as CollectionListViewModel
participant Repo as BookmarkRepository
participant Toast as UIToast
User->>UI: onBookmarkClick(collectionId)
UI->>VM: toggleCollectionBookmark(collectionId)
VM->>Repo: toggleCollectionBookmark(collectionId)
alt 성공
Repo-->>VM: success(isBookmarked, delta)
VM->>VM: update uiState (isBookmarked, bookmarkCount)
VM-->>UI: emit ToggleCollectionBookmarkSuccess(isBookmarked)
UI->>Toast: show success toast
else 실패
Repo-->>VM: error
VM-->>UI: emit ToggleCollectionBookmarkFailure
UI->>Toast: show error toast
end
sequenceDiagram
participant ScreenA as Profile/Home/Detail
participant MainNav as MainNavigator
participant Nav as NavController
participant Route as Route(CollectionList)
ScreenA->>MainNav: navigateToCollectionList(routeType)
MainNav->>Nav: navigateToCollectionList(routeType = routeType)
Nav->>Route: construct CollectionList(routeType = routeType)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 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: 2
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/flint/presentation/collectionlist/CollectionListViewModel.kt`:
- Around line 28-29: _uiState is a MutableStateFlow but uiState is exposed as
MutableStateFlow, allowing external mutation; change the public property uiState
to a read-only StateFlow by declaring uiState: StateFlow<CollectionListUiState>
= _uiState (keep the private _uiState as MutableStateFlow), and add the
necessary import for kotlinx.coroutines.flow.StateFlow so callers can only
collect the state and not modify it.
In
`@app/src/main/java/com/flint/presentation/collectionlist/component/CollectionFileItem.kt`:
- Around line 53-54: The poster1Url assignment directly accesses imageList[0],
which can throw IndexOutOfBoundsException when imageList is empty; change the
assignment in the component (poster1Url) to safely access the list (e.g., use
imageList.getOrNull(0) ?: "" or imageList.firstOrNull() ?: "") and keep the
existing safe check for poster2Url (imageList.size >= 2) or similarly use
getOrNull(1) ?: "" to avoid any out-of-bounds access.
🧹 Nitpick comments (7)
app/src/main/java/com/flint/domain/model/user/UserProfileResponseModel.kt (1)
10-21: Companion object의 초기값을null로 통일하는 것을 권장합니다.
profileImageUrl타입이String?로 변경되었지만,Empty와Fake객체에서는 여전히 빈 문자열""을 사용하고 있습니다. 이로 인해 호출부에서null체크와 빈 문자열 체크를 모두 해야 하는 상황이 발생할 수 있습니다.♻️ 권장 수정안
companion object { val Empty = UserProfileResponseModel( id = "", isFliner = false, nickname = "", - profileImageUrl = "" + profileImageUrl = null ) val Fake = UserProfileResponseModel( id = "123", isFliner = true, nickname = "닉네임", - profileImageUrl = "" + profileImageUrl = null ) }app/src/main/java/com/flint/presentation/collectionlist/uistate/CollectionListUiState.kt (1)
6-8:@Immutable어노테이션 추가 고려Compose의 recomposition 최적화를 위해
@Immutable어노테이션 추가를 고려해보세요.ProfileUiState와 일관성도 유지됩니다.💡 제안하는 수정
+import androidx.compose.runtime.Immutable + +@Immutable data class CollectionListUiState( val collectionList: UiState<CollectionListModel> = UiState.Loading )app/src/main/java/com/flint/data/dto/user/response/BookmarkedCollectionListResponseDto.kt (1)
30-33: 프로퍼티 명칭 정합성 개선을 고려해주세요.
profileUrl이profileImageUrl로 매핑되어 있어 의미가 어긋납니다. 도메인/매퍼 영향 범위를 고려해profileImageUrl로 이름을 맞추는 리팩터를 검토해 주세요.app/src/main/java/com/flint/presentation/profile/ProfileViewModel.kt (1)
46-68: 섹션 조회 실패 시 사용자 피드백 경로를 고려해 주세요.현재 실패 시 상태 갱신 없이 종료되므로, 필요하다면 에러 상태나 안내 메시지 처리를 추가하면 UX가 안정적입니다.
app/src/main/java/com/flint/domain/mapper/collection/CollectionMapper.kt (1)
57-96: 컬렉션 아이템 매핑 중복을 줄일 여지가 있습니다.Created/Bookmarked/Recent 매핑이 동일 필드를 반복하므로 공통 헬퍼로 묶으면 필드 변경 시 유지보수가 쉬워집니다.
app/src/main/java/com/flint/presentation/collectionlist/CollectionListViewModel.kt (1)
22-23: Repository를 private으로 변경하세요
userRepository와collectionRepository가val로 선언되어 public 프로퍼티로 노출됩니다. ViewModel의 내부 의존성은 외부에서 접근할 필요가 없으므로private으로 캡슐화하는 것이 좋습니다.♻️ 권장 수정사항
`@HiltViewModel` class CollectionListViewModel `@Inject` constructor( savedStateHandle: SavedStateHandle, - val userRepository: UserRepository, - val collectionRepository: CollectionRepository, + private val userRepository: UserRepository, + private val collectionRepository: CollectionRepository, ) : ViewModel() {app/src/main/java/com/flint/presentation/collectionlist/CollectionListScreen.kt (1)
73-131: Failure 상태에 대한 사용자 피드백 고려
else브랜치(Line 130)가 비어있어UiState.Failure상태에서 사용자에게 아무런 피드백이 없습니다. 에러 메시지나 재시도 버튼을 표시하는 것이 UX 관점에서 좋습니다.현재 스프린트 범위가 아니라면 TODO 주석을 남겨두는 것을 권장합니다.
♻️ 에러 상태 처리 예시
- else -> {} + is UiState.Failure -> { + // TODO: 에러 상태 UI 구현 (에러 메시지, 재시도 버튼 등) + } + + else -> {}
6fc0653 to
0f2572d
Compare
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/collectionlist/CollectionListScreen.kt`:
- Around line 127-184: The when-block handling collectionList only covers
UiState.Loading and UiState.Success, leaving error/empty states rendering blank;
add explicit branches for UiState.Empty and UiState.Error inside the same
when(collectionList) to render a minimal Empty/Error UI (message + optional
icon) and a retry action (wire to an existing reload callback or add a new
onRetry lambda parameter to the screen if none exists), e.g., implement case
UiState.Empty { Centered text "No collections" + optional create button } and
case is UiState.Error { Centered text with error message and a Button that calls
onRetry() }, keeping the rest of the layout (LazyVerticalGrid,
CollectionFileItem, onCollectionItemClick, onBookmarkClick) unchanged.
In
`@app/src/main/java/com/flint/presentation/collectionlist/CollectionListViewModel.kt`:
- Around line 71-90: When toggling bookmark state in CollectionListViewModel
(handling collectionList -> updatedCollections), ensure that on the SAVED screen
you remove the item when it is being un-bookmarked instead of leaving a toggled
item: detect the SAVED context (your existing screen/state flag) and for
collection.id == collectionId, if isBookmarked == false and current view is
SAVED exclude that collection from the resulting list; for all updates clamp
bookmarkCount to a non-negative value (e.g., bookmarkCount = max(0,
collection.bookmarkCount + if (isBookmarked) 1 else -1)). Update the code that
builds updatedCollections and the UiState.Success(CollectionListModel(...))
accordingly.
🧹 Nitpick comments (1)
app/src/main/java/com/flint/core/navigation/model/CollectionListRouteType.kt (1)
3-8: 라우팅 enum에 UI 문자열을 직접 넣지 않는 편이 좋아요.core/navigation 모델에 한국어 문자열이 들어가면 i18n 및 레이어 분리가 깨지기 쉬워요. presentation에서 routeType → stringResource 매핑으로 분리하거나, 최소한
@StringRes로리소스 ID만 들고 있게 정리하는 방향을 추천합니다. Based on learnings, core 계층의 UI 의존을 최소화하세요.
| when (collectionList) { | ||
| is UiState.Loading -> { | ||
| FlintLoadingIndicator() | ||
| } | ||
|
|
||
| Text( | ||
| text = "총 ${collections.size}개", | ||
| color = FlintTheme.colors.gray100, | ||
| modifier = | ||
| Modifier | ||
| .fillMaxWidth() | ||
| .padding(horizontal = 16.dp), | ||
| ) | ||
| is UiState.Success -> { | ||
| with(collectionList.data) { | ||
| LazyVerticalGrid( | ||
| contentPadding = PaddingValues(10.dp), | ||
| columns = GridCells.Fixed(2), | ||
| horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally), | ||
| verticalArrangement = Arrangement.spacedBy(12.dp), | ||
| modifier = Modifier.padding(horizontal = 10.dp), | ||
| ) { | ||
| item( | ||
| span = { GridItemSpan(maxLineSpan) } | ||
| ) { | ||
| Spacer(Modifier.height(12.dp)) | ||
|
|
||
| Spacer(Modifier.height(24.dp)) | ||
| Text( | ||
| text = "총 ${collections.size}개", | ||
| color = FlintTheme.colors.gray100, | ||
| modifier = | ||
| Modifier | ||
| .fillMaxWidth() | ||
| .padding(horizontal = 6.dp) | ||
| .padding(bottom = 14.dp), | ||
| ) | ||
| } | ||
|
|
||
| LazyVerticalGrid( | ||
| contentPadding = PaddingValues(10.dp), | ||
| columns = GridCells.Fixed(2), | ||
| horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally), | ||
| verticalArrangement = Arrangement.spacedBy(12.dp), | ||
| modifier = | ||
| Modifier | ||
| .padding(horizontal = 10.dp), | ||
| ) { | ||
| items( | ||
| items = collections, | ||
| key = { it.collectionId }, | ||
| ) { collection -> | ||
| Box(modifier = Modifier.fillMaxSize(1f).align(Alignment.CenterHorizontally)) { | ||
| CollectionFileItem( | ||
| collection = collection, | ||
| modifier = | ||
| Modifier | ||
| .align(Alignment.Center) | ||
| .noRippleClickable( | ||
| onClick = { onCollectionItemClick(collection.collectionId) } | ||
| items( | ||
| items = collections, | ||
| key = { it.id }, | ||
| ) { collection -> | ||
| Box( | ||
| modifier = | ||
| Modifier | ||
| .fillMaxSize(1f) | ||
| .align(Alignment.CenterHorizontally), | ||
| ) { | ||
| CollectionFileItem( | ||
| collection = collection, | ||
| onBookmarkClick = { onBookmarkClick(collection.id) }, | ||
| modifier = | ||
| Modifier | ||
| .align(Alignment.Center) | ||
| .noRippleClickable( | ||
| onClick = { onCollectionItemClick(collection.id) }, | ||
| ), | ||
| ) | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| else -> {} | ||
| } |
There was a problem hiding this comment.
Error/Empty 상태가 빈 화면으로 끝납니다.
Loading/Success만 처리하고 나머지는 렌더링이 비어 있어, 실패/빈 상태에서 사용자에게 아무 피드백이 없습니다. 최소한 에러/빈 상태 UI나 재시도 경로를 제공해주세요.
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/flint/presentation/collectionlist/CollectionListScreen.kt`
around lines 127 - 184, The when-block handling collectionList only covers
UiState.Loading and UiState.Success, leaving error/empty states rendering blank;
add explicit branches for UiState.Empty and UiState.Error inside the same
when(collectionList) to render a minimal Empty/Error UI (message + optional
icon) and a retry action (wire to an existing reload callback or add a new
onRetry lambda parameter to the screen if none exists), e.g., implement case
UiState.Empty { Centered text "No collections" + optional create button } and
case is UiState.Error { Centered text with error message and a Button that calls
onRetry() }, keeping the rest of the layout (LazyVerticalGrid,
CollectionFileItem, onCollectionItemClick, onBookmarkClick) unchanged.
There was a problem hiding this comment.
에러 관리 나중에 할테니까 답글 달지 말고 Resolve 누르렴
There was a problem hiding this comment.
🧩 Analysis chain
In
@app/src/main/java/com/flint/presentation/collectionlist/CollectionListScreen.kt
around lines 127 - 184, The when-block handling collectionList only covers
UiState.Loading and UiState.Success, leaving error/empty states rendering blank;
add explicit branches for UiState.Empty and UiState.Error inside the same
when(collectionList) to render a minimal Empty/Error UI (message + optional
icon) and a retry action (wire to an existing reload callback or add a new
onRetry lambda parameter to the screen if none exists), e.g., implement case
UiState.Empty { Centered text "No collections" + optional create button } and
case is UiState.Error { Centered text with error message and a Button that calls
onRetry() }, keeping the rest of the layout (LazyVerticalGrid,
CollectionFileItem, onCollectionItemClick, onBookmarkClick) unchanged.
</details>
<!-- fingerprinting:phantom:poseidon:eagle -->
<!-- This is an auto-generated comment by CodeRabbit -->' || true
# Try to resolve the thread using the pulls review-comment endpoint
gh api \
--method PATCH \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/imflint/Flint-Android/pulls/149/reviews/2711375937 \
-f event='COMMENT' || true
# List current review threads to understand the structure
gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/imflint/Flint-Android/pulls/149/comments \
--jq '.[] | select(.id == 2711375937) | {id, path, line, body}'
🏁 Script executed:
#!/bin/bash
# Resolve the review comment thread
gh api \
--method PUT \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/imflint/Flint-Android/pulls/comments/2711375937 \
-f body='_⚠️ Potential issue_ | _🟡 Minor_
**Error/Empty 상태가 빈 화면으로 끝납니다.**
Loading/Success만 처리하고 나머지는 렌더링이 비어 있어, 실패/빈 상태에서 사용자에게 아무 피드백이 없습니다. 최소한 에러/빈 상태 UI나 재시도 경로를 제공해주세요.
<details>
<summary>🤖 Prompt for AI Agents</summary>
Length of output: 68
🏁 Script executed:
#!/bin/bash
# Get the review thread ID and resolve it
gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/imflint/Flint-Android/pulls/149/comments/2711375937 \
--jq '{id, pull_request_review_id, path}'
# Resolve the review thread by marking it as resolved
gh api \
--method PUT \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/imflint/Flint-Android/pulls/149/comments/2711375937/replies \
-f body="Resolved as requested." 2>/dev/null || echo "Could not add reply"
# Try to resolve using the review thread endpoint
gh pr review 149 --repo imflint/Flint-Android --comment -b ""
Length of output: 670
@nahy-512 네, 알겠습니다! 👍
🧬 Code Graph Analysis Results
[Filename: app/src/main/java/com/flint/presentation/main/MainNavigator.kt, Lines 116-118]
fun navigateToCollectionList(routeType: CollectionListRouteType) {
navController.navigateToCollectionList(routeType = routeType)
}
[Filename: app/src/main/java/com/flint/presentation/collectionlist/navigation/CollectionListNavigation.kt, Lines 12-22]
fun NavController.navigateToCollectionList(
routeType: CollectionListRouteType,
navOptions: NavOptions? = null
) {
navigate(
Route.CollectionList(
routeType = routeType,
),
navOptions,
)
}
[Filename: app/src/main/java/com/flint/core/designsystem/theme/Theme.kt, Lines 8-16]
`@Composable`
fun FlintTheme(content: `@Composable` () -> Unit) {
CompositionLocalProvider(
LocalColors provides FlintColors,
LocalTypography provides FlintTypography,
) {
content()
}
}
[Filename: app/src/main/java/com/flint/core/designsystem/component/toast/ShowToast.kt, Lines 25-50]
`@Composable`
fun ShowToast(
text: String,
imageVector: ImageVector?,
paddingValues: PaddingValues,
yOffset: Dp,
hide: () -> Unit,
) {
LaunchedEffect(Unit) {
delay(2.seconds)
hide()
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomCenter,
) {
FlintToast(
text = text,
imageVector = imageVector,
modifier = Modifier
.padding(paddingValues)
.padding(bottom = yOffset),
)
}
}
[Filename: app/src/main/java/com/flint/core/designsystem/component/toast/ShowSaveToast.kt, Lines 23-46]
`@Composable`
fun ShowSaveToast(
navigateToSavedCollection: () -> Unit,
paddingValues: PaddingValues,
yOffset: Dp,
hide: () -> Unit,
) {
LaunchedEffect(Unit) {
delay(2.seconds)
hide()
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomCenter,
) {
SaveToast(
navigateToSavedCollection = navigateToSavedCollection,
modifier = Modifier
.padding(paddingValues)
.padding(bottom = yOffset),
)
}
}
[Filename: app/src/main/java/com/flint/presentation/collectionlist/component/CollectionFileItem.kt, Lines 39-84]
`@Composable`
fun CollectionFileItem(
collection: CollectionItemModel,
onBookmarkClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
modifier = modifier,
) {
with(collection) {
CollectionFileContent(
profileImageUrl = profileUrl.orEmpty(),
nickname = nickname,
isBookmarked = isBookmarked,
bookmarkCount = bookmarkCount,
poster1Url = imageList.getOrElse(0) { "" },
poster2Url = imageList.getOrElse(1) { "" },
onBookmarkClick = onBookmarkClick,
modifier = Modifier.size(154.dp),
)
}
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.width(154.dp),
) {
Text(
text = collection.title,
style = FlintTheme.typography.body1M16,
color = FlintTheme.colors.white,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
Text(
text = collection.description,
style = FlintTheme.typography.caption1R12,
color = FlintTheme.colors.gray300,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.height(45.dp),
)
}
}
}
[Filename: app/src/main/java/com/flint/core/designsystem/component/topappbar/FlintBackTopAppbar.kt, Lines 16-59]
`@Composable`
fun FlintBackTopAppbar(
// 뒤로가기 버튼이 있는 이상 뒤로 움직이는 이벤트는 무조건 있음. 그래서 필수 인자
onClick: () -> Unit,
// 밖에서 주입해주기 위해 / 책임을 밖으로 빼기 위해
modifier: Modifier = Modifier,
backgroundColor: Color = FlintTheme.colors.background,
title: String = "",
closeable: Boolean = false,
actionText: String = "",
onActionClick: () -> Unit = {},
textColor: Color = Color.Unspecified,
) {
FlintBasicTopAppbar(
modifier = modifier,
backgroundColor = backgroundColor,
navigationIcon = {
Icon(
modifier = Modifier.noRippleClickable(onClick = onClick),
imageVector = ImageVector.vectorResource(R.drawable.ic_back),
contentDescription = null,
tint = FlintTheme.colors.white,
)
},
title = title,
action = {
if (closeable) {
Icon(
modifier = Modifier.noRippleClickable(onClick = onActionClick),
imageVector = ImageVector.vectorResource(R.drawable.ic_cancel),
contentDescription = null,
tint = FlintTheme.colors.white,
)
} else {
Text(
modifier = Modifier.noRippleClickable(onClick = onActionClick),
text = actionText,
color = textColor,
style = FlintTheme.typography.body1M16,
)
}
},
)
}
🧠 Learnings used
Learnt from: giovannijunseokim
Repo: imflint/Flint-Android PR: 72
File: app/src/main/java/com/flint/core/designsystem/component/collection/PeopleBottomSheet.kt:85-98
Timestamp: 2026-01-13T17:03:25.553Z
Learning: In PeopleBottomSheet.kt, the `people` list passed to LazyColumn is static and does not change while the bottom sheet is visible, so a key parameter is not required for the items() call.
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:48.312Z
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.
| val updatedCollections = collectionList.data.collections.map { collection -> | ||
| if (collection.id == collectionId) { | ||
| collection.copy( | ||
| isBookmarked = isBookmarked, | ||
| bookmarkCount = if (isBookmarked) { | ||
| collection.bookmarkCount + 1 | ||
| } else { | ||
| collection.bookmarkCount - 1 | ||
| } | ||
| ) | ||
| } else { | ||
| collection | ||
| } | ||
| }.toImmutableList() | ||
|
|
||
| currentState.copy( | ||
| collectionList = UiState.Success( | ||
| CollectionListModel(collections = updatedCollections) | ||
| ) | ||
| ) |
There was a problem hiding this comment.
SAVED 목록에서 언북마크 시 항목 제거가 필요합니다.
현재 로직은 SAVED 화면에서도 항목을 단순 토글만 해서 “저장 목록”에 미저장 항목이 남을 수 있어요. 또한 bookmarkCount가 0일 때 감소하면 음수가 될 수 있습니다. 아래처럼 SAVED에서는 언북마크 시 제거하고, 카운트는 0 하한을 두는 방식을 권장합니다.
🛠️ 수정 제안
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+import kotlin.math.max
@@
- val updatedCollections = collectionList.data.collections.map { collection ->
- if (collection.id == collectionId) {
- collection.copy(
- isBookmarked = isBookmarked,
- bookmarkCount = if (isBookmarked) {
- collection.bookmarkCount + 1
- } else {
- collection.bookmarkCount - 1
- }
- )
- } else {
- collection
- }
- }.toImmutableList()
+ val updatedCollections =
+ when (routeType) {
+ CollectionListRouteType.SAVED ->
+ collectionList.data.collections.mapNotNull { collection ->
+ if (collection.id == collectionId) {
+ if (!isBookmarked) null
+ else collection.copy(
+ isBookmarked = true,
+ bookmarkCount = max(0, collection.bookmarkCount + 1),
+ )
+ } else {
+ collection
+ }
+ }.toImmutableList()
+ else ->
+ collectionList.data.collections.map { collection ->
+ if (collection.id == collectionId) {
+ val nextCount =
+ max(
+ 0,
+ collection.bookmarkCount + if (isBookmarked) 1 else -1,
+ )
+ collection.copy(
+ isBookmarked = isBookmarked,
+ bookmarkCount = nextCount,
+ )
+ } else {
+ collection
+ }
+ }.toImmutableList()
+ }📝 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.
| val updatedCollections = collectionList.data.collections.map { collection -> | |
| if (collection.id == collectionId) { | |
| collection.copy( | |
| isBookmarked = isBookmarked, | |
| bookmarkCount = if (isBookmarked) { | |
| collection.bookmarkCount + 1 | |
| } else { | |
| collection.bookmarkCount - 1 | |
| } | |
| ) | |
| } else { | |
| collection | |
| } | |
| }.toImmutableList() | |
| currentState.copy( | |
| collectionList = UiState.Success( | |
| CollectionListModel(collections = updatedCollections) | |
| ) | |
| ) | |
| import kotlinx.coroutines.flow.update | |
| import kotlinx.coroutines.launch | |
| import kotlin.math.max | |
| val updatedCollections = | |
| when (routeType) { | |
| CollectionListRouteType.SAVED -> | |
| collectionList.data.collections.mapNotNull { collection -> | |
| if (collection.id == collectionId) { | |
| if (!isBookmarked) null | |
| else collection.copy( | |
| isBookmarked = true, | |
| bookmarkCount = max(0, collection.bookmarkCount + 1), | |
| ) | |
| } else { | |
| collection | |
| } | |
| }.toImmutableList() | |
| else -> | |
| collectionList.data.collections.map { collection -> | |
| if (collection.id == collectionId) { | |
| val nextCount = | |
| max( | |
| 0, | |
| collection.bookmarkCount + if (isBookmarked) 1 else -1, | |
| ) | |
| collection.copy( | |
| isBookmarked = isBookmarked, | |
| bookmarkCount = nextCount, | |
| ) | |
| } else { | |
| collection | |
| } | |
| }.toImmutableList() | |
| } | |
| currentState.copy( | |
| collectionList = UiState.Success( | |
| CollectionListModel(collections = updatedCollections) | |
| ) | |
| ) |
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/flint/presentation/collectionlist/CollectionListViewModel.kt`
around lines 71 - 90, When toggling bookmark state in CollectionListViewModel
(handling collectionList -> updatedCollections), ensure that on the SAVED screen
you remove the item when it is being un-bookmarked instead of leaving a toggled
item: detect the SAVED context (your existing screen/state flag) and for
collection.id == collectionId, if isBookmarked == false and current view is
SAVED exclude that collection from the resulting list; for all updates clamp
bookmarkCount to a non-negative value (e.g., bookmarkCount = max(0,
collection.bookmarkCount + if (isBookmarked) 1 else -1)). Update the code that
builds updatedCollections and the UiState.Success(CollectionListModel(...))
accordingly.
| } | ||
| } | ||
|
|
||
| fun toggleCollectionBookmark(collectionId: String) { |
There was a problem hiding this comment.
p3: 요거 관해서 Throttle + Optimistic UI 구현해둔게 있는데 궁금하시면 CollectionDetailViewModel 참고해주세요! 👍
There was a problem hiding this comment.
넵 안그래도 작업해주셨다고 해서 보고 추가로 저도 적용해봐야지 했는데ㅎㅎ 확인해보겠습니다 감사합니다!
📮 관련 이슈
📌 작업 내용
CollectionListScreen에CollectionListRouteType을 추가하고, 화면 이동 시에 타입을 통해 분기처리 해줬어요.RECENT/CREATED/SAVED에 따른 컬렉션 조회 API를 연동했어요.📸 스크린샷
Screen_recording_20260121_120206.mp4
Screen_recording_20260121_185731.mp4
😅 미구현
🫛 To. 리뷰어
Route가core에 위치해버려서,domain에 위치한type내에CollectionListRouteType를 위치시키니까 의존성이 굉장히 이상해진 것 같네요..Route인터페이스라면core에 위치시키는 게 맞지만,Home,CollectionList등의 구현체들은presentation레이어에 위치하는 게 맞다고 생각해요. 관련해서 다른 분들은 어떻게 생각하시는지 궁금합니다.Summary by CodeRabbit
새로운 기능
개선 사항
✏️ Tip: You can customize this high-level summary in your review settings.