Conversation
WalkthroughFeed screens: adds lock icon for private feeds, updates FeedWrite to use Android 13+ Photo Picker with legacy fallback and keyboard focus handling. ViewModel tightens load-more conditions. Group UI: date picker API reorders parameters and layout tweaks; applies advanced IME padding on two screens. Minor spacing/padding adjustments elsewhere. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User as 사용자
participant UI as FeedWriteScreen
participant OS as Android System
participant Picker as Photo Picker
User->>UI: "사진 추가" 클릭
UI->>UI: remainingSlots = 3 - currentImageCount
alt Android 13+ AND remainingSlots > 1
UI->>Picker: launch PickMultipleVisualMedia(maxItems=remainingSlots)
Picker-->>UI: 선택된 URIs
else
UI->>OS: launch GetMultipleContents("image/*")
OS-->>UI: 선택된 URIs
end
UI->>UI: onAddImages(uris.take(remainingSlots))
note over UI: 외부 탭 시 키보드 해제(LocalFocusManager)
sequenceDiagram
autonumber
participant VM as FeedViewModel
participant UI as Feed List
UI->>VM: 스크롤 끝 감지 시 loadMore 요청
VM->>VM: canLoadMore = !isLoading && !isLoadingMore && !isRefreshing && !isPullToRefreshing && !isLastPage
alt canLoadMore = true
VM-->>UI: 다음 페이지 로드 진행
else
VM-->>UI: 로드 보류
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ 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. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
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/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)
423-443: 신고 토스트 자동 숨김 로직이 실행되지 않는 버그
showToast자동 해제LaunchedEffect가if (showDialog)블록 내부에 있어, 대화상자가 열리지 않은 일반 신고 시에는 결코 실행되지 않습니다. 결과적으로 토스트가 사라지지 않고 고정됩니다. 토스트AnimatedVisibility인근(항상 컴포저블 트리에 존재)으로 이동해 주세요.아래 diff처럼 토스트 아래에
LaunchedEffect(showToast)를 추가하고,showDialog블록 내부의 것을 제거하면 해결됩니다.@@ AnimatedVisibility( visible = showToast, enter = slideInVertically( initialOffsetY = { -it }, animationSpec = tween(durationMillis = 2000) ), exit = slideOutVertically( targetOffsetY = { -it }, animationSpec = tween(durationMillis = 2000) ), modifier = Modifier .align(Alignment.TopCenter) .padding(horizontal = 20.dp, vertical = 16.dp) .zIndex(2f) ) { ToastWithDate( message = "게시글 신고를 완료했어요." ) } + // 토스트 자동 숨김 (3초) + LaunchedEffect(showToast) { + if (showToast) { + delay(3000) + showToast = false + } + } @@ if (showDialog) { Box( Modifier .fillMaxSize() .clickable { showDialog = false }) { Box(Modifier.align(Alignment.Center)) { DialogPopup( title = stringResource(R.string.delete_feed_dialog_title), description = stringResource(R.string.delete_feed_dialog_description), onConfirm = { showDialog = false isBottomSheetVisible = false feedDetailViewModel.deleteFeed(feedId) }, onCancel = { showDialog = false isBottomSheetVisible = false } ) - LaunchedEffect(showToast) { - if (showToast) { - delay(3000) - showToast = false - } - } } } }Also applies to: 505-511
🧹 Nitpick comments (8)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)
311-312: 잠금 아이콘 조건식 일관화 제안 (nullable 여부 확인 필요)현재
isLockIcon = feedDetail.isPublic == false는isPublic이Boolean?일 때만 의미가 있습니다. 다른 화면(GroupRoomRecruitScreen)은!detail.isPublic로 사용 중이라 타입과 동작이 일관되지 않습니다.isPublic이 non-null이면!feedDetail.isPublic이 더 간결하고, nullable이면 의도(unknown을 잠금으로 볼지, 공개로 볼지)에 맞춰 아래처럼 명확히 표현해 주세요.
- non-null일 때:
- isLockIcon = feedDetail.isPublic == false + isLockIcon = !feedDetail.isPublic
- nullable(Boolean?)이고 null을 공개로 취급:
- isLockIcon = feedDetail.isPublic == false + isLockIcon = (feedDetail.isPublic == false)
- nullable(Boolean?)이고 null도 잠금으로 취급:
- isLockIcon = feedDetail.isPublic == false + isLockIcon = (feedDetail.isPublic != true)타입 정의에 따라 어떤 것이 맞는지 확인 부탁드립니다.
app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt (1)
46-46: IME 패딩 도입 좋습니다. 루트 Box에 fillMaxSize 추가를 권장합니다
advancedImePadding()적용은 의도대로 키보드 겹침을 완화합니다. 동일 패턴을 쓰는 다른 화면(GroupRoomUnlockScreen)과의 일관성 및 오버레이 크기 보장을 위해 루트 Box에도fillMaxSize()를 함께 지정하는 편이 안전합니다.- Box(modifier = Modifier.advancedImePadding()) { + Box(modifier = Modifier.fillMaxSize().advancedImePadding()) {Also applies to: 110-110
app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupDatePicker.kt (3)
58-58: modifier 우선순위 개선 제안현재
modifier.width(48.dp)는 내부 width가 외부 modifier를 덮어씁니다. 외부에서 override 가능하도록 순서를 바꾸는 것이 Compose 관례에 더 가깝습니다.다음처럼 수정하면 외부 modifier가 마지막에 적용되어 우선됩니다:
- modifier = modifier.width(48.dp), + modifier = Modifier.width(48.dp).then(modifier),
110-113: Kotlin 컬렉션 max() 사용 — 최신 표준에 맞게 교체 필요
days.max()는 최신 Kotlin에서 deprecated입니다. List가 1..N 순으로 생성되므로 마지막 요소를 사용하는 것이 간단하고 빠릅니다.- selectedItem = day.coerceAtMost(days.max()), + selectedItem = day.coerceAtMost(days.last()),또는 안전하게
LocalDate.of(year, month, 1).lengthOfMonth()를 다시 계산해도 됩니다.
42-49: minDate/maxDate 경계년/월에서의 월/일 범위 미제한years는 경계 내로 잘려 있지만, months/days는 항상 전체 범위(1..12, 1..lengthOfMonth)를 보여줍니다. 경계년(=min/max year)에서 minDate, maxDate를 넘어선 날짜 선택이 가능해질 수 있습니다. 실제 선택 시 별도 클램핑이 없으므로 out-of-range 선택을 막으려면 months/days도 경계에 맞춰 제한하는 편이 안전합니다.
경계년/월일 때 유효한 months/days만 노출하도록 계산식을 분기하는 것을 권장합니다. 필요하시면 해당 분기 로직 패치를 제안드리겠습니다.
Also applies to: 80-119
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (1)
273-275: 중복 가드 간소화 가능
canLoadMoreCurrentTab에 이미!isPullToRefreshing과 다른 가드가 반영되므로, 아래 추가 체크는 중복입니다. 단일 진입 가드로 단순화하면 상태 플래그 분기 실수를 줄일 수 있습니다.- if (!_uiState.value.canLoadMoreCurrentTab || _uiState.value.isRefreshing) return + if (!_uiState.value.canLoadMoreCurrentTab) returnapp/src/main/java/com/texthip/thip/ui/feed/screen/FeedWriteScreen.kt (2)
122-131: Photo Picker 계약을 remainingSlots로 매번 재생성 — 불필요한 launcher 재등록 오버헤드
rememberLauncherForActivityResult는 contract 변경 시 재등록이 발생합니다.remainingSlots에 따라 contract를 매번 바꾸기보다는 최대치(3)로 고정하고, 결과에서take(remainingSlots)로 잘라내는 편이 안정적입니다. 또한 음수 슬럿을 방지하는 클램프를 권장합니다.- val remainingSlots = 3 - uiState.currentImageCount + val remainingSlots = (3 - uiState.currentImageCount).coerceAtLeast(0) - // Android 13+ Photo Picker (개수 제한 지원) - 최소 2로 설정하여 API 제약 회피 + // Android 13+ Photo Picker (개수 제한 지원) - contract는 최대치(3)로 고정 val photoPickerLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.PickMultipleVisualMedia(maxItems = maxOf(2, remainingSlots)) + contract = ActivityResultContracts.PickMultipleVisualMedia(maxItems = 3) ) { uris -> - if (uris.isNotEmpty() && remainingSlots > 0) { - onAddImages(uris.take(remainingSlots)) - } + if (remainingSlots > 0 && uris.isNotEmpty()) onAddImages(uris.take(remainingSlots)) }이렇게 하면 recompose 시 contract 변경으로 인한 재등록 가능성을 제거할 수 있습니다.
217-223: Photo Picker 사용 조건 완화(슬롯 1개 남아도 사용)Android 13+에서 슬롯이 1개만 남아도
PickMultipleVisualMedia를 그대로 사용할 수 있습니다. 굳이 레거시로 폴백하지 말고 조건을 단순화하면 UX 일관성이 좋아집니다.- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && remainingSlots > 1) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && remainingSlots > 0) { photoPickerLauncher.launch( PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) ) - } else { + } else if (remainingSlots > 0) { legacyImagePickerLauncher.launch("image/*") }참고: contract를 3으로 고정했다면 Photo Picker는 항상 열리고, 결과는
take(remainingSlots)로 잘립니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (9)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedWriteScreen.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupDatePicker.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt(0 hunks)app/src/main/java/com/texthip/thip/ui/group/makeroom/screen/GroupMakeRoomScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomRecruitScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomUnlockScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt(2 hunks)
💤 Files with no reviewable changes (1)
- app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupRoomDurationPicker.kt
🔇 Additional comments (6)
app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (1)
82-83: 본문 영역 여백/가독성 개선 변경사항 LGTM하단 패딩 추가와
Text의modifier = Modifier.fillMaxWidth()간소화는 레이아웃 안정성과 가독성에 긍정적입니다. 동작 영향 없이 의도대로 개선되었습니다.Also applies to: 94-94
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomRecruitScreen.kt (1)
285-285: “참여 인원” 좌측 패딩 50.dp 조정 LGTM시각적 밸런스가 좋아졌습니다. 다양한 화면 폭에서도 안정적인 간격이 필요하면, 추후
Row내 정렬/가중치로의 전환을 고려해볼 수 있습니다.app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomUnlockScreen.kt (1)
39-39: 루트 컨테이너 IME 패딩 적용 LGTM
advancedImePadding()를 루트 Box에 추가하여 키보드 표시 시 입력 박스가 가려지는 문제를 방지합니다. 기존 포커스/키보드 제어 로직과도 충돌 없습니다.Also applies to: 100-100
app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupDatePicker.kt (1)
31-36: 모든 GroupDatePicker 호출부가 명명 인자를 사용하므로 위치 인자 컴파일 에러 없음– 실행된 스크립트 검증 결과,
GroupDatePicker(호출부 전부가
selectedDate = …,minDate = …등의 명명 인자를 사용하고 있음이 확인되었습니다.
– 위치 인자를 사용하는 호출부가 없으므로 modifier 파라미터 순서 변경에 따른 컴파일 오류 우려가 없습니다.app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (1)
35-36: Pull-To-Refresh 중 무한 스크롤 비활성화 — 적절한 가드 추가
!isPullToRefreshing조건 추가로 당기는 새로고침 중에는 load more가 트리거되지 않도록 확실히 막습니다. 👍app/src/main/java/com/texthip/thip/ui/feed/screen/FeedWriteScreen.kt (1)
165-171: 바깥 터치 시 키보드 해제 UX 추가 — 좋습니다
pointerInput+focusManager.clearFocus()로 입력 경험이 개선되었습니다.
| val legacyImagePickerLauncher = rememberLauncherForActivityResult( | ||
| contract = ActivityResultContracts.GetMultipleContents() | ||
| ) { uris: List<Uri> -> | ||
| ) { uris -> | ||
| if (uris.isNotEmpty()) { | ||
| onAddImages(uris) | ||
| } | ||
| } |
There was a problem hiding this comment.
레거시 선택 경로에서 개수 제한 미적용
현재 GetMultipleContents 결과를 그대로 onAddImages(uris)로 넘겨 제한(최대 3장, 남은 슬롯 수)이 깨질 수 있습니다. ViewModel에서 방어 코드를 갖고 있더라도 UI 레이어에서 한 번 더 제한을 적용하는 것이 안전합니다.
- ) { uris ->
- if (uris.isNotEmpty()) {
- onAddImages(uris)
- }
- }
+ ) { uris ->
+ if (uris.isNotEmpty() && remainingSlots > 0) {
+ onAddImages(uris.take(remainingSlots))
+ }
+ }🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/screen/FeedWriteScreen.kt around
lines 134-140, the legacy GetMultipleContents launcher forwards all returned
URIs to onAddImages, bypassing the UI-level max-image and remaining-slot limits;
restrict the forwarded list by computing the allowed count (e.g., allowed =
min(uris.size, remainingSlotsOrMaxImages)) and call onAddImages with only
uris.take(allowed), optionally notify the user if some selections were dropped.
➕ 이슈 링크
🔎 작업 내용
📸 스크린샷
😢 해결하지 못한 과제
비밀글일때 lock 아이콘 구현 (해당 책으로 모집중인 모임방)
피드 게시글에서 자세히보기 화면 보다가 뒤로가기 하면 피드 스크롤 상태 초기화 문제
📢 리뷰어들에게
Summary by CodeRabbit
New Features
Bug Fixes
Style