Skip to content

REFACTOR] QA 반영 2차#114

Merged
Nico1eKim merged 6 commits intoTHIP-TextHip:developfrom
rbqks529:refactor/#102_QA_2
Aug 20, 2025
Merged

REFACTOR] QA 반영 2차#114
Nico1eKim merged 6 commits intoTHIP-TextHip:developfrom
rbqks529:refactor/#102_QA_2

Conversation

@rbqks529
Copy link
Collaborator

@rbqks529 rbqks529 commented Aug 20, 2025

➕ 이슈 링크


🔎 작업 내용

  • 갤러리 이미지 개수제한 구현
  • 모집중인 모임방 패딩 수정
  • 모임방 생성 비밀번호 입력시 키보드 패딩 추가
  • 전체 피드 게시글 글자 패딩 수정
  • 자물쇠 아이콘 연결

📸 스크린샷


😢 해결하지 못한 과제

  • 비밀글일때 lock 아이콘 구현 (해당 책으로 모집중인 모임방)

  • 피드 게시글에서 자세히보기 화면 보다가 뒤로가기 하면 피드 스크롤 상태 초기화 문제


📢 리뷰어들에게

  • 참고해야 할 사항들을 적어주세요

Summary by CodeRabbit

  • New Features

    • Android 13+ 사진 선택기 지원(여러 장 선택, 남은 슬롯 제한 적용).
    • 비공개 피드에 잠금 아이콘 표시.
  • Bug Fixes

    • 당겨서 새로고침 중 추가 로딩 방지.
    • 키보드 표시 시 화면 가림 현상 완화(입력 영역 패딩 적용).
  • Style

    • 그룹 모집/방만들기/잠금 화면 및 저장한 피드 카드의 여백·정렬 조정.
    • 그룹 기간 선택기의 너비 분배 조정으로 가독성 개선.

@coderabbitai
Copy link

coderabbitai bot commented Aug 20, 2025

Walkthrough

Feed 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

Cohort / File(s) Summary
Feed 댓글 화면 아이콘
app/.../ui/feed/screen/FeedCommentScreen.kt
ActionBarButton 호출에 isLockIcon 추가(값: feedDetail.isPublic == false).
Feed 작성 화면: 포토 피커 & 포커스
app/.../ui/feed/screen/FeedWriteScreen.kt
Android 13+ Photo Picker 도입(PickMultipleVisualMedia, maxItems=remainingSlots), 하위호환 GetMultipleContents 런처 병행. 남은 슬롯 계산 및 URI 자르기, 외부 탭 시 키보드 해제.
피드 로드 조건 강화
app/.../ui/feed/viewmodel/FeedViewModel.kt
canLoadMoreAllFeeds / canLoadMoreMyFeeds에 !isPullToRefreshing 추가.
그룹 날짜 선택 컴포넌트
app/.../ui/group/makeroom/component/GroupDatePicker.kt, app/.../ui/group/makeroom/component/GroupRoomDurationPicker.kt
GroupDatePicker 시그니처 변경: modifier를 첫 인자로 이동하고 내부 year wheel에 전달된 modifier 사용. DurationPicker에서 두 DatePicker의 .weight(1f) 제거.
그룹 화면 IME 패딩 적용
app/.../ui/group/makeroom/screen/GroupMakeRoomScreen.kt, app/.../ui/group/room/screen/GroupRoomUnlockScreen.kt
루트 Box에 advancedImePadding() 적용.
그룹 모집 화면 패딩 조정
app/.../ui/group/room/screen/GroupRoomRecruitScreen.kt
"참여 인원" 섹션 좌측 패딩 90.dp → 50.dp.
저장된 피드 카드 간격
app/.../ui/mypage/component/SavedFeedCard.kt
콘텐츠 Column 하단 패딩 16.dp 추가, 경미한 포맷팅 정리.

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)
Loading
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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

  • [API] 피드 API 구현 #100: FeedDetailResponse에 isPublic 추가와 연계되어 FeedCommentScreen의 잠금 아이콘 표시 변경과 직접적으로 연결됨.
  • [refactor]: PR Merge 오류 수정 #104: FeedViewModel의 새로고침/로드 상태 처리 변경으로, 본 PR의 canLoadMore 조건 강화와 같은 영역을 수정.
  • [REFACTOR] 1차 QA #107: FeedCommentScreen 전반 리팩터링과 함께 동일 파일 내 액션바/내비게이션 변경이 있어 본 PR의 isLockIcon 추가와 관련.

Suggested reviewers

  • JJUYAAA
  • Nico1eKim

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 자동 해제 LaunchedEffectif (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 == falseisPublicBoolean?일 때만 의미가 있습니다. 다른 화면(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) return
app/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.

📥 Commits

Reviewing files that changed from the base of the PR and between 08e1ac0 and 0c33e27.

📒 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

하단 패딩 추가와 Textmodifier = 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()로 입력 경험이 개선되었습니다.

Comment on lines +134 to 140
val legacyImagePickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetMultipleContents()
) { uris: List<Uri> ->
) { uris ->
if (uris.isNotEmpty()) {
onAddImages(uris)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

레거시 선택 경로에서 개수 제한 미적용

현재 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.

@Nico1eKim Nico1eKim merged commit 82ae5e6 into THIP-TextHip:develop Aug 20, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants