Flt 15 컬렉션 생성 UI 개편#209
Hidden character warning
Conversation
- CollectionCreateContentItemList 제거 - CollectionCreateContentImage: HorizontalPager 기반 이미지 슬라이더 (최대 5개, 빈 리스트 시 미표시, 1개 시 인디케이터 숨김) - CollectionCreateContentReason: 선택 이유 텍스트필드 + 스포일러 토글 분리 - AddContentSelectItem으로 컴포넌트 네이밍 개선 - CollectionCreateScreen에서 세 컴포넌트 조합으로 교체
- AddContentSelectItem: 콘텐츠 선택 항목 (체크 아이콘 포함) - CollectionCreateContentImage: 이미지 페이저 (삭제 버튼, 페이지 인디케이터 포함) - CollectionCreateContentReason: 선택 이유 입력 (이미지 첨부, 스포일러 토글 포함)
- CollectionCreateContentItemList 제거 - CollectionCreateContentImage: HorizontalPager 기반 이미지 슬라이더 (최대 5개, 빈 리스트 시 미표시, 1개 시 인디케이터 숨김) - CollectionCreateContentReason: 선택 이유 텍스트필드 + 스포일러 토글 분리 - AddContentSelectItem으로 컴포넌트 네이밍 개선 - CollectionCreateScreen에서 세 컴포넌트 조합으로 교체
- AddContentSelectItem: 콘텐츠 선택 항목 (체크 아이콘 포함) - CollectionCreateContentImage: 이미지 페이저 (삭제 버튼, 페이지 인디케이터 포함) - CollectionCreateContentReason: 선택 이유 입력 (이미지 첨부, 스포일러 토글 포함)
- ContentDetail에 imageUrls 필드 추가 - CollectionCreateContentImage HorizontalPager 무한 스크롤 적용 - 인디케이터 도트 currentIndex 기준으로 수정 - CollectionCreateScreen 프리뷰로 교체 (더미 데이터 포함) - 저작권 문구 수정
…-컬렉션-생성-UI-개편 # Conflicts: # app/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateScreen.kt # app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentImage.kt
- 커버 사진 삭제 버튼 동작 연결 - 콘텐츠 이미지 Uri 로컬 저장 및 S3 업로드 플로우 구현 - 콘텐츠 이미지 1장씩 추가, 최대 5장 제한 - 이미지 표시 방식 Crop → Fit(4:3)으로 변경 - S3 업로드 로직 공통 함수로 리팩터링
- 이미지 추가 시 새 사진으로 자동 스크롤 - 이미지 삭제 시 이전/다음 사진으로 자연스럽게 이동 - 사진 1장일 때 페이저 스크롤 비활성화 - 이미지 스케일 FillBounds로 변경 (전체 영역 채우기)
|
Warning Review limit reached
More reviews will be available in 10 minutes and 56 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthrough이 PR은 컬렉션 생성 흐름에 썸네일 및 콘텐츠별 이미지 선택·업로드 기능을 추가합니다. 데이터 계약부터 UI 렌더링까지 일관된 이미지 URI 관리를 구현하고, StorageRepository를 통한 S3 업로드 처리를 추가합니다. ChangesCollection Image Upload Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
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🧪 Generate unit tests (beta)
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: 7
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/presentation/collectioncreate/component/AddContentSelectItem.kt (1)
60-67:⚠️ Potential issue | 🟠 Major | ⚡ Quick win선택 아이콘에 접근성 설명/상태를 추가해 주세요.
Line 62가
contentDescription = null이고 컨트롤이 Line 67에서 클릭 가능해서, 보조기기 사용자는 이 요소의 목적과 선택 상태를 파악하기 어렵습니다. 선택/해제 상태를 설명하는 semantics(또는 동적 contentDescription)를 넣어 주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/main/java/com/flint/presentation/collectioncreate/component/AddContentSelectItem.kt` around lines 60 - 67, The Icon currently uses contentDescription = null and only Modifier.clickable, so screen readers can't tell its purpose or selected state; update the Icon to provide a dynamic contentDescription and semantics by using isSelected to produce a descriptive string (e.g., "Select item, selected" vs "Select item, not selected") and add accessibility semantics on the Modifier (e.g., set role to Checkbox/toggle and set a stateDescription or proper checked state) while keeping onClick behavior (refer to Icon, isSelected, onClick and the Modifier.clickable in AddContentSelectItem.kt).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@app/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateScreen.kt`:
- Around line 86-90: The gallery launcher handler currently calls
viewModel.updateThumbnailImageUri(uri) even when uri is null (user cancelled),
causing the thumbnail to be cleared; change the callback in the
rememberLauncherForActivityResult registered as galleryLauncher so it ignores
null results (only call viewModel.updateThumbnailImageUri(uri) when uri != null)
to preserve the existing thumbnail on cancel.
In
`@app/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateViewModel.kt`:
- Around line 63-64: The creation flow currently masks upload failures by using
a fallback empty string for thumbnail (uploadImageIfNeeded(... ) ?: "") and by
letting uploadContentImagesIfNeeded() feed into a mapNotNull path (lines around
235-243), which silently drops failed uploads; change the flow so that
uploadImageIfNeeded and uploadContentImagesIfNeeded return explicit failure
results (e.g., nullable/Result/ sealed outcome) and in the collection creation
path (where thumbnailImageUri is processed and where content images are mapped)
explicitly check for failures and abort the create operation, setting _uiState
to an error/failure state and returning early instead of proceeding with missing
image keys; update the callers (the createCollection routine and any mapNotNull
usage) to handle a null/failure by stopping the request and surface a clear
error to the UI.
- Around line 249-251: The contentResolver.getType(uri) call inside
withContext(Dispatchers.IO) (used to set mimeType) can throw
SecurityException/FileNotFoundException/IOException and will cancel the
surrounding postCollectionCreate() coroutine; wrap the contentResolver access in
a try/catch (or runCatching) inside withContext(Dispatchers.IO) to catch those
exceptions, log the error, and return a safe default like "image/jpeg" instead
of rethrowing. Apply the same pattern to the other contentResolver access in
this method (the similar call at the 262-264 area) so both mimeType resolution
and the second resolver call do not propagate exceptions that would cancel
postCollectionCreate().
- Around line 225-230: removeContentImageUri에서
current.contentImageUris.removeAt(index)가 유효 범위 검증 없이 호출되어
IndexOutOfBoundsException을 일으킬 수 있습니다; _uiState.update 내부에서 current을 얻은
직후(contentDetailsMap 조회 후) index가 0 이상이고 index < current.contentImageUris.size인지
검사해 범위 밖이면 단순히 state를 반환(변경 없음)하도록 하고, 유효할 때만 current.copy를 만들어
contentImageUris에서 removeAt(index)를 수행하도록 수정하세요; 관련 심볼: removeContentImageUri,
_uiState.update, contentDetailsMap, current, contentImageUris, removeAt.
In
`@app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentImage.kt`:
- Around line 87-96: The Icon for deletion currently has contentDescription =
null which makes it inaccessible to screen readers; update the Icon (in
CollectionCreateContentImage where deletedIndex and onDeleteClick are used) to
provide a meaningful, localized contentDescription (e.g., via stringResource
like a "delete image" label) so assistive tech can identify the action; ensure
the label is descriptive and localized and consider adding a semantic role
(button) if not already present.
- Around line 73-79: The HorizontalPager code computes val index = page %
imageUris.size which will throw a divide-by-zero when imageUris is empty; update
the CollectionCreateContentImage composable to check imageUris.isEmpty() and
return early (or render a safe placeholder) before constructing HorizontalPager
(refer to pagerState, imageUris and the Box that uses index) so the pager is
never created with an empty list.
In
`@app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentReason.kt`:
- Line 74: The image-selection control uses .size(width = 48.dp, height = 28.dp)
and contentDescription = null which violates accessibility touch-target and
description requirements; update the composable (the Image/Icon or IconButton
used in CollectionCreateContentReason) to ensure a minimum touch target of 48.dp
(e.g., use Modifier.size(48.dp) or add
Modifier.minimumInteractiveComponentSize()) and replace contentDescription =
null with a meaningful description (use stringResource like
stringResource(R.string.select_image) or a descriptive literal) so assistive
technologies can convey the button purpose; apply the same changes to the other
occurrences in the same block (lines referenced 78-85).
---
Outside diff comments:
In
`@app/src/main/java/com/flint/presentation/collectioncreate/component/AddContentSelectItem.kt`:
- Around line 60-67: The Icon currently uses contentDescription = null and only
Modifier.clickable, so screen readers can't tell its purpose or selected state;
update the Icon to provide a dynamic contentDescription and semantics by using
isSelected to produce a descriptive string (e.g., "Select item, selected" vs
"Select item, not selected") and add accessibility semantics on the Modifier
(e.g., set role to Checkbox/toggle and set a stateDescription or proper checked
state) while keeping onClick behavior (refer to Icon, isSelected, onClick and
the Modifier.clickable in AddContentSelectItem.kt).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3e40fda7-1e4e-4869-b62f-9f72068cf5bb
📒 Files selected for processing (12)
app/src/main/java/com/flint/data/dto/collection/request/CollectionCreateRequestDto.ktapp/src/main/java/com/flint/domain/mapper/collection/CollectionCreateMapper.ktapp/src/main/java/com/flint/domain/model/collection/CollectionCreateModel.ktapp/src/main/java/com/flint/presentation/collectioncreate/AddContentScreen.ktapp/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateScreen.ktapp/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateViewModel.ktapp/src/main/java/com/flint/presentation/collectioncreate/component/AddContentSelectItem.ktapp/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentImage.ktapp/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentReason.ktapp/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentSection.ktapp/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateThumbnail.ktapp/src/main/java/com/flint/presentation/collectioncreate/uistate/CollectionCreateUiState.kt
- 갤러리 선택 취소 시 기존 썸네일이 삭제되던 문제 수정 - contentResolver 호출 시 발생할 수 있는 예외(SecurityException 등) 처리 - 이미지 업로드 실패 시 생성 요청을 중단하고 실패 상태를 반환하도록 변경 - 콘텐츠 이미지가 비어있을 때 발생할 수 있는 0으로 나누기 예외 방지
- removeContentImageUri에서 유효 범위 밖 인덱스로 인한 IndexOutOfBoundsException 방지
| @@ -36,6 +43,8 @@ import androidx.compose.ui.unit.dp | |||
| import androidx.hilt.navigation.compose.hiltViewModel | |||
There was a problem hiding this comment.
[버그] 갤러리 복귀 시 pendingContentId 유실
remember는 컴포지션이 살아있는 동안만 유지됨. 저사양 기기에서 갤러리 앱을 여는 동안 시스템이 앱 프로세스를 종료하거나 Activity가 재생성되면 pendingContentId가 null로 리셋됨.
결과: 사용자가 갤러리에서 사진을 선택해도 addContentImageUri()가 호출되지 않고 사진이 무음으로 버려짐.
rememberSaveable로 교체하면 해결됨.
// 변경 전
var pendingContentId by remember { mutableStateOf<String?>(null) }
// 변경 후
var pendingContentId by rememberSaveable { mutableStateOf<String?>(null) }| import androidx.compose.ui.Alignment | ||
| import androidx.compose.ui.Modifier | ||
| import androidx.compose.runtime.LaunchedEffect | ||
| import androidx.compose.runtime.getValue |
There was a problem hiding this comment.
[버그] 초기 렌더링 시 pager가 마지막 이미지로 점프
prevSize가 imageUris.size로 초기화되므로 첫 컴포지션에서 LaunchedEffect가 실행될 때 isAdded = (N > N) = false → 삭제 경로 진입.
targetIndex = minOf(deletedIndex, imageUris.size - 1) = minOf(-1, N-1) = -1
targetPage = base + (-1) → pager가 이미지[N-1]로 이동해 첫 이미지가 아닌 마지막 이미지가 표시됨. 이미지가 2장 이상일 때 항상 재현.
prevSize 초기값을 0으로 바꾸거나, LaunchedEffect를 초기 실행과 크기 변경을 구분해서 처리해야 함.
// 변경 전
var prevSize by remember { mutableIntStateOf(imageUris.size) }
// 변경 후
var prevSize by remember { mutableIntStateOf(0) }| @@ -53,8 +60,18 @@ class CollectionCreateViewModel @Inject constructor( | |||
|
|
|||
| private fun postCollectionCreate() { | |||
| viewModelScope.launch { | |||
There was a problem hiding this comment.
[버그] 완료 버튼 빠르게 두 번 탭 시 컬렉션 2개 생성
onClickFinish() → postCollectionCreate() 흐름에서 isLoading 같은 중복 실행 방어가 없음.
업로드 + API 호출이 수 초 걸리는 동안 버튼이 비활성화되지 않으므로, 두 번 빠르게 탭하면 viewModelScope.launch 코루틴 2개가 병렬 실행 → 컬렉션이 2개 생성되고 네비게이션도 2번 발생.
isLoading 플래그로 보호 필요.
fun onClickFinish() {
if (_uiState.value.isLoading) return // 추가
postCollectionCreate()
}
private fun postCollectionCreate() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) } // 추가
// ... 기존 로직
_uiState.update { it.copy(isLoading = false) } // 완료 후
}
}|
|
||
| val imageBytes = runCatching { | ||
| withContext(Dispatchers.IO) { | ||
| context.contentResolver.openInputStream(uri)?.use { it.readBytes() } |
There was a problem hiding this comment.
[정리] mimeTypeToFileExtension 3중 복제
동일한 when 블록이 CollectionCreateViewModel, EditProfileViewModel, OnboardingViewModel 세 곳에 각각 private fun으로 존재.
새 포맷(예: image/avif) 지원 시 세 파일을 모두 수정해야 하고 하나라도 누락되면 포맷별 동작이 달라짐.
FileExtension enum에 companion 함수로 추출하는 것을 권장.
// FileExtension.kt
companion object {
fun fromMimeType(mimeType: String): FileExtension = when (mimeType) {
"image/png" -> PNG
"image/gif" -> GIF
"image/webp" -> WEBP
else -> JPEG
}
} - 갤러리 선택 중 프로세스/Activity 재생성 시 pendingContentId 유실 방지 (rememberSaveable 적용)
- 콘텐츠 이미지 페이저가 초기 진입 시 마지막 이미지로 표시되던 문제 수정
- isLoading 플래그를 추가해 업로드~생성 API 호출 중 완료 버튼을 비활성화하고 onClickFinish 재호출을 차단
📮 관련 이슈
📌 작업 내용
📸 스크린샷
😅 미구현
🫛 To. 리뷰어
Summary by CodeRabbit
릴리스 노트
새로운 기능
UI 개선