Skip to content

[REFACTOR] 완료된 모임방 구현, 모임방 사용성 개선작업#139

Merged
Nico1eKim merged 9 commits intoTHIP-TextHip:developfrom
Nico1eKim:refactor/#138-completed_rooms
Sep 24, 2025
Merged

[REFACTOR] 완료된 모임방 구현, 모임방 사용성 개선작업#139
Nico1eKim merged 9 commits intoTHIP-TextHip:developfrom
Nico1eKim:refactor/#138-completed_rooms

Conversation

@Nico1eKim
Copy link
Member

@Nico1eKim Nico1eKim commented Sep 22, 2025

➕ 이슈 링크


🔎 작업 내용

  • 내 모임방에서 아직 모집중인 모임방은 percent대신 deadline이 뜨도록 수정했습니다
  • 완료된 모임방 구현 완료했습니다
  • 완료된 모임방 -> kebab 없앰 (추후 방 나가기 구현되면 다시 보이게 하고 toast message 띄우게 수정)
  • 기록장 -> 기록 좋아요, 수정, 삭제 등 불가하게 수정; 댓글 바텀시트에서 textfield 없앰; 댓글 및 답글 달기, 수정, 좋아요 등 불가하게 수정;
  • 오늘의 한마디 -> textfield 없앰; kebab 누르면 toast message 뜨게 수정
  • 검색 화면에서 전체 모임방을 보여주는 플로우를 추가했습니다
  • 최근 생성된 독서 모임방 캐러셀을 추가하고, 제일 첫번째에 보여지고, 캐러셀이 무한으로 돌아가도록 수정했습니다

📸 스크린샷


😢 해결하지 못한 과제

  • [] TASK


📢 리뷰어들에게

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

Summary by CodeRabbit

  • New Features

    • 완료된(만료) 모임방 읽기 전용 모드 전면 적용: 입력/액션 비활성화 및 안내 토스트 노출.
    • 전체 모임방 보기 기능 추가 및 검색에서 "전체" 옵션 지원.
    • 최근 생성 섹션 추가로 최신 모임방 확인 가능.
  • UI Changes

    • 내 방 카드: 마감일이 있으면 진행률 대신 “시작까지”+날짜 표시.
    • 그룹 화면에 전체 보기 버튼(전체 모임방 둘러보기) 및 관련 문구 추가.

@coderabbitai
Copy link

coderabbitai bot commented Sep 22, 2025

Walkthrough

완료(만료) 플래그(isExpired)와 마감일(deadlineDate)을 모델·서비스·내비게이션·UI 전반에 추가·전파하여 만료된 모임방에 대해 읽기 전용 동작(입력 숨김, 액션 대신 토스트)을 적용하고, 검색/내비게이션에 전체 보기(viewAll/isAllCategory) 및 최근 생성 섹션을 도입했습니다.

Changes

Cohort / File(s) Summary
Models: room responses
app/src/main/java/com/texthip/thip/data/model/rooms/response/JoinedRoomListResponse.kt, app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomMainResponse.kt
JoinedRoomResponse@SerialName("deadlineDate") val deadlineDate: String? = null 추가. RoomMainList@SerialName("recentRoomList") val recentRoomList: List<RoomMainResponse> = emptyList() 추가.
Service & Repository
app/src/main/java/com/texthip/thip/data/service/RoomsService.kt, app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt
searchRooms API에 @Query("isAllCategory") isAllCategory: Boolean = false 추가(서비스 → 리포지토리로 전달). getRoomsPlaying 엔드포인트 경로 변경: rooms/{roomId}/playingrooms/{roomId}.
Navigation: routes & extensions & graph
app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt, app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt, app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt
Search → data class(viewAll), Room/RoomChat/NoteisExpired: Boolean = false 추가. 내비게이션 함수들에 isExpired/viewAll 인자 추가 및 라우트 전달. GroupSearchViewModel 도입 및 viewAll 처리 연결.
Group screens: room/chat/note/done/myroom
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt, .../GroupRoomChatScreen.kt, .../note/screen/GroupNoteScreen.kt, .../done/screen/GroupDoneScreen.kt, .../myroom/component/GroupMainCard.kt
여러 Screen/Content에 isExpired: Boolean = false 파라미터 추가 및 전파. 만료 시 입력/액션 비활성화, 토스트 표시 흐름 추가. GroupDoneScreenonRoomClick 콜백 추가(만료 플래그 전달). GroupMainCarddeadlineDate 존재 시 진행률 대신 "until_start" 표시로 조건 렌더링.
Group UI components: comments, bottom sheet, sections, pager
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt, .../CommentSection.kt, .../myroom/component/GroupDeadlineRoomSection.kt
CommentBottomSheetisExpired 파라미터 추가. CommentSectionisExpiredonShowToast 추가해 만료 시 액션을 토스트로 대체. 카루셀에 최근 생성 섹션 추가 및 페이저 무한 스와이프(모듈로 매핑) 로직 도입.
Group search: UI & viewmodel
app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt, .../screen/GroupSearchScreen.kt, .../component/GroupFilteredSearchResult.kt, .../component/GroupRecentSearch.kt
뷰모델에 isAllCategory 추가, onViewAllRooms 동작 및 검색/로드 로직에 isAllCategory 반영. 검색화면/컴포넌트에 onViewAllRooms/onViewAllRoomsClick 경로 추가. 장르 칩에 "All" 옵션 추가 및 인덱스 매핑 변경.
Feed / Group home: empty bar & screen wiring
app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt, app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt
EmptyMySubscriptionBar를 public으로 확장해 modifier, text, onClick 파라미터 추가. GroupScreen에 전체 모임방 보기 콜백(onNavigateToGroupSearchAllRooms) 및 해당 바 삽입.
Strings / resources
app/src/main/res/values/strings.xml
until_start, expired_room_read_only_message, look_around_all_rooms, look_around_all_rooms_title, all, room_section_recent 등 문자열 추가; deadline_string 제거.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Done as GroupDoneScreen
  participant Nav as NavController
  participant Routes as GroupRoutes
  participant Room as GroupRoomScreen
  Note right of Done: 카드 클릭 시 onRoomClick 전달

  User->>Done: 클릭(roomId)
  Done->>Nav: navigateToGroupRoom(roomId, isExpired=true)
  Nav->>Routes: build route Room(roomId, isExpired=true)
  Routes->>Room: Compose with isExpired=true
  Room-->>User: 읽기 전용 UI (입력 숨김, 액션 토스트)
Loading
sequenceDiagram
  actor User
  participant Chat as GroupRoomChatContent
  participant Toast as ToastHost
  participant Bottom as BottomSheet

  User->>Chat: 카드 클릭
  alt isExpired == true
    Chat->>Toast: show(expired_room_read_only_message)
  else isExpired == false
    Chat->>Bottom: openBottomSheet()
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • rbqks529
  • JJUYAAA

Poem

귀가 쫑긋, 당근 들고 왔어요 🐇
만료 깃발이면 조용히, 읽기만 해요.
마감일이 반짝이면 "until_start" 노래 불러요.
클릭은 토스트로, 채팅은 잠깐 쉬어요.
라우트 타고 반짝이는 코드, 당근 축하해요 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.42% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed 제목 “[REFACTOR] 완료된 모임방 구현, 모임방 사용성 개선작업”은 변경 사항의 핵심인 완료된 모임방 기능 구현과 모임방 사용성 개선을 명확히 요약하고 있으며 불필요한 정보나 모호한 표현 없이 간결하게 작성되었습니다.
Linked Issues Check ✅ Passed 링크된 이슈 #138의 체크리스트인 완료된 모임방 구현, 전체 모임방 보기 플로우 추가, 추천(최근 생성) 캐러셀 추가 세 가지 항목이 모두 코드 변경과 UI 수정으로 충실히 반영되어 있습니다.
Out of Scope Changes Check ✅ Passed 전체적으로 변경 사항이 링크된 이슈의 세 가지 목표(완료된 모임방 구현, 전체 모임방 보기 플로우, 추천 캐러셀) 지원을 위한 구현으로 보이며, 해당 범위를 벗어난 불필요한 코드 변경은 발견되지 않습니다.
Description Check ✅ Passed 작성된 본문은 이슈 링크, 작업 내용, 스크린샷, 해결하지 못한 과제, 리뷰어 안내의 템플릿 구조를 모두 포함하고 있으며 작업 내용도 자세히 기술되어 있어 템플릿을 잘 따르고 있습니다.
✨ 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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 2

🧹 Nitpick comments (12)
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt (3)

234-235: TODO 주석에 대한 구체적인 계획 필요

// TODO: 방 삭제 기능 추가 시 변경 주석이 있는데, 현재 로직(!isExpired || !isOwner)이 의도한 동작인지 명확하지 않습니다. 만료된 방의 소유자가 kebab 메뉴를 볼 수 없도록 하는 것이 맞는지 확인이 필요합니다.

이슈를 생성하거나 더 구체적인 설명을 주석에 추가하는 것을 권장합니다.


114-114: 에러 메시지 색상 일관성

에러 상태에서 colors.White를 사용하고 있는데, 다른 화면들과 일관성 있게 에러 메시지는 colors.Red를 사용하는 것이 좋을 것 같습니다.

-                Text(text = state.message, color = colors.White)
+                Text(text = state.message, color = colors.Red)

62-62: isExpired 기본값을 호출에서 명시적으로 전달하세요

app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt(라인 74)에서 isExpired: Boolean = false로 정의되어 있습니다. 다음 호출들이 값을 생략해 기본값(false)을 사용합니다: app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt:94, 233. 일부 호출은 명시적으로 전달함(isExpired = true @148, isExpired = false @167). 생략이 의도된 동작인지 확인하고, 의도가 아니면 명시적으로 true/false를 전달하거나 주석으로 의도 표기.

app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt (1)

74-76: isExpired 파라미터 전달 구조 개선 고려

세 개의 네비게이션 함수에서 isExpired 파라미터를 추가했는데, 향후 더 많은 상태 정보가 필요할 경우 파라미터가 계속 늘어날 수 있습니다.

향후 확장성을 고려하여 상태 정보를 담는 data class를 만들어 사용하는 방법을 고려해보세요:

data class RoomNavigationState(
    val isExpired: Boolean = false,
    // 향후 추가 상태들
)

fun NavHostController.navigateToGroupRoom(
    roomId: Int, 
    state: RoomNavigationState = RoomNavigationState()
) {
    navigate(GroupRoutes.Room(roomId, state.isExpired))
}

Also applies to: 84-86, 89-96

app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomChatScreen.kt (3)

77-77: 중복된 토스트 메시지 설정 로직

expiredRoomMessage를 통해 토스트를 표시하는 로직이 onShowExpiredRoomToast 콜백에 중복되어 있습니다.

토스트 메시지 설정 로직을 ViewModel로 이동하거나, 토스트 타입에 EXPIRED_ROOM을 추가하여 일관된 방식으로 처리하는 것이 좋겠습니다:

- val expiredRoomMessage = stringResource(R.string.expired_room_read_only_message)
  
  LaunchedEffect(key1 = Unit) {
      viewModel.eventFlow.collectLatest { event ->
          when (event) {
              is GroupRoomChatEvent.ShowToast -> {
                  when (event.type) {
+                     ToastType.EXPIRED_ROOM -> {
+                         toastMessage = stringResource(R.string.expired_room_read_only_message)
+                         toastColor = colorWhite
+                     }
                      // ... 다른 케이스들

Also applies to: 133-136


275-281: 만료된 방에서의 메뉴 클릭 처리 개선 필요

onMenuClick 핸들러에서 isExpired 체크 로직이 있지만, 사용자에게 더 명확한 피드백을 제공할 수 있습니다.

메뉴 아이콘 자체를 비활성화 상태로 표시하거나, 시각적 힌트를 제공하는 것이 좋겠습니다:

CardCommentGroup(
    data = message,
    isMenuEnabled = !isExpired,  // 메뉴 활성화 상태 전달
    onMenuClick = {
        if (isExpired) {
            onShowExpiredRoomToast()
        } else {
            selectedMessage = message
            isBottomSheetVisible = true
        }
    }
)

288-295: CommentTextField 숨김 처리 확인

만료된 방에서 CommentTextField를 숨기는 것은 적절하나, 사용자에게 왜 입력이 불가능한지 안내 메시지를 표시하는 것이 좋을 것 같습니다.

if (!isExpired) {
    CommentTextField(
        input = inputText,
        hint = stringResource(R.string.group_room_chat_hint),
        onInputChange = onInputTextChanged,
        onSendClick = onSendClick
    )
+} else {
+    Box(
+        modifier = Modifier
+            .fillMaxWidth()
+            .padding(horizontal = 20.dp, vertical = 10.dp),
+        contentAlignment = Alignment.Center
+    ) {
+        Text(
+            text = stringResource(R.string.expired_room_read_only_message),
+            style = typography.info_r400_s12_h24,
+            color = colors.Grey01
+        )
+    }
}
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (1)

147-149: 완료된 방 네비게이션 시 isExpired = true 하드코딩

GroupDoneScreen에서 모든 방을 isExpired = true로 네비게이션하고 있는데, 실제 방의 만료 상태를 서버에서 받아와 전달하는 것이 더 정확할 것 같습니다.

서버에서 각 방의 실제 만료 상태를 받아와서 전달하도록 개선이 필요합니다. 현재는 완료된 방 목록에 있다고 해서 모두 만료된 것으로 처리하고 있습니다.

app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (4)

197-197: 토스트 메시지 중복 선언

expiredRoomMessageGroupNoteContent 내부에서 선언되었지만, showExpiredToast 람다에서만 사용됩니다.

토스트 메시지를 한 곳에서 관리하도록 개선:

- val expiredRoomMessage = stringResource(R.string.expired_room_read_only_message)
-
  // ...
  
- val showExpiredToast = {
-     toastMessage = expiredRoomMessage
-     showToast = true
- }
+ fun showExpiredToast() {
+     toastMessage = stringResource(R.string.expired_room_read_only_message)
+     showToast = true
+ }

Also applies to: 374-377


388-391: 만료 상태 체크 로직 중복

여러 곳에서 if (isExpired) showExpiredToast() else { ... } 패턴이 반복됩니다.

공통 함수로 추출하여 중복 제거:

inline fun executeIfNotExpired(
    isExpired: Boolean,
    onExpired: () -> Unit,
    action: () -> Unit
) {
    if (isExpired) onExpired() else action()
}

// 사용 예시:
onLongPress = {
    executeIfNotExpired(
        isExpired = isExpired,
        onExpired = showExpiredToast,
        action = { selectedPostForMenu = post }
    )
}

Also applies to: 397-400, 411-415, 420-423


493-508: FAB 버튼 숨김 처리 개선

만료된 방에서 FAB를 완전히 숨기는 대신, 비활성화 상태로 표시하고 클릭 시 안내 메시지를 보여주는 것이 더 나은 UX일 수 있습니다.

- if (!isExpired) {
-     ExpandableFloatingButton(
-         menuItems = listOf(
-             // ...
-         )
-     )
- }
+ ExpandableFloatingButton(
+     isEnabled = !isExpired,
+     onDisabledClick = { showExpiredToast() },
+     menuItems = listOf(
+         // ...
+     )
+ )

652-652: 토스트 색상 로직 개선

isErrorToast 플래그 대신 토스트 타입 enum을 사용하면 더 확장 가능한 구조가 될 것 같습니다.

enum class ToastType {
    SUCCESS, ERROR, WARNING, INFO
}

// 색상 결정 로직
val toastColor = when(toastType) {
    ToastType.ERROR -> colors.Red
    ToastType.WARNING -> colors.Yellow
    else -> colors.White
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira 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 f280150 and 5cd88cf.

📒 Files selected for processing (13)
  • app/src/main/java/com/texthip/thip/data/model/rooms/response/JoinedRoomListResponse.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/data/service/RoomsService.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/done/screen/GroupDoneScreen.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMainCard.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt (6 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt (4 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (10 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomChatScreen.kt (6 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt (5 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (7 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt (2 hunks)
  • app/src/main/res/values/strings.xml (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-01T07:19:10.981Z
Learnt from: rbqks529
PR: THIP-TextHip/THIP-Android#30
File: app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt:108-112
Timestamp: 2025-07-01T07:19:10.981Z
Learning: The user rbqks529 prefers to extract hardcoded UI strings to StringResource files (strings.xml) rather than keeping them inline in Compose components, which is a good practice for maintainability and localization in Android development.

Applied to files:

  • app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt
🧬 Code graph analysis (5)
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt (1)
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (1)
  • onEvent (59-71)
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomChatScreen.kt (1)
app/src/main/java/com/texthip/thip/ui/common/forms/CommentTextField.kt (1)
  • CommentTextField (42-152)
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (3)
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteViewModel.kt (1)
  • onEvent (131-175)
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (1)
  • onEvent (59-71)
app/src/main/java/com/texthip/thip/ui/common/buttons/ExpandableFloatingButton.kt (1)
  • ExpandableFloatingButton (45-114)
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt (1)
app/src/main/java/com/texthip/thip/ui/common/forms/CommentTextField.kt (1)
  • CommentTextField (42-152)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (2)
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt (1)
  • GroupRoomScreen (59-118)
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomChatScreen.kt (1)
  • GroupRoomChatScreen (59-138)
🔇 Additional comments (14)
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt (3)

54-54: 기본값을 명시적으로 설정한 좋은 API 설계!

isExpired: Boolean = false 파라미터 추가가 잘 구현되었습니다. Compose 컴포넌트에 필요한 파라미터만 전달하는 것이 모범 사례이며, 기본값을 false로 설정하여 기존 호출 코드와의 호환성을 유지했습니다.


149-175: 만료된 모임방에서 댓글 입력 차단 로직이 명확합니다.

if (!isExpired) 조건을 사용하여 완료된 모임방에서는 CommentTextField를 렌더링하지 않도록 구현했습니다. 이는 읽기 전용 모드를 명확하게 구현하는 좋은 방법입니다.


126-126: isExpired 파라미터가 하위 컴포넌트까지 일관성 있게 전달됩니다.

CommentLazyListCommentSection으로 isExpired 상태가 올바르게 전파되어 전체적인 만료 모드 처리가 일관성 있게 구현되었습니다.

Also applies to: 232-232, 273-273

app/src/main/res/values/strings.xml (2)

178-178: 적절한 문자열 리소스 추가!

until_start 문자열이 추가되어 마감일 표시 기능에 사용됩니다. 하드코딩된 문자열 대신 리소스를 사용하는 것이 유지보수와 지역화에 좋은 접근입니다.


260-260: 만료된 모임방 안내 메시지가 명확합니다.

expired_room_read_only_message 문자열이 사용자에게 완료된 모임방에서는 조회만 가능하다는 것을 명확하게 안내합니다. 사용자 경험을 고려한 좋은 메시지입니다.

app/src/main/java/com/texthip/thip/data/model/rooms/response/JoinedRoomListResponse.kt (1)

22-22: 데이터 모델에 마감일 필드 추가가 적절합니다.

deadlineDate: String? = null 필드가 추가되어 UI에서 마감일 정보를 표시할 수 있게 되었습니다. nullable과 기본값 설정으로 기존 API와의 호환성을 유지했습니다.

app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMainCard.kt (2)

125-158: 조건부 렌더링 로직이 깔끔하게 구현되었습니다.

data.deadlineDate의 존재 여부에 따라 진행도 표시와 시작까지 남은 시간 표시를 선택적으로 렌더링하는 로직이 명확합니다. 사용자에게 상황에 맞는 정보를 제공하는 좋은 UX입니다.


172-175: 배경 modifier 포맷팅이 일관성 있게 개선되었습니다.

멀티라인으로 RoundedCornerShape 파라미터를 명시적으로 표현하여 코드 가독성이 향상되었습니다.

app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt (1)

34-34: 네비게이션 라우트에 만료 상태 지원이 잘 추가되었습니다.

Room, RoomChat, Note 라우트에 isExpired: Boolean = false 파라미터가 추가되어 만료된 모임방으로의 네비게이션을 지원합니다. 기본값 설정으로 기존 네비게이션 코드와의 호환성을 유지했습니다.

Also applies to: 40-40, 43-48

app/src/main/java/com/texthip/thip/ui/group/done/screen/GroupDoneScreen.kt (1)

40-40: 완료된 모임방 클릭 이벤트 처리가 올바르게 구현되었습니다.

onRoomClick: (Int) -> Unit 콜백이 추가되어 완료된 모임방을 클릭했을 때 해당 모임방으로 네비게이션할 수 있게 되었습니다. 콜백이 컴포넌트 계층을 통해 적절히 전파되고 있습니다.

Also applies to: 59-59, 124-124

app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt (3)

11-11: 문자열 리소스 사용이 우수한 관례를 따릅니다.

이전 학습 내용에 따르면, 사용자는 하드코딩된 문자열 대신 StringResource 사용을 선호하므로 stringResource(R.string.expired_room_read_only_message) 사용이 적절합니다.

Also applies to: 14-14, 31-31


23-23: 만료 모드 지원을 위한 파라미터 추가가 적절합니다.

isExpired: Boolean = falseonShowToast: (String) -> Unit = {} 파라미터가 추가되어 완료된 모임방에서의 상호작용을 제어할 수 있게 되었습니다.

Also applies to: 29-29


44-67: 만료된 모임방에서의 상호작용 차단이 일관성 있게 구현되었습니다.

답글, 좋아요, 길게 누르기 등 모든 상호작용에서 isExpired 조건을 확인하여 토스트 메시지를 표시하거나 실제 액션을 수행하도록 구현되었습니다. 사용자에게 일관된 경험을 제공합니다.

Also applies to: 78-99

app/src/main/java/com/texthip/thip/data/service/RoomsService.kt (1)

111-111: API 엔드포인트 변경 확인 — 클라이언트 일관성 확보

리포지토리 내부 Kotlin 검색 결과 '/rooms/{roomId}/playing' 사용처가 없고 RoomsService.kt가 @get("rooms/{roomId}")로 업데이트되어 클라이언트 쪽 변경은 완료되었습니다.

Comment on lines +290 to 292
val feedViewModel: FeedViewModel =
hiltViewModel(navController.getBackStackEntry(MainTabRoutes.Group))
val feedUiState by feedViewModel.uiState.collectAsState()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

FeedViewModel 인스턴스 생성 위치

FeedViewModelMainTabRoutes.Group의 백스택 엔트리에서 가져오고 있는데, 해당 엔트리가 없을 경우 크래시가 발생할 수 있습니다.

- val feedViewModel: FeedViewModel =
-     hiltViewModel(navController.getBackStackEntry(MainTabRoutes.Group))
+ val feedViewModel: FeedViewModel = try {
+     hiltViewModel(navController.getBackStackEntry(MainTabRoutes.Group))
+ } catch (e: IllegalArgumentException) {
+     hiltViewModel()  // 기본 인스턴스 생성
+ }
📝 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.

Suggested change
val feedViewModel: FeedViewModel =
hiltViewModel(navController.getBackStackEntry(MainTabRoutes.Group))
val feedUiState by feedViewModel.uiState.collectAsState()
val feedViewModel: FeedViewModel = try {
hiltViewModel(navController.getBackStackEntry(MainTabRoutes.Group))
} catch (e: IllegalArgumentException) {
hiltViewModel() // 기본 인스턴스 생성
}
val feedUiState by feedViewModel.uiState.collectAsState()
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt
around lines 290-292, the code assumes
navController.getBackStackEntry(MainTabRoutes.Group) always exists which can
crash if the entry is absent; change it to safely obtain the NavBackStackEntry
(e.g., check navController.getBackStackEntry(...) existence or use
navController.getBackStackEntry(...) inside a try/catch or runCatching) and fall
back to a safe owner (like navController.currentBackStackEntry or the local
composable owner) before calling hiltViewModel, ensuring you only call
hiltViewModel with a non-null owner to prevent the crash.

@Nico1eKim Nico1eKim changed the title [REFACTOR] 완료된 모임방 구현 [REFACTOR] 완료된 모임방 구현, 모임방 사용성 개선작업 Sep 22, 2025
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: 4

🧹 Nitpick comments (14)
app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt (2)

140-147: 접근성(a11y) 개선: 클릭 가능한 영역에 Role 지정

시맨틱 Role을 명시하면 보조기기에서 버튼으로 올바르게 인식합니다.

-            .clickable { onClick() }
+            .clickable(role = Role.Button, onClick = onClick)

필요 시 import:

import androidx.compose.ui.semantics.Role

50-56: 중첩 clickable 제거 제안 — 이중 터치 타겟/시맨틱 중복 가능성

현재 상위 BoxWithConstraints와 하위(Empty/Row) 둘 다 clickable입니다. 중첩 시 내부만 소비되어 UX 일관성이 깨질 수 있고 시맨틱이 중복됩니다. 상위의 clickable을 제거하고, 빈 상태는 EmptyMySubscriptionBar, 데이터가 있는 상태는 Row에만 클릭을 유지하는 편이 안전합니다.

 BoxWithConstraints(
     modifier = modifier
         .fillMaxWidth()
         .height(82.dp)
-        .clickable { onClick() }
 ) {

Row 쪽은 Role을 함께 지정해 주세요:

                 Row(
                     modifier = Modifier
                         .fillMaxWidth()
                         .height(58.dp)
-                        .clickable { onClick() },
+                        .clickable(role = Role.Button, onClick = onClick),
                     verticalAlignment = Alignment.CenterVertically
                 ) {

Also applies to: 84-90

app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt (4)

53-60: 선택 인덱스 범위 안전화(경계값 처리)

selectedGenreIndex가 [-1, genres.lastIndex] 범위를 벗어나면 하이라이트가 사라지는 등 미묘한 UX가 생길 수 있습니다. 안전하게 클램프하세요.

-    val finalSelectedIndex = if (selectedGenreIndex != -1) {
-        // 특정 장르가 선택되었다면, '전체' 칩 때문에 +1 된 인덱스를 사용
-        selectedGenreIndex + 1
-    } else {
-        // 특정 장르가 선택되지 않았다면, '전체' 칩(인덱스 0)을 선택
-        0
-    }
+    val safeIndex = selectedGenreIndex.coerceIn(-1, genres.lastIndex)
+    val finalSelectedIndex = if (safeIndex != -1) {
+        // 특정 장르가 선택되었다면, '전체' 칩 때문에 +1
+        safeIndex + 1
+    } else {
+        // 특정 장르가 선택되지 않았다면, '전체'(0)
+        0
+    }

113-117: 무한 스크롤 중복 호출 방지(로딩 중 재호출 게이트)

LaunchedEffect(shouldLoadMore)는 값 변화 때만 재실행되지만, 스크롤 경계에서 false→true가 잦으면 로딩 중 중복 호출이 발생할 여지가 있습니다. isLoadingMore를 키와 조건에 포함해 방지하세요.

-            LaunchedEffect(shouldLoadMore) {
-                if (shouldLoadMore) {
-                    onLoadMore()
-                }
-            }
+            LaunchedEffect(shouldLoadMore, isLoadingMore) {
+                if (shouldLoadMore && !isLoadingMore) {
+                    onLoadMore()
+                }
+            }

119-140: LazyColumn에 안정 키 부여로 스크롤/애니메이션 안정화

항목 키가 없으면 데이터 변경 시 스크롤 위치 보존/효율적인 재조합이 어려울 수 있습니다. roomId를 키로 지정하세요.

-            LazyColumn(state = listState, contentPadding = PaddingValues(bottom = 20.dp)) {
-                itemsIndexed(roomList) { index, room ->
+            LazyColumn(state = listState, contentPadding = PaddingValues(bottom = 20.dp)) {
+                itemsIndexed(items = roomList, key = { _, room -> room.roomId }) { index, room ->

210-216: Preview 2: "전체" 중복 생성됨 — 호출 규약과 불일치

컴포저블 내부에서 이미 '전체' 칩을 추가하므로 미리 전달하면 칩이 중복됩니다. Preview도 계약에 맞춰 수정하세요.

-            GroupFilteredSearchResult(
-                genres = listOf("전체", "소설", "에세이", "자기계발", "경제/경영", "과학"),
+            GroupFilteredSearchResult(
+                genres = listOf("소설", "에세이", "자기계발", "경제/경영", "과학"),

또한 KDoc에 “genres에는 '전체'를 포함하지 말 것(-1이 '전체' 의미)”을 명시하면 호출부 혼선을 줄일 수 있습니다.

app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupDeadlineRoomSection.kt (2)

103-105: 변수명 섀도잉으로 가독성 저하

상단의 scale(=0.94f)과 여기의 scale이 섀도잉됩니다. currentScale 등으로 분리하면 혼동을 줄일 수 있습니다.

-                val scale = if (isCurrent) 1f else 0.94f
+                val currentScale = if (isCurrent) 1f else 0.94f
@@
-                            scaleX = scale
-                            scaleY = scale
+                            scaleX = currentScale
+                            scaleY = currentScale

82-84: room_section_recent 리소스 확인 — 기본값 존재, 지역화 미등록

app/src/main/res/values/strings.xml:416에 최근 생성된 독서 모임방로 정의되어 있습니다; values-/strings.xml(다국어)에서는 해당 키가 없습니다 — 필요한 언어에 번역을 추가하세요.

app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt (2)

98-102: 포커스 해제 조건에 isAllCategory 포함 제안

"전체 보기" 진입 시에도 키보드/포커스를 내리는 편이 자연스럽습니다.

-    LaunchedEffect(uiState.isCompleteSearching) {
-        if (uiState.isCompleteSearching) {
+    LaunchedEffect(uiState.isCompleteSearching, uiState.isAllCategory) {
+        if (uiState.isCompleteSearching || uiState.isAllCategory) {
             focusManager.clearFocus()
         }
     }

170-191: 중복 조건 상수로 추출하여 드리프트 방지

uiState.isCompleteSearching || uiState.isAllCategory 조건이 2곳에 반복됩니다. 상수로 올려 재사용하세요.

+    val isCompleteOrAll = uiState.isCompleteSearching || uiState.isAllCategory
@@
-                    uiState.isCompleteSearching || uiState.isAllCategory -> {
+                    isCompleteOrAll -> {
@@
-        if (uiState.isCompleteSearching || uiState.isAllCategory) {
+        if (isCompleteOrAll) {

Also applies to: 196-201

app/src/main/java/com/texthip/thip/ui/group/search/component/GroupRecentSearch.kt (1)

39-40: "전체 모임 둘러보기" 액션 추가 👍 — 접근성 속성 보완 권장

Row 전체 클릭은 좋지만 역할/포커스 힌트가 없어 스크린리더 접근성이 떨어집니다. semantics로 버튼 역할을 부여하세요.

-        Row(
-            modifier = Modifier
-                .fillMaxWidth()
-                .clickable {
-                    onViewAllRoomsClick()
-                },
+        Row(
+            modifier = Modifier
+                .fillMaxWidth()
+                .semantics { role = Role.Button }
+                .clickable { onViewAllRoomsClick() }
             ,

필요 import:

import androidx.compose.ui.semantics.role
import androidx.compose.foundation.interaction.MutableInteractionSource // 선택
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.Role

Also applies to: 72-92

app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt (2)

53-53: 빈 화면 표시 조건 보강 검토

현재 (isCompleteSearching || isAllCategory) && searchResults.isEmpty() && !isSearching 입니다. 초기 진입에서 isInitialfalse로 전환되므로 실사용상 문제는 없겠지만, 방어적으로 !isInitial을 함께 두면 추후 상태 추가 시 안전합니다.

-    val showEmptyState: Boolean get() = (isCompleteSearching || isAllCategory) && searchResults.isEmpty() && !isSearching
+    val showEmptyState: Boolean get() =
+        !isInitial && (isCompleteSearching || isAllCategory) && searchResults.isEmpty() && !isSearching

195-203: load more 호출 가드 보강 제안

canLoadMore만으로는 nextCursor == null인 상태에서 호출될 수 있습니다. 커서 기반 페이징이면 nextCursor 체크를 추가하세요.

-        if (currentState.canLoadMore) {
+        if (currentState.canLoadMore && currentState.nextCursor != null) {
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (1)

186-190: LaunchedEffect 키를 route.viewAll로 지정 권장

Unit 대신 route.viewAll을 키로 써야 재조립/복원 시 의도치 않은 재호출을 줄일 수 있습니다.

-        LaunchedEffect(Unit) {
+        LaunchedEffect(route.viewAll) {
             if (route.viewAll) {
                 viewModel.onViewAllRooms()
             }
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira 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 5cd88cf and d366113.

📒 Files selected for processing (14)
  • app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomMainResponse.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/data/service/RoomsService.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupDeadlineRoomSection.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt (6 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/search/component/GroupRecentSearch.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt (6 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt (7 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (11 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt (2 hunks)
  • app/src/main/res/values/strings.xml (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/res/values/strings.xml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-01T07:19:10.981Z
Learnt from: rbqks529
PR: THIP-TextHip/THIP-Android#30
File: app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt:108-112
Timestamp: 2025-07-01T07:19:10.981Z
Learning: The user rbqks529 prefers to extract hardcoded UI strings to StringResource files (strings.xml) rather than keeping them inline in Compose components, which is a good practice for maintainability and localization in Android development.

Applied to files:

  • app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt
🧬 Code graph analysis (4)
app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt (1)
app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt (1)
  • EmptyMySubscriptionBar (133-168)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (3)
app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt (1)
  • GroupSearchScreen (40-62)
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt (1)
  • GroupRoomScreen (59-118)
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomChatScreen.kt (1)
  • GroupRoomChatScreen (59-138)
app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt (1)
app/src/main/java/com/texthip/thip/ui/common/buttons/GenreChipRow.kt (1)
  • GenreChipRow (21-114)
app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt (1)
app/src/main/java/com/texthip/thip/ui/common/buttons/FilterButton.kt (1)
  • FilterButton (37-128)
🔇 Additional comments (33)
app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt (2)

48-49: 트레일링 콤마 추가 OK

Compose 파라미터 포맷팅/디프 관리에 유용합니다. 문제 없습니다.


65-76: 인덱스 변환 로직 명확하고 일관적입니다

-1(해제/토글)과 0(전체)에 대한 매핑, 그 외 newIndex - 1로의 복원 모두 기대 동작입니다.

GenreChipRow 내부 토글 규칙과 본 매핑이 가정대로 동작하는지, 실제 기기에서 "전체 → 특정 장르 → 전체 재선택" 순서로 빠르게 탭하며 시각적 하이라이트가 일관한지 한번 확인 부탁드립니다.

app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt (3)

22-22: Search 라우트의 viewAll 기본값 추가 적절

기본값(false) 덕분에 역호환성이 유지됩니다. 프로세스 복원 시 SavedState 직렬화에도 문제 없을 것으로 보입니다.


43-48: Note 라우트 확장 OK — 딥링크/백스택 복원 동작 수동 확인 필요

isExpired 추가는 역호환(기본값)으로 보입니다. 리포지토리에서 GroupRoutes.Note 사용처를 찾지 못했으니 딥링크 파서와 Nav 백스택 복원 로직에서 기본값 경로만 사용되는지 수동으로 확인해 결과를 코멘트로 남겨주세요.


34-41: isExpired 전달 경로 수동 확인 필요

app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt에서 Room/RoomChat에 isExpired(Boolean, 기본값=false) 추가됨. rg로 navigate 호출을 찾지 못해 자동 검증 실패; 모든 navigate(...)에서 isExpired 전달 여부와 기본값(false) 사용 시 UI 동작이 의도된 것인지 수동으로 확인.

app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt (2)

60-70: 섹션 주석 업데이트 👍

"마감임박/인기/최근 생성" 명시로 API 기대치가 명확해졌습니다.


141-152: 검색 API에 isAllCategory 추가 — RoomsService.searchRooms 시그니처 일치 여부 확인 필요

RoomsRepository의 searchRooms 호출에서 인자(순서/추가: isAllCategory)가 변경되었습니다. 코드베이스 검색으로 RoomsService.searchRooms 심볼을 찾지 못해 자동 검증이 불가하므로, RoomsService.searchRooms의 시그니처(인자 개수·순서·기본값 포함)가 Repository 호출과 정확히 일치하는지 수동 확인 필요.

app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt (2)

179-181: 장르 토글 로직 간결화 Good

현재 선택 항목을 다시 누르면 null 해제, 범위 체크 포함되어 있어 안정적입니다.


49-61: onViewAllRooms 연결 LGTM — ViewModel에서 부수효과 관리 수동 확인 필요

onViewAllRooms가 ViewModel로 전달되는 것은 확인됩니다. 코드베이스 검색에서 'onViewAllRooms'/'isAllCategory' 결과가 없어 자동 검증에 실패했으니, isAllCategory 전환 시 초기화/스크롤 상태 등 부수효과가 ViewModel에서 관리되는지 수동 확인 필요.

app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt (2)

40-42: 검색 라우트 viewAll 전달 LGTM

EmptyMySubscriptionBar와의 연결만 마지막으로 검증해주세요.


74-76: isExpired 전파 일관성 확보 — 검증 필요

Room/RoomChat/Note 확장함수 시그니처가 라우트와 일치하고 기본값(false)으로 호출부 호환성이 유지됩니다. 다만 제공한 검증 스크립트가 빈 결과를 반환해 자동 확인에 실패했습니다.

레포 전체에서 재검색하여 정의/호출을 확인하세요:

#!/bin/bash
rg -n --hidden --glob '!**/build/**' -C3 '\bnavigateToGroup\w*\(' -S

파일: app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt (74-76) — 또한 84-86, 90-96에 적용.

app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt (2)

153-157: "전체 모임 둘러보기" 바 추가 — 네비게이션 호출부 확인 필요

UI 구성은 적절합니다. onNavigateToGroupSearchAllRooms가 실제로 GroupRoutes.Search(viewAll = true)로 라우팅되는지 호출부(정의/전달 위치)를 확인해 주세요. 자동 검증에서 ripgrep이 파일을 검색하지 못해 호출 추적을 완료하지 못했습니다. 파일: app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt (L153-157)


58-60: 새 콜백 파라미터 전파 LGTM — 자동 검증 실패(파일 검색 불가)

rg가 파일을 검색하지 못해 자동 검증에 실패했습니다 — ViewModel/Navigation에서 람다 캡처로 인한 불필요한 재컴포지션이 없도록 콜백을 불변으로 전달하고(remember/rememberUpdatedState 사용 권장) 수동 검증 필요.

위치: app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt (라인 58–60, 77–82)

app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomMainResponse.kt (1)

20-22: recentRoomList 추가 — LGTM (필드명 검증 필요)

emptyList() 기본값으로 역직렬화 안전. 제공하신 검색 결과가 비어 있어 코드베이스에서 'recentRoomList' 사용처를 찾지 못했으니 백엔드 필드명이 정확히 'recentRoomList'인지 최종 확인 필요.

app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt (8)

27-27: isAllCategory 플래그 추가 적절

전체 보기 플로우를 명확히 구분하는 불리언 플래그 도입은 상태 전이 단순화에 도움이 됩니다.


112-114: 라이브 검색 전환 시 플래그 리셋 OK

isCompleteSearching=false, isAllCategory=false로 리셋하여 의도한 대로 라이브/일반/전체 보기 상태가 충돌하지 않습니다.


126-148: 빈 검색어 시 전체 보기로 라우팅하는 UX 합리적

트림한 쿼리 기준으로 분기하고, 작업 전 기존 잡을 취소하는 순서도 적절합니다.


150-163: 전체 보기에서 빈 키워드 전송 호환성 확인 필요

onViewAllRooms()searchQuery="" 상태로 performSearchWithCurrentQuery()를 호출하여, 결국 roomsRepository.searchRooms(keyword = "")가 됩니다. 서버가 isAllCategory=true일 때 keyword=""를 허용하지 않으면 400/빈 결과가 날 수 있습니다. 백엔드가 “미전달(null)”과 “빈 문자열(“”)”을 동일하게 처리하는지 확인해 주세요. 필요 시 Retrofit 쿼리를 nullable로 바꾸고 빈 경우 전달 생략을 권장합니다.

다음 두 방향 중 하나를 제안합니다:

  • 서비스/레포 시그니처 변경: @Query("keyword") keyword: String?로 두고, 호출부에서 query.takeIf { it.isNotEmpty() } 전달
  • 서버가 ""를 허용하는지 QA로 확인 후 주석으로 계약 명시

168-179: 필터/정렬 변경 시 재검색 조건 합리적

완료 검색 또는 전체 보기 모드에서만 재조회하도록 한 것은 불필요한 트래픽을 막아줍니다.


181-193: isFinalized 파생 로직 명확

!isLiveSearching || isAllCategory 파생은 의도에 부합합니다. 중복 호출 방지를 위해 이 경로가 연달아 호출될 수 있는 지점(필터/정렬 탭 전환 등)에서 searchJob 취소가 선행되는 것도 좋습니다.


227-263: 성공/실패 상태 전이 일관성 양호

성공 시엔 기존 플래그를 유지하고, 실패/널 응답 시에만 isLiveSearching/isCompleteSearching를 갱신하는 정책이 UI 깜빡임을 줄입니다.


205-226: 검증 필요 — 빈 문자열 대신 null 전달 여부 확인

GroupSearchViewModel의 searchRooms 호출에서 category = selectedGenre?.apiCategory ?: "" 및 keyword가 빈 문자열로 전달될 수 있습니다. 백엔드가 빈 문자열을 '필터 없음'으로 처리하지 않으면 호출부에서 빈 문자열 대신 null을 전달하고 roomsRepository/service/API 시그니처를 nullable로 변경하세요. 권장 예: keyword = query.ifBlank { null }, category = selectedGenre?.apiCategory ?: null.

위치: app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt (약 205–226행). 저장소 검색 실패로 자동 검증 불가 — 수동 확인 필요.

app/src/main/java/com/texthip/thip/data/service/RoomsService.kt (3)

55-55: API 주석 업데이트 LGTM

“마감임박/인기/최근 생성” 설명 반영 좋습니다.


111-116: 엔드포인트 변경(rooms/{roomId}/playing → rooms/{roomId}) — 서버 전환 및 응답 필드(모델) 확인 필요

레포에서 @get("rooms/{roomId}")만 사용되며 '/playing' 경로는 존재하지 않습니다. 서버 전환이 완료되지 않았다면 404 위험이 있으니 백엔드 전환 일정과 응답 필드 동일성(RoomsPlayingResponse 재사용 가능 여부)을 확인하세요. 영향 범위: RoomsService.getRoomsPlaying, RoomsRepository, GroupRoomViewModel, GroupRoomScreen, RoomsPlayingResponse.

     /** 기록장 API들 */
-    @GET("rooms/{roomId}")
+    @GET("rooms/{roomId}")
     suspend fun getRoomsPlaying(
         @Path("roomId") roomId: Int
     ): BaseResponse<RoomsPlayingResponse>
+
+    /** [LEGACY] 서버 전환 완료 전 임시 유지 — 완료 후 제거 */
+    @GET("rooms/{roomId}/playing")
+    suspend fun getRoomsPlayingLegacy(
+        @Path("roomId") roomId: Int
+    ): BaseResponse<RoomsPlayingResponse>

101-107: isAllCategory 쿼리 파라미터 지원 여부 확인 필요
백엔드 API 스펙(문서/Swagger)에서 isAllCategory 쿼리 파라미터가 정의되어 있는지 반드시 확인해주세요.

app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (8)

31-31: GroupSearchViewModel 직접 주입 추가 OK

내비게이션 진입 시 뷰모델 초기화 흐름이 명확해졌습니다.


86-99: 검색/전체보기 진입 포인트 분리 좋습니다

탭에서 바로 전체 보기로 진입 가능한 훅이 생겨 사용성에 도움됩니다.


151-153: 완료 목록 → 방 진입 시 isExpired=true 전달 적절

완료 방은 읽기 전용이므로 명시적으로 전달하는 방향 타당합니다.


279-293: 만료 상태 라우팅 전달 일관성 확보 OK

Room → Chat/Note로 isExpired를 그대로 전파하는 구조가 읽기 전용 가드에 도움됩니다.


331-337: 채팅 화면 만료 가드 전달 OK

만료 방에서 입력 차단 및 토스트 노출에 필요한 정보가 잘 전달됩니다.


171-171: My 화면에서 isExpired=false 하드코딩 — 잘못된 상태 전달 가능성

진행 중이 아닌 모든 방에 대해 isExpired=false로 고정 전달하면 실제 만료 방도 쓰기 가능 UI가 노출될 수 있습니다. 이전 리뷰에서도 동일 지적이 있었습니다.

해결 옵션:

  • A(권장): 호출부에서 실제 만료 여부를 계산해 전달(예: deadlineDate/progressEndDate/room.type 기반 또는 RoomUtils 유틸 사용).
  • B(대안): navigateToGroupRoomisExpired 파라미터를 제거하고, GroupRoomScreen이 서버 응답으로 만료 여부를 판별.
    원하시면 선택한 방향에 맞춰 관련 호출부/라우트/스크린 시그니처 일괄 패치를 제안하겠습니다.

305-307: BackStackEntry 없을 때 크래시 위험 — 안전 가드 추가

navController.getBackStackEntry(MainTabRoutes.Group)가 없으면 IllegalArgumentException로 크래시합니다. try/catch로 폴백 인스턴스를 만드세요.

-        val feedViewModel: FeedViewModel =
-            hiltViewModel(navController.getBackStackEntry(MainTabRoutes.Group))
+        val feedViewModel: FeedViewModel = runCatching {
+            hiltViewModel(navController.getBackStackEntry(MainTabRoutes.Group))
+        }.getOrElse {
+            hiltViewModel()
+        }

352-354: FeedViewModel 백스택 의존 재발 — 동일 가드 적용 필요

위와 동일한 위험. 동일한 가드를 적용해 주세요.

-        val feedViewModel: FeedViewModel =
-            hiltViewModel(navController.getBackStackEntry(MainTabRoutes.Group))
+        val feedViewModel: FeedViewModel = runCatching {
+            hiltViewModel(navController.getBackStackEntry(MainTabRoutes.Group))
+        }.getOrElse {
+            hiltViewModel()
+        }

Comment on lines +134 to +138
fun EmptyMySubscriptionBar(
modifier: Modifier = Modifier,
text: String = stringResource(R.string.find_thip_mate),
onClick: () -> Unit = {}
) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Composable 기본값에 stringResource 사용은 컴파일 오류를 유발합니다.

stringResource(...)@composable 컨텍스트에서만 호출 가능하므로 기본 파라미터 값에서 호출하면 빌드가 실패합니다. 리소스 ID를 기본으로 받고 본문에서 문자열을 해석하세요.

다음 수정안을 적용해 주세요:

 @Composable
 fun EmptyMySubscriptionBar(
-    modifier: Modifier = Modifier,
-    text: String = stringResource(R.string.find_thip_mate),
-    onClick: () -> Unit = {}
+    modifier: Modifier = Modifier,
+    @StringRes textResId: Int = R.string.find_thip_mate,
+    onClick: () -> Unit = {}
 ) {
+    val displayText = stringResource(textResId)
@@
-            text = text,
+            text = displayText,

파일 상단에 import 추가:

import androidx.annotation.StringRes

Also applies to: 152-153

🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt
around lines 134-138 (and similarly at 152-153), the composable currently uses
stringResource(...) as a default parameter which causes a compile error because
stringResource is only valid in a @Composable context; change the parameter to
accept a @StringRes Int resource ID (add import androidx.annotation.StringRes at
the top), give it a default resource ID (e.g. R.string.find_thip_mate), and
inside the composable body call stringResource(resId) to obtain the actual
String; apply the same pattern for the other affected composable at lines
152-153.

Comment on lines +87 to 92
val actualPageCount = roomSections.size

val effectivePagerState = rememberPagerState(
initialPage = 0,
pageCount = { roomSections.size }
initialPage = 2,
pageCount = { Int.MAX_VALUE }
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

"무한 스와이프" 구현 불완전 — 초기 페이지가 2면 좌측(이전) 방향이 막힙니다

pageCount를 Int.MAX_VALUE로 두면서 initialPage=2는 시작점이 너무 앞쪽이라 0 이하로는 이동 불가합니다. 가운데 근처에서 시작하고 섹션 개수에 정렬되도록 초기 페이지를 계산하세요.

적용 제안:

-            val actualPageCount = roomSections.size
+            val actualPageCount = roomSections.size

-            val effectivePagerState = rememberPagerState(
-                initialPage = 2,
-                pageCount = { Int.MAX_VALUE }
-            )
+            val startPage = remember(actualPageCount) {
+                if (actualPageCount > 0) {
+                    val mid = Int.MAX_VALUE / 2
+                    mid - (mid % actualPageCount)
+                } else 0
+            }
+            val effectivePagerState = rememberPagerState(
+                initialPage = startPage,
+                pageCount = { if (actualPageCount > 0) Int.MAX_VALUE else 1 }
+            )
@@
-                val actualPage = page % actualPageCount
+                val actualPage = if (actualPageCount > 0) page % actualPageCount else 0

Also applies to: 100-102

🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupDeadlineRoomSection.kt
around lines 87-92 (and also apply same change at lines ~100-102), the pager
uses pageCount = Int.MAX_VALUE with a fixed initialPage = 2 which prevents
swiping left past 0; compute an initial page positioned near the middle of the
Int.MAX_VALUE range but aligned to the actual number of sections: take center =
Int.MAX_VALUE / 2, subtract center % actualPageCount to align to a multiple of
actualPageCount, then add the desired small offset (e.g., 2) to get the
initialPage; replace the hardcoded initialPage with this computed value and
reuse the same logic for the other occurrence.

Comment on lines +50 to +52
val allChipText = stringResource(id = R.string.all)
val chipList = remember(genres) { listOf(allChipText) + genres }

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

'전체' 칩 중복 가능성과 로케일 변경 시 갱신 누락

  • 현재 chipList는 호출 측이 이미 "전체"(= R.string.all)를 포함해 전달하면 중복 칩이 생겨 인덱스 매핑이 틀어질 수 있습니다(아래 Preview 2가 실제로 그 케이스).
  • remember(genres)만 키로 사용하여 로케일이 바뀌어 allChipText가 변해도 chipList가 재계산되지 않습니다.

아래처럼 allChipText를 키에 포함하고, 전달된 genres에서 동일 텍스트를 제거해 중복을 방지해주세요.

-    val allChipText = stringResource(id = R.string.all)
-    val chipList = remember(genres) { listOf(allChipText) + genres }
+    val allChipText = stringResource(id = R.string.all)
+    // 로케일 변화(allChipText 변경)에 반응하고, 호출 측에 "전체"가 포함돼도 중복 제거
+    val chipList = remember(allChipText, genres) {
+        val filtered = genres.filterNot { it == allChipText }
+        listOf(allChipText) + filtered
+    }

다음 스크립트로 호출부에서 "전체"를 직접 전달하는 곳이 있는지 빠르게 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# GroupFilteredSearchResult 호출부 훑기
rg -n --type=kt -C3 '\bGroupFilteredSearchResult\('

echo
echo '리터럴로 "전체"를 전달하는 목록 리터럴 탐지(오탐 가능):'
rg -n --type=kt -C2 'listOf\([^)]*"전체"'

Length of output: 88


rg의 --type=kt 오류로 검색이 실패했습니다. 확장자 필터 방식으로 재검사합니다.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching .kt files for GroupFilteredSearchResult calls..."
rg -n -C3 '\bGroupFilteredSearchResult\(' -g '**/*.kt' || true

echo; echo 'Searching for GroupFilteredSearchResult calls that include the literal "전체"...'
rg -n -C3 'GroupFilteredSearchResult\([^)]*"전체"[^)]*\)' -g '**/*.kt' || true

echo; echo 'Searching for listOf(... "전체" ...) in .kt files (may be false positives)...'
rg -n -C2 'listOf\([^)]*"전체"[^)]*\)' -g '**/*.kt' || true

echo; echo 'Searching for any literal "전체" in .kt files (broad search)...'
rg -n -C1 '"전체"' -g '**/*.kt' || true

Length of output: 5093


중복 '전체' 제거 및 로케일 변경 반영

Preview 호출에서 "전체"를 직접 전달해 중복 인덱스 오류와 로케일 변경 미반영 문제가 발생합니다. allChipText를 remember 키에 포함하고 전달된 genres에서 동일 텍스트를 제거하세요.

-    val allChipText = stringResource(id = R.string.all)
-    val chipList = remember(genres) { listOf(allChipText) + genres }
+    val allChipText = stringResource(id = R.string.all)
+    // 로케일 변화(allChipText 변경)에 반응하고, 호출 측에 "전체"가 포함돼도 중복 제거
+    val chipList = remember(allChipText, genres) {
+        val filtered = genres.filterNot { it == allChipText }
+        listOf(allChipText) + filtered
+    }

발견된 호출 예:

  • app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt:211 (listOf("전체", ...))
  • app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt:171 (genres = genreDisplayNames)
📝 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.

Suggested change
val allChipText = stringResource(id = R.string.all)
val chipList = remember(genres) { listOf(allChipText) + genres }
val allChipText = stringResource(id = R.string.all)
// 로케일 변화(allChipText 변경)에 반응하고, 호출 측에 "전체"가 포함돼도 중복 제거
val chipList = remember(allChipText, genres) {
val filtered = genres.filterNot { it == allChipText }
listOf(allChipText) + filtered
}
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt
around lines 50–52, the code creates chipList without accounting for locale
changes and can duplicate the "전체" item; update the remember call to include
allChipText as a key and build chipList by prepending allChipText to a genres
list that has any identical text removed (e.g., genres.filterNot { it ==
allChipText }), and update preview/call sites (where "전체" is passed literally)
to stop injecting the localized "all" string so callers supply only genre items.

Comment on lines +82 to +85
text = stringResource(R.string.look_around_all_rooms_title),
color = colors.White,
style = typography.smalltitle_sb600_s18_h24
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

리소스 키/아이콘 존재 여부 확인

look_around_all_rooms_title, ic_chevron 리소스가 정상 추가/배치되었는지 확인 바랍니다.

Also applies to: 88-91


🏁 Script executed:

#!/bin/bash
rg -n --glob '!**/build/**' '<string name="look_around_all_rooms_title"|ic_chevron' app/src/main

Length of output: 0


추가 검증 스크립트 실행합니다.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "검색: 리소스 키와 드로어블 이름을 전체 저장소에서 찾습니다..."
rg -n --hidden --no-ignore-vcs --glob '!**/build/**' -S 'look_around_all_rooms_title' || true
rg -n --hidden --no-ignore-vcs --glob '!**/build/**' -S 'ic_chevron' || true

echo "검색: 일반적인 Android 참조 패턴 검사(@string/@drawable/R.string/R.drawable)..."
rg -n --hidden --no-ignore-vcs --glob '!**/build/**' -S 'R\.string\.look_around_all_rooms_title|@string/look_around_all_rooms_title' || true
rg -n --hidden --no-ignore-vcs --glob '!**/build/**' -S 'R\.drawable\.ic_chevron|@drawable/ic_chevron|@mipmap/ic_chevron' || true

echo "strings.xml 파일 목록(최대 10개)과 각 파일 상위 200라인 출력..."
find . -type f -path '*/res/values*/strings*.xml' | head -n 10 | while read -r f; do
  echo "===> $f"
  sed -n '1,200p' "$f"
done || true

echo "drawable / mipmap 경로에서 ic_chevron 파일 목록 출력..."
find . -type f \( -path '*/res/drawable*/*ic_chevron*' -o -path '*/res/mipmap*/*ic_chevron*' \) -print || true

echo "검사 완료."

Length of output: 10007


리소스 확인 — look_around_all_rooms_title 누락, ic_chevron 존재

  • ic_chevron drawable 확인: app/src/main/res/drawable/ic_chevron.xml (및 ic_chevron_left.xml, ic_chevron_right.xml) 존재.
  • look_around_all_rooms_title 문자열 리소스 미발견: app/src/main/res/values/strings.xml에서 해당 키가 없음. strings.xml에 키 추가하거나 코드에서 사용중인 리소스 키를 수정하세요.
  • 사용 위치: app/src/main/java/com/texthip/thip/ui/group/search/component/GroupRecentSearch.kt (라인 82–85, 88–91).
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/search/component/GroupRecentSearch.kt
around lines 82–85 (and also review nearby usage at 88–91), the code references
stringResource(R.string.look_around_all_rooms_title) but that key is missing
from app/src/main/res/values/strings.xml; either add a new entry named
look_around_all_rooms_title to strings.xml with the appropriate localized text,
or change the reference here to an existing string resource key; after updating,
rebuild to ensure no missing resource errors and verify any other uses of this
key in the file are consistent.

Copy link
Collaborator

@rbqks529 rbqks529 left a comment

Choose a reason for hiding this comment

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

코드 수정 확인했습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

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

여기는 어떤 수정 사항이 있나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

전체 모임방을 볼 수 있는 버튼이 생겼는데 이 컴포넌트와 동일해서 string을 받을 수 있도록 했고, onClick을 추가했습니다 !

Copy link
Collaborator

Choose a reason for hiding this comment

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

여기는 또 어떤게 수정된건가요? 최근 검색어 보여주는 곳 아닌감

Copy link
Member Author

Choose a reason for hiding this comment

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

맞습니두 여기에서도 전체 모임방을 볼 수 있는 버튼이 추가돼서 해당 버튼 추가했습니닷

@Nico1eKim Nico1eKim merged commit 0eb78dd into THIP-TextHip:develop Sep 24, 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.

[REFACTOR] 완료된 모임방 구현, 모임방 사용성 개선작업

2 participants