[API] 방 검색 API 연결 및 방 시간 UI 로직 수정#106
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Caution Review failedThe pull request is closed. Walkthrough모임방 검색 API 모델·서비스·레포지토리를 추가하고, 검색 화면을 ViewModel 기반 uiState로 재구성하며 검색 결과 컴포저블을 SearchRoomItem으로 전환하고 무한 스크롤·페이징을 도입했습니다. 마감일 처리를 서버 문자열로 전환하고 관련 유틸을 제거했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as GroupSearchScreen
participant VM as GroupSearchViewModel
participant Repo as RoomsRepository
participant Svc as RoomsService
participant API as /rooms/search
UI->>VM: updateSearchQuery(query)
VM-->>VM: debounce 300ms
VM->>Repo: searchRooms(keyword, category, sort, isFinalized, cursor=null)
Repo->>Svc: GET /rooms/search (query params)
Svc->>API: 요청
API-->>Svc: BaseResponse<RoomsSearchResponse>
Svc-->>Repo: RoomsSearchResponse
Repo-->>VM: Result<RoomsSearchResponse?>
VM-->>UI: uiState(searchResults, nextCursor, hasMore)
sequenceDiagram
participant UI as Live/FilteredResult (LazyList)
participant VM as GroupSearchViewModel
participant Repo as RoomsRepository
participant Svc as RoomsService
participant API as /rooms/search
UI-->>UI: near end? (derivedStateOf)
UI->>VM: onLoadMore()
VM->>Repo: searchRooms(..., cursor=nextCursor)
Repo->>Svc: GET /rooms/search (cursor)
API-->>Svc: Response(nextCursor,isLast,roomList)
Repo-->>VM: Result appended
VM-->>UI: append results, update nextCursor/hasMore
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related issues
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. 📜 Recent review detailsConfiguration used: CodeRabbit UI 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (2)
✨ 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.
Pull Request Overview
This PR implements room search API integration and updates the UI logic to handle time-related data as returned by the server. The main changes involve refactoring time display from integer-based days to string-based time representations and integrating actual API calls for room search functionality.
- Replaces integer-based day calculations with string-based time display throughout the UI
- Integrates room search API with proper pagination and filtering
- Removes legacy date utility classes and simplifies time handling logic
Reviewed Changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| app/src/main/res/values/strings.xml | Updates string resources for time display formatting |
| app/src/main/java/com/texthip/thip/utils/rooms/RoomUtils.kt | Removes deprecated date calculation method |
| app/src/main/java/com/texthip/thip/utils/rooms/DateUtils.kt | Deletes entire utility class for date calculations |
| app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookGroupScreen.kt | Updates to use API response objects directly instead of mock data |
| app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt | Simplifies navigation logic and removes unused view model |
| app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt | Complete rewrite to implement actual search functionality with API integration |
| app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt | Major refactor to use view model state and remove local state management |
| app/src/main/java/com/texthip/thip/ui/group/search/component/GroupLiveSearchResult.kt | Updates component to use API response objects and adds infinite scroll |
| app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt | Updates component to use API response objects and adds infinite scroll |
| app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomRecruitScreen.kt | Updates time display logic to use string manipulation instead of calculations |
| app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt | Changes endDate parameter type from Int to String |
| app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt | Changes endDate parameter type from Int to String |
| app/src/main/java/com/texthip/thip/data/service/RoomsService.kt | Adds new API endpoint for room search |
| app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt | Implements room search repository method |
| app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsSearchResponse.kt | Defines new data models for search API response |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
| if (lastVisibleIndex != null && | ||
| if (lastVisibleIndex != null && | ||
| recruitingList.isNotEmpty() && | ||
| !isLoadingMore && |
There was a problem hiding this comment.
[nitpick] This complex boolean condition for load more logic spans multiple lines and could be extracted into a computed property or helper function for better readability.
| loadMoreJob?.cancel() | ||
|
|
||
| // 공백도 검색 가능하도록 수정 (빈 문자열만 제외) | ||
| if (query.isNotEmpty()) { |
There was a problem hiding this comment.
The comment mentions allowing blank searches but the condition only checks for empty strings. Consider using query.isNotBlank() if spaces should trigger search, or update the comment to match the actual behavior.
|
|
||
| fun onSearchButtonClick() { | ||
| val query = uiState.value.searchQuery | ||
| if (query.isNotEmpty()) { // 공백도 검색 가능 (빈 문자열만 제외) |
There was a problem hiding this comment.
Inconsistent behavior: the comment states that spaces are searchable, but isNotEmpty() allows strings with only spaces. If spaces should be searchable, use isNotBlank() instead.
| text = detail.recruitEndDate.replace( | ||
| "뒤", | ||
| "남음" | ||
| ), |
There was a problem hiding this comment.
Using string replacement for time formatting is fragile. Consider using a dedicated utility function to handle time string transformations consistently across the app.
| ), | |
| text = formatRecruitEndDate(detail.recruitEndDate), |
| endDate?.let { | ||
| Text( | ||
| text = stringResource(R.string.card_item_end_date_recruit, endDate), | ||
| text = endDate + stringResource(R.string.card_item_end), |
There was a problem hiding this comment.
String concatenation for time display formatting should be handled consistently. Consider using string formatting or a utility function to ensure proper formatting across all time displays.
| text = endDate + stringResource(R.string.card_item_end), | |
| text = stringResource(R.string.card_item_end, endDate), |
There was a problem hiding this comment.
Actionable comments posted: 7
🔭 Outside diff range comments (1)
app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt (1)
146-152: 문자열 덧붙이기 대신 포맷 리소스로 구성하고, 불필요한 따옴표/공백 제거 필요현재
endDate + stringResource(R.string.card_item_end)는:
- 리소스 값의 선행 공백/따옴표 유무에 따라 화면 문자열이 어색해질 수 있고,
- 현지화(RTL/LTR) 조합에 취약합니다.
또한let블록 내에서endDate캡처 변수만 사용하고it을 쓰지 않는 점도 사소한 가독성 저하입니다.권장: 포맷 리소스를 도입해 일관되게 조합하세요. 우선 strings.xml에서 따옴표와 선행 공백을 제거한 뒤, 포맷 문자열을 추가하고 아래처럼 적용하는 것을 추천합니다.
적용 예(이 파일 내 diff):
- endDate?.let { - Text( - text = endDate + stringResource(R.string.card_item_end), - color = colors.Red, - style = typography.menu_sb600_s12_h20 - ) - } + endDate?.let { + Text( + text = stringResource( + R.string.card_item_deadline_format, + it, + stringResource(R.string.card_item_end) + ), + color = colors.Red, + style = typography.menu_sb600_s12_h20 + ) + }추가로, Preview의 샘플 데이터도 일관되게 완성형 문자열을 사용해 주세요:
- endDate = "3일 뒤" + endDate = "3일 뒤" ... - endDate = "5" + endDate = "5일 뒤"strings.xml 변경(선택 영역 밖, 지원용 코드):
<string name="card_item_end">모집 마감</string> <string name="card_item_deadline_format">%1$s %2$s</string>
🧹 Nitpick comments (28)
app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt (1)
111-113: 상단 여백 72.dp 하드코딩은 디바이스/인셋 변화에 취약합니다. 시스템 인셋 + 앱바 높이를 반영하도록 개선 제안현재 TopAppBar가 오버레이되는 구조에서 고정 72.dp는 상태바/노치/앱바 높이 변경 시 겹침/과도한 공백이 생길 수 있습니다. WindowInsets와 앱바 높이를 더한 계산값을 사용하면 안정적입니다.
예시(참고용, 파일 상단에 필요한 import 추가):
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.ui.unit.dp // GroupContent 내부 어딘가 val topPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + 56.dp + 16.dp // 56.dp: 앱바 높이(디자인 시스템 상수로 대체 권장), 16.dp: 기존 마진 GroupSearchTextField( modifier = Modifier.padding(top = topPadding, bottom = 32.dp), onClick = onNavigateToGroupSearch )app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt (3)
150-168: LazyColumn 아이템에 안정적인 key를 부여해주세요 (스크롤 위치 보존/성능 개선)현재 items에 key가 없어 데이터 변경 시 스크롤 포지션 점프/재조합 비용 증가 가능성이 있습니다. roomId를 key로 지정하는 것을 권장합니다.
- LazyColumn( + LazyColumn( state = listState, verticalArrangement = Arrangement.spacedBy(20.dp), contentPadding = PaddingValues(bottom = 20.dp), modifier = Modifier.fillMaxSize() ) { - items(uiState.myRooms) { room -> + items( + items = uiState.myRooms, + key = { it.roomId } + ) { room -> CardItemRoom( title = room.roomName, participants = room.memberCount, maxParticipants = room.recruitCount, isRecruiting = RoomUtils.isRecruitingByType(room.type), endDate = room.endDate, imageUrl = room.bookImageUrl, onClick = { onCardClick(room) } ) } }
82-86: 무한 스크롤 트리거 안정성 개선 제안 (snapshotFlow 활용)현 구현도 동작은 하나, shouldLoadMore가 true로 유지되는 동안 UI 프레임마다 재평가될 수 있습니다. listState 기반으로 snapshotFlow를 사용하면 스크롤 경계 지점에서만 로드 트리거되어 안정적입니다.
예시:
LaunchedEffect(listState, uiState.canLoadMore, uiState.isLoadingMore) { if (!uiState.canLoadMore || uiState.isLoadingMore) return@LaunchedEffect snapshotFlow { val lastVisible = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1 val total = listState.layoutInfo.totalItemsCount lastVisible >= total - 3 && total > 0 }.collect { shouldLoad -> if (shouldLoad) onLoadMore() } }
88-95: 주석 문장 보완 제안“// Filter 상태를”로 끝나는 미완 주석입니다. 간단히 문장을 완결해 가독성을 높여주세요.
- // Filter 상태를 + // Filter 상태를 UI에 반영하기 위한 선택 상태(boolean 배열)app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupDeadlineRoomSection.kt (3)
68-70: 장르 문자열 계산은 remember로 메모이즈하면 미세 성능 개선재구성 빈도가 높을 수 있는 섹션이므로 toDisplayStrings() 결과를 remember로 고정하면 불필요 계산을 줄일 수 있습니다.
- val genreStrings = Genre.entries.toDisplayStrings() + val genreStrings = remember { Genre.entries.toDisplayStrings() }
73-81: roomSections를 Pair 대신 명시적 데이터 구조로 표현하면 가독성↑Pair<String, List>는 first/second 해석 비용이 있어 필드명 있는 data class(예: Section(title, rooms))를 권장합니다. 유지보수성 측면에서 의미가 분명해집니다.
예시:
data class RoomSection(val title: String, val rooms: List<RoomMainResponse>) val roomSections = listOf( RoomSection(stringResource(R.string.room_section_deadline), roomMainList?.deadlineRoomList ?: emptyList()), RoomSection(stringResource(R.string.room_section_popular), roomMainList?.popularRoomList ?: emptyList()), )
197-205: endDate에 deadlineDate 문자열 직접 매핑(LGTM)카드 컴포넌트의 endDate:String? 이관과 일치합니다. 문자열 리소스/표시 포맷과의 호환성도 좋아 보입니다. isRecruiting을 true로 고정한 가정은 API 계약(양 섹션 모두 모집중) 변경 시 취약할 수 있으니, 추후 필드가 내려오면 값 전달로 대체하는 것을 권장합니다.
app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt (1)
125-136: searchRooms 파라미터에 타입 안정성(정의된 Enum/래퍼) 도입 제안category/sort를 생 문자열로 노출하면 오타/백엔드 파라미터 변경에 취약합니다. Enum + 매핑 또는 옵션 래퍼를 도입해 안정성을 높이세요. 또한 UI에서 null 처리 부담을 줄이려면 빈 응답을 표준화해 non-null로 반환하는 것도 고려해볼만 합니다.
예시:
enum class RoomSort(val api: String) { DEADLINE("deadline"), POPULAR("popular"), LATEST("latest") } enum class RoomCategory(val api: String) { ALL("all"), LITERATURE("literature") /* ... */ } suspend fun searchRooms( keyword: String, category: RoomCategory, sort: RoomSort = RoomSort.DEADLINE, isFinalized: Boolean = false, cursor: String? = null ): Result<RoomsSearchResponse> = runCatching { roomsService.searchRooms(keyword, category.api, sort.api, isFinalized, cursor) .handleBaseResponse() .getOrThrow() ?: RoomsSearchResponse(emptyList(), null) // 모델에 맞는 빈 응답 기본값 }원하시면 UI 요구사항에 맞춘 도메인 모델 변환(Repository 레벨에서 DTO→Domain)까지 함께 정리해드리겠습니다.
app/src/main/java/com/texthip/thip/data/service/RoomsService.kt (1)
92-101: 검색 API 엔드포인트 추가 LGTM, 정렬 파라미터의 타입 안정성 개선 제안Retrofit 시그니처와 기본값 구성이 명확합니다. 단,
sort가 매직 스트링(예: "deadline", "like", "latest")으로 흐르는 것을 방지하려면 타입 안정성을 주는 래퍼를 권장합니다.가벼운 개선안:
- 정렬 키를 sealed class 또는 enum으로 정의하고,
.queryValue로 매핑해 전달- 혹은 상수 집합을 @stringdef로 제한
예시(선택 영역 밖, 지원용 코드):
enum class RoomSearchSort(val query: String) { DEADLINE("deadline"), LIKE("like"), LATEST("latest"), }Service 사용부 예:
suspend fun searchRooms( keyword: String, category: String, sort: RoomSearchSort = RoomSearchSort.DEADLINE, isFinalized: Boolean = false, cursor: String? = null ) = roomsService.searchRooms( keyword = keyword, category = category, sort = sort.query, isFinalized = isFinalized, cursor = cursor )app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsSearchResponse.kt (1)
13-22: 필드 네이밍/타입 합의 정리 제안(특히 isPublic, recruitCount, deadlineDate)
isPublic은 UI에서 주로isSecret로 쓰이므로 매핑 계층에서val isSecret = !isPublic형태의 변환을 중앙화하면 혼선을 줄일 수 있습니다.recruitCount가 "정원(최대 인원)" 의미라면, 도메인 계층에서는capacity/maxMemberCount같은 명시적 이름으로 노출되게 매핑하는 것을 권장합니다.deadlineDate가 서버 포맷 문자열(예: "3일 뒤")이라면 괜찮지만, 비어있음이 의미 있는 상태라면String?로 모델링하는 편이 더 명확합니다. 반대로 항상 내려온다면 기본값""유지도 OK입니다.선택 사항(선택 영역 밖, 지원용 코드): UI 매핑 예시
data class UiSearchRoomItem( val roomId: Int, val imageUrl: String?, val title: String, val participants: Int, val maxParticipants: Int, val deadlineText: String?, // null: 미노출 val isSecret: Boolean, ) fun SearchRoomItem.toUi(): UiSearchRoomItem = UiSearchRoomItem( roomId = roomId, imageUrl = bookImageUrl, title = roomName, participants = memberCount, maxParticipants = recruitCount, deadlineText = deadlineDate.ifBlank { null }, isSecret = !isPublic, )app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomRecruitScreen.kt (2)
108-119: 람다 인자 포맷 변경은 비기능적 변경호출부 포맷 변경(멀티라인)은 가독성을 약간 개선하지만 기능 변화는 없습니다. 그대로 진행해도 무방합니다.
342-345: 서버 응답 문자열에 대한 단순 치환은 취약합니다 (i18n·표기 일관성 문제)
"뒤" -> "남음"단순 치환은 서버에서 "D-5", "모집 중", 다른 언어 응답을 내려줄 때 모두 대응하지 못합니다. 또한 다른 컴포넌트(CardItemRoom, CardItemRoomSmall)와의 접미사 처리 방식이 제각각이라 표기 불일치가 생길 수 있습니다. 접미사 문자열 리소스를 사용하고, 케이스별로 안전하게 처리하거나, 더 바람직하게는 ViewModel/Mapper에서 일관된 최종 표기를 만들어 UI는 그대로 표시만 하도록 분리하는 것을 권장합니다.아래는 최소 침습 수정안(접미사 리소스 사용 + 안전 처리)입니다.
- Text( - text = detail.recruitEndDate.replace( - "뒤", - "남음" - ), - style = typography.info_m500_s12, - color = colors.NeonGreen - ) + Text( + text = when { + detail.recruitEndDate.endsWith("뒤") -> + detail.recruitEndDate.replace("뒤", stringResource(R.string.card_item_end)) + detail.recruitEndDate.contains("D-") -> + detail.recruitEndDate + stringResource(R.string.card_item_end) + else -> detail.recruitEndDate + }, + style = typography.info_m500_s12, + color = colors.NeonGreen + )UI 전역에서 마감일 표기가 일관되게 나오는지(예: "3일 남음", "D-5 남음", "모집 중" 등) 한 번 눈으로 확인 부탁드립니다. 필요 시 ViewModel에서 통일된 포맷을 제공하도록 변경해드릴 수 있습니다.
app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt (3)
152-158: '3일 뒤' + '남음' 중복 표기 가능성 — 안전한 접미사 처리 또는 ViewModel로 위임 권장현재는
endDate + 접미사방식이라 서버가 "3일 뒤"를 주는 경우 "3일 뒤남음"처럼 부자연스러운 표기가 됩니다. 아래처럼 안전 처리하거나, 더 나은 접근으로는 ViewModel/Mapper에서 최종 문자열을 생성해 Card는 그대로 출력만 하도록 하세요.국소 수정안:
- Text( - text = endDate - + if (isRecruiting) stringResource( - R.string.card_item_end - ) else stringResource(R.string.card_item_finish), + val suffix = if (isRecruiting) stringResource(R.string.card_item_end) + else stringResource(R.string.card_item_finish) + Text( + text = when { + endDate.endsWith("뒤") -> endDate.replace("뒤", suffix) + endDate.contains("D-") -> endDate + suffix + else -> endDate + }, color = if (isRecruiting) colors.Red else colors.Grey01, style = typography.menu_sb600_s12_h20, maxLines = 1 )또는,
endDate는 완성된 표시 문자열(예: "D-5 남음", "모집 종료")만 전달받고 여기서는 그대로 출력하도록 변경하는 것도 추천합니다.
149-156: 미세: let 블록 안에서 endDate 대신 it 사용널 가드로
let을 사용하는 만큼 블록 내부에서는 캡처된 값을 사용하는 것이 가독성과 안전성에 유리합니다.- endDate?.let { + endDate?.let { end -> Spacer(modifier = Modifier.height(5.dp)) Text( - text = endDate + text = end + if (isRecruiting) stringResource( R.string.card_item_end ) else stringResource(R.string.card_item_finish),
180-206: Preview의 endDate 예시 값 통일 제안프리뷰에서
"3일 뒤","3"등 혼재되어 있어 렌더 결과 확인이 어렵습니다. 서버 형식과 맞는 대표 포맷 1~2가지를 일관되게 사용하는 것을 제안드립니다(예:"D-3","3일 뒤").app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt (1)
81-96: 무한 스크롤 트리거에 isLoadingMore 가드 추가 권장현재
shouldLoadMore는isLoadingMore상태를 고려하지 않아 레이아웃 정보 갱신 타이밍에 따라 중복 호출 가능성이 있습니다. 가벼운 가드를 추가해 중복 로딩을 방지하세요.- val shouldLoadMore by remember { + val shouldLoadMore by remember { derivedStateOf { val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull() - lastVisibleItem != null && lastVisibleItem.index >= roomList.size - 3 && canLoadMore + lastVisibleItem != null && + lastVisibleItem.index >= roomList.size - 3 && + canLoadMore && + !isLoadingMore } - } + }스크롤 하단에서 빠르게 상하로 튕길 때 onLoadMore가 1회만 호출되는지 로그로 한 번 확인 부탁드립니다.
app/src/main/java/com/texthip/thip/ui/group/search/component/GroupLiveSearchResult.kt (2)
35-49: 무한 스크롤 트리거에 isLoadingMore 가드 추가 권장 (중복 로딩 방지)
shouldLoadMore계산에!isLoadingMore를 포함시켜 중복 호출을 방지하세요.GroupFilteredSearchResult와 로직을 맞추면 일관성도 좋아집니다.- val shouldLoadMore by remember { + val shouldLoadMore by remember { derivedStateOf { val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull() - lastVisibleItem != null && lastVisibleItem.index >= roomList.size - 3 && canLoadMore + lastVisibleItem != null && + lastVisibleItem.index >= roomList.size - 3 && + canLoadMore && + !isLoadingMore } - } + }
51-87: 중복 코드 축소 제안: 무한 스크롤 감지 유틸/컴포저블로 공통화
GroupFilteredSearchResult와 본 컴포저블의 무한 스크롤 감지·로딩 인디케이터 로직이 거의 동일합니다. 공통 훅/유틸(예: rememberEndlessListLoader(state, canLoadMore, isLoadingMore, threshold = 3))로 추출하면 유지보수성이 좋아집니다.원하시면 공통 헬퍼 컴포저블/함수를 제안드리겠습니다.
app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookGroupScreen.kt (1)
185-201: 무한 스크롤 snapshotFlow에 중복 방지 연산자 추가 제안레이아웃 변경이 빈번할 때 같은 인덱스가 연속 방출될 수 있습니다.
distinctUntilChanged()를 추가해 여지를 줄이세요.- LaunchedEffect(listState, canLoadMore, isLoadingMore) { - snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index } - .collect { lastVisibleIndex -> + LaunchedEffect(listState, canLoadMore, isLoadingMore) { + snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index } + .distinctUntilChanged() + .collect { lastVisibleIndex -> if (lastVisibleIndex != null && recruitingList.isNotEmpty() && !isLoadingMore && lastVisibleIndex >= recruitingList.size - 3 && canLoadMore ) { onLoadMore() } } }주의:
kotlinx.coroutines.flow.distinctUntilChanged임포트가 필요합니다.app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt (5)
125-139: 라이브 검색 중 로딩 상태 표시가 없어 빈 화면으로 보일 수 있습니다.
uiState.isSearching동안에는showEmptyState가 false이고hasResults도 false일 수 있어 아무것도 렌더링되지 않습니다. 간단한 로딩 인디케이터를 추가하는 것이 좋습니다.아래처럼 분기 하나를 추가해 주세요.
uiState.isLiveSearching -> { - if (uiState.showEmptyState) { + if (uiState.showEmptyState) { GroupEmptyResult( mainText = stringResource(R.string.group_no_search_result1), subText = stringResource(R.string.group_no_search_result2) ) - } else if (uiState.hasResults) { + } else if (uiState.isSearching) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + androidx.compose.material3.CircularProgressIndicator(color = colors.White) + } + } else if (uiState.hasResults) { GroupLiveSearchResult( roomList = uiState.searchResults, onRoomClick = { room -> onRoomClick(room.roomId) }, canLoadMore = uiState.canLoadMore, isLoadingMore = uiState.isLoadingMore, onLoadMore = { viewModel.loadMoreRooms() } ) } }
58-62: 정렬 타입의 하드코딩 문자열 제거 제안."deadline"/"memberCount" 문자열 비교는 오타 리스크가 커서 상수/Enum으로 통일하는 것이 안전합니다. ViewModel에 상수를 두고 참조하도록 변경을 권장합니다.
ViewModel에 상수 추가 후 이 분기를 다음과 같이 바꿔주세요.
- val selectedSortOptionIndex = when (uiState.selectedSort) { - "deadline" -> 0 - "memberCount" -> 1 - else -> 0 - } + val selectedSortOptionIndex = when (uiState.selectedSort) { + GroupSearchViewModel.SORT_DEADLINE -> 0 + GroupSearchViewModel.SORT_MEMBER_COUNT -> 1 + else -> 0 + }ViewModel 쪽 변경은 아래 코멘트에 함께 제안합니다.
182-188: 정렬 타입 매핑의 중복 문자열을 상수로 통일.UI와 ViewModel 모두에서 같은 문자열을 사용합니다. 상수 사용으로 단일 출처 유지가 좋습니다.
- val sortType = when (sortOptions.indexOf(selected)) { - 0 -> "deadline" - 1 -> "memberCount" - else -> "deadline" - } + val sortType = when (sortOptions.indexOf(selected)) { + 0 -> GroupSearchViewModel.SORT_DEADLINE + 1 -> GroupSearchViewModel.SORT_MEMBER_COUNT + else -> GroupSearchViewModel.SORT_DEADLINE + } viewModel.updateSortType(sortType)
103-170: 방어적 렌더링 분기 보강 제안.ViewModel 오류/널 응답 시 모든 UI 플래그가 false가 되면(버그 시나리오) 빈 화면이 됩니다. ViewModel 수정이 근본 해결이나, 여기에도 마지막 else 분기를 두고 에러/빈 상태를 보여주면 안전합니다.
원한다면 마지막에
else -> GroupEmptyResult(...)또는 에러 메시지 표시 분기를 추가해 드릴 수 있습니다.
195-201: Preview에서 hiltViewModel 사용 회피 권장.Preview에서 Hilt ViewModel을 직접 생성하면 미리보기 런타임에 실패할 수 있습니다. 미리보기 전용 Fake ViewModel을 주입하거나 파라미터를 노출하는 오버로드를 두는 방식을 권장합니다.
원하시면 Preview용 스텁 ViewModel 샘플 코드를 제공하겠습니다.
app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt (4)
35-36: 정렬 타입 문자열 상수화로 단일 출처 유지.문자열 "deadline"/"memberCount"를 상수로 노출해 UI와 ViewModel이 공유하도록 하세요.
- val selectedSort: String = "deadline", // "deadline" 또는 "memberCount" + val selectedSort: String = SORT_DEADLINE, // "deadline" 또는 "memberCount"ViewModel 클래스 내부에 아래 상수를 추가하세요.
class GroupSearchViewModel @Inject constructor( private val roomsRepository: RoomsRepository, private val recentSearchRepository: RecentSearchRepository ) : ViewModel() { + companion object { + const val SORT_DEADLINE = "deadline" + const val SORT_MEMBER_COUNT = "memberCount" + const val RECENT_SCOPE_ROOM = "ROOM" + }UI 파일의 문자열 비교/매핑은 이 상수를 사용하도록 변경해주세요(별도 코멘트 참고).
151-157: 정렬 타입 업데이트 문자열 하드코딩 제거.상수를 사용해 오타 리스크를 줄이세요.
- fun updateSortType(sort: String) { + fun updateSortType(sort: String) { updateState { it.copy(selectedSort = sort) } // 정렬 변경 시 새로운 검색 수행 (공백도 허용) if (uiState.value.searchQuery.isNotEmpty() && !uiState.value.isInitial) { performSearchWithCurrentQuery() } }사용처는
SORT_DEADLINE/SORT_MEMBER_COUNT로 제한하는 것을 권장합니다(예: UI 매핑에서만 생성).
219-219: 에러 메시지의 문자열 리소스/이벤트화 권장.ViewModel에서 하드코딩된 문자열을 직접 노출하면 현지화와 테스트가 어렵습니다. 에러 코드를 상태로 들고, UI에서 stringResource로 변환하거나, UI Event(Snackbar/Toast)로 전달하는 패턴을 권장합니다.
원하시면 ErrorState/UiEvent로 리팩터를 제안해 드리겠습니다.
281-295: "ROOM" 스코프 문자열 상수화 제안.매직 스트링 대신 상수를 사용하면 추후 도메인 확장/변경에 안전합니다.
- recentSearchRepository.getRecentSearches("ROOM") + recentSearchRepository.getRecentSearches(RECENT_SCOPE_ROOM)상수는 companion object에 추가하는 안을 위에서 제안했습니다.
📜 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 (19)
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsSearchResponse.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(2 hunks)app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt(3 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/myroom/mock/GroupCardItemRoomData.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt(6 hunks)app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomRecruitScreen.kt(7 hunks)app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt(6 hunks)app/src/main/java/com/texthip/thip/ui/group/search/component/GroupLiveSearchResult.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookGroupScreen.kt(9 hunks)app/src/main/java/com/texthip/thip/utils/rooms/DateUtils.kt(0 hunks)app/src/main/java/com/texthip/thip/utils/rooms/RoomUtils.kt(0 hunks)app/src/main/res/values/strings.xml(1 hunks)
💤 Files with no reviewable changes (2)
- app/src/main/java/com/texthip/thip/utils/rooms/RoomUtils.kt
- app/src/main/java/com/texthip/thip/utils/rooms/DateUtils.kt
🧰 Additional context used
🧬 Code Graph Analysis (3)
app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt (1)
app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt (1)
CardItemRoomSmall(36-157)
app/src/main/java/com/texthip/thip/ui/group/search/component/GroupLiveSearchResult.kt (1)
app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt (1)
CardItemRoomSmall(36-157)
app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt (5)
app/src/main/java/com/texthip/thip/ui/group/search/component/GroupRecentSearch.kt (1)
GroupRecentSearch(30-65)app/src/main/java/com/texthip/thip/ui/group/search/component/GroupEmptyResult.kt (1)
GroupEmptyResult(18-40)app/src/main/java/com/texthip/thip/ui/group/search/component/GroupLiveSearchResult.kt (1)
GroupLiveSearchResult(27-88)app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt (1)
GroupFilteredSearchResult(37-136)app/src/main/java/com/texthip/thip/ui/common/buttons/FilterButton.kt (1)
FilterButton(37-128)
🔇 Additional comments (22)
app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt (1)
133-136: 검증 완료:searchRooms파라미터 순서 일치
RoomsService.searchRooms인터페이스 정의
— keyword, category, sort, isFinalized, cursor호출부(
RoomsRepository.kt라인 133–136)에서도 동일한 순서와 타입으로 전달하고 있어 문제가 없습니다.app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt (1)
42-42: endDate 타입 변경 확인 완료모든 호출부(
GroupFilteredSearchResult,GroupLiveSearchResult,GroupRoomRecruitScreen및 Preview)에서String형태로endDate가 전달되고 있음을 확인했습니다. 문제 없이 머지 가능합니다.app/src/main/java/com/texthip/thip/data/service/RoomsService.kt (1)
31-31: 모델 임포트 추가 적절신규 응답 모델 임포트가 정확합니다. 빌드 안정성 측면에서 문제 없습니다.
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (1)
173-181: 일관성 확인 완료: Int 타입으로 일치
navigateToGroupRecruit(roomId: Int)확장함수의 파라미터와
GroupSearchScreen(onRoomClick: (Int) -> Unit)의 인자는 모두 Int 타입으로 정의되어 있어
타입 불일치 우려가 없습니다. 해당 코멘트는 더 이상 적용되지 않습니다.app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsSearchResponse.kt (1)
6-11: 응답 래퍼 모델 정의 적절페이지네이션 필드(
nextCursor,isLast)와 리스트 기본값이 명확합니다. 직렬화 어노테이션 사용도 올바릅니다.app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomRecruitScreen.kt (3)
58-67: AI 요약과 불일치: onNavigateToGroupScreen 파라미터는 제거되지 않았습니다AI 요약에는 공개 API에서
onNavigateToGroupScreen파라미터가 제거되었다고 되어 있으나, 본 코드에는 동일 파라미터가 그대로 존재하고 사용되고 있습니다. 요약 업데이트가 필요해 보입니다.
99-107: 비밀방 참여 분기 처리 로직 적절비밀방 여부에 따라 비밀번호 입력 화면으로 분기하는 로직이 명확하며, 공개방은 즉시 참여 트리거로 분기되어 UX 의도에 부합합니다.
400-407: 추천 카드 endDate 바인딩 변경 적절
CardItemRoomSmall의endDate가 문자열로 전환됨에 따라rec.recruitEndDate를 그대로 전달하는 변경이 일관성 있게 반영되었습니다.app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt (2)
41-41: endDate 타입을 Int? → String?로 전환한 공개 API 변경 적절서버의 문자열 기반 마감일 응답에 맞춰 컴포넌트 API를 문자열로 변경한 방향이 타당합니다. 호출부와 미리보기도 일관되게 반영되었습니다.
호출하는 모든 곳이 새 시그니처로 컴파일되는지 CI에서 확인 부탁드립니다.
85-87: 레이아웃 포맷 조정은 OK
fillMaxWidth()와 고정 높이 지정의 포맷 조정은 기능 영향이 없고 현재 UI 구조에 적합합니다.app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt (2)
97-133: 로딩 인디케이터 아이템 처리 깔끔 — LGTM
isLoadingMore일 때 하단에 인디케이터 아이템을 추가하는 방식이 깔끔하고 레이아웃 교란을 최소화합니다.
100-107: 데이터 모델 매핑 적절 및 비밀방 여부 반영 OK
SearchRoomItem→CardItemRoomSmall필드 매핑이 정확하고,isSecret = !room.isPublic처리도 명확합니다.app/src/main/java/com/texthip/thip/ui/group/search/component/GroupLiveSearchResult.kt (1)
54-61: 데이터 모델 매핑 적절 및 비밀방 여부 반영 OK라이브 검색 결과에서도
SearchRoomItem매핑과!room.isPublic처리가 정확합니다.app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookGroupScreen.kt (5)
59-76: VM 데이터 그대로 연동하는 방향 좋습니다UI에서 별도 매핑 없이
uiState.recruitingRooms를 직접 전달하고,onLoadMore에서 VM 메서드를 호출하는 구조가 단순하고 명확합니다.
80-96: 모집 중 리스트 타입 교체(RecruitingRoomItem) 적절
CardItemRoom에 필요한 필드가 모두 포함되어 있어, 별도 변환 없이 바인딩할 수 있는 점이 좋습니다.
208-217: CardItemRoom 바인딩 변경 적절문자열 기반 마감일(
deadlineEndDate)과 이미지 URL 바인딩이 새 모델에 맞게 정확히 반영되었습니다.isRecruiting = true하드코딩도 화면 성격상 타당합니다.
240-258: 하단 CTA 버튼과 리스트 패딩 처리 깔끔 — LGTM
contentPadding = PaddingValues(bottom = 80.dp)로 겹침 방지, 버튼 스타일 일관성 유지 모두 좋습니다.
266-289: Preview 데이터 최신 모델 사용 OK미리보기가
RecruitingRoomItem으로 갱신되어 디자인 검증에 유용합니다.app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt (2)
65-69: 검색 완료 시 포커스 해제 처리 LGTM.검색 완료 신호에 맞춘 포커스 해제는 UX 상 자연스럽습니다.
93-99: 텍스트 바인딩 및 검색 트리거 LGTM.uiState 바인딩과 onSearch에서 ViewModel 액션 호출이 일관적입니다.
app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt (2)
345-349: onCleared에서 Job 취소 처리 LGTM.코루틴 리소스 정리가 명확합니다.
242-249: 라이브 검색 페이지네이션isFinalized플래그 일관성 검증 필요
- 첫 페이지 호출:
isFinalized = !isLiveSearch(라이브 모드 → false)- 추가 페이지 로딩:
isFinalized = true(항상 true)이대로 두면 백엔드가 페이지별
isFinalized를 다르게 해석했을 때 결과 리스트가 일관되지 않을 수 있습니다.
백엔드 API 명세 또는 실제 동작을 확인해 주세요.필요 시:
- 라이브 모드 추가 페이지 로딩에도
isFinalized = false적용- 또는 라이브 검색에서 무한 스크롤 비활성화
레포 내 호출 위치 빠르게 확인용 스크립트:
rg -nP --glob '!**/build/**' -C3 '\bsearchRooms\s*\(' rg -nP --glob '!**/build/**' -C2 '\bisFinalized\s*='
| val endDate: String? = null, // 마감 시간 텍스트 (예: "8시간 뒤") | ||
| val imageUrl: String? = null, // API에서 받은 이미지 URL | ||
| val isSecret: Boolean = false | ||
| ) | ||
|
|
||
|
|
||
| ) No newline at end of file |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
endDate의 타입 변경(Int? → String?) 일관성 좋습니다
UI 전반이 문자열 기반 마감 표기로 이행되면서 본 데이터 모델 수정이 방향성에 부합합니다.
레거시(Int) 타입이나 변환 유틸 의존이 남아있는지 확인 부탁드립니다:
🏁 Script executed:
#!/bin/bash
# endDate가 Int로 선언/사용되는 곳 탐지
rg -nP -C2 '\bendDate\s*:\s*Int\??' --type=kt
# 남은 일수 계산 유틸 호출 탐지
rg -nP -C2 'getEndDateInDays|DateUtils' --type=ktLength of output: 153
endDate의 Int 타입 선언/사용 및 변환 유틸 호출 여부를 Kotlin 파일 전역에서 재검증합니다. 다음 스크립트를 실행해주세요:
🏁 Script executed:
#!/bin/bash
# endDate가 Int로 선언/사용되는 곳 탐지 (Kotlin 파일)
rg -nP -C2 '\bendDate\s*:\s*Int\??' -g '*.kt'
# 남은 일수 계산 유틸 호출 탐지 (Kotlin 파일)
rg -nP -C2 'getEndDateInDays|DateUtils' -g '*.kt'Length of output: 577
endDate 변환 유틸리티 정비 필요
String?으로 변경된 endDate 타입에 맞춰, 기존의 정수형 변환 유틸(getEndDateInDays)이 아직 남아 있습니다. 해당 메서드가 더 이상 사용되지 않거나, 새로운 문자열 기반 로직으로 대체되어야 합니다.
수정 대상:
- app/src/main/java/com/texthip/thip/ui/group/done/mock/MyRoomCardData.kt
• fun MyRoomCardData.getEndDateInDays(): Int { … }
조치:
- 해당 메서드를 제거하거나
- 문자열 파싱 로직으로 업데이트하여 코드베이스 전체에서 일관된
String타입 처리 보장
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardItemRoomData.kt
around lines 9 to 12, the endDate field was changed to String? but the old
integer-based utility getEndDateInDays() in
app/src/main/java/com/texthip/thip/ui/group/done/mock/MyRoomCardData.kt still
assumes an Int; remove that obsolete method or replace its logic with a
String-based parser that accepts the nullable endDate string, safely handles
null/empty values, parses formats used by the API (e.g., “8시간 뒤”, ISO
timestamps, or relative strings), and returns a consistent Int result (or change
callers to expect String), updating all usages across the codebase to use the
new string-handling utility or removed method accordingly.
| endDate = room.endDate, | ||
| imageUrl = room.bookImageUrl, | ||
| onClick = { onCardClick(room) } | ||
| ) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
endDate를 문자열로 직접 전달하도록 변경된 점 확인(LGTM) + 전파 영향 검증 요청
CardItemRoom의 endDate가 String?로 이관되었다면 본 변경은 타당합니다. 남은 참조 중 Int 기반(endDate: Int? 또는 변환 유틸) 사용이 없는지 전체 코드베이스 검증을 권장합니다.
다음 스크립트로 시그니처/잔존 참조를 확인해주세요:
🏁 Script executed:
#!/bin/bash
# CardItemRoom의 endDate 파라미터 타입이 String?인지 확인
rg -nP -C3 'fun\s+CardItemRoom\s*\([^)]*endDate\s*:\s*String\?' --type=kt
# endDate를 Int로 사용하는 잔존 호출/정의 탐지
rg -nP -C2 '\bendDate\s*:\s*Int\??' --type=kt
# 삭제/미사용이어야 할 과거 유틸 호출 탐지
rg -nP -C2 'getEndDateInDays|DateUtils' --type=ktLength of output: 256
파일 타입 인식 에러로 --type=kt 대신 glob 패턴을 사용해 .kt 파일에서 재검증합니다.
🏁 Script executed:
#!/bin/bash
# CardItemRoom의 endDate 파라미터 타입이 String?인지 확인
rg -nP -C3 'fun\s+CardItemRoom\s*\([^)]*endDate\s*:\s*String\?' -g '*.kt'
# endDate를 Int로 사용하는 잔존 호출/정의 탐지
rg -nP -C2 '\bendDate\s*:\s*Int\??' -g '*.kt'
# 삭제/미사용이어야 할 과거 유틸 호출 탐지
rg -nP -C2 'getEndDateInDays|DateUtils' -g '*.kt'Length of output: 653
endDate를 String?으로 이관 확인 완료, 구 변환 유틸 제거 필요
CardItemRoom의 endDate 파라미터가 정상적으로 String?로 변경되었으나, mock 데이터 클래스에 여전히 Int 변환용 유틸(getEndDateInDays())이 남아 있습니다. 코드 정합성을 위해 해당 메서드 및 호출부를 제거하거나 String? 기반 로직으로 대체해주세요.
- app/src/main/java/com/texthip/thip/ui/group/done/mock/MyRoomCardData.kt:32-34 (
fun getEndDateInDays())
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt
around lines 163-166 and
app/src/main/java/com/texthip/thip/ui/group/done/mock/MyRoomCardData.kt around
lines 32-34: the CardItemRoom now accepts endDate as String?, but the mock still
exposes an Int conversion util getEndDateInDays() and/or calls it; remove the
getEndDateInDays() function from MyRoomCardData and eliminate any calls to it,
and update the mock data to supply endDate as a nullable String (formatted the
same way production code expects) so the screen passes room.endDate directly to
CardItemRoom without converting to Int.
| @@ -31,73 +26,44 @@ import com.texthip.thip.ui.common.buttons.FilterButton | |||
| import com.texthip.thip.ui.common.forms.SearchBookTextField | |||
| import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar | |||
| import com.texthip.thip.ui.group.myroom.component.GroupRecentSearch | |||
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
잘못된 import 경로로 인한 컴파일 오류 가능성 (GroupRecentSearch).
GroupRecentSearch의 실제 패키지는 ui/group/search/component로 보입니다. 현재 ui/group/myroom/component로 import되어 있어 컴파일 실패가 예상됩니다.
아래 변경으로 경로를 수정하세요.
-import com.texthip.thip.ui.group.myroom.component.GroupRecentSearch
+import com.texthip.thip.ui.group.search.component.GroupRecentSearch필요 시 아래 스크립트로 파일 위치를 확인해 주세요.
🏁 Script executed:
#!/bin/bash
# GroupRecentSearch 실제 파일 위치 및 패키지 확인
fd -t f 'GroupRecentSearch.kt' | while read -r f; do
echo "File: $f"
sed -n '1,5p' "$f"
echo
doneLength of output: 545
GroupRecentSearch 패키지 선언 및 import 경로 정정 필요
GroupRecentSearch.kt 파일의 패키지 선언이 실제 경로(ui/group/search/component)와 불일치하여, 아래와 같이 수정이 필요합니다.
• app/src/main/java/com/texthip/thip/ui/group/search/component/GroupRecentSearch.kt
- package com.texthip.thip.ui.group.myroom.component
+ package com.texthip.thip.ui.group.search.component• app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt
- import com.texthip.thip.ui.group.myroom.component.GroupRecentSearch
+ import com.texthip.thip.ui.group.search.component.GroupRecentSearch📝 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.
| import com.texthip.thip.ui.group.myroom.component.GroupRecentSearch | |
| import com.texthip.thip.ui.group.search.component.GroupRecentSearch |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt
around line 28, the import points to
com.texthip.thip.ui.group.myroom.component.GroupRecentSearch which doesn't match
the actual file location; change the import to
com.texthip.thip.ui.group.search.component.GroupRecentSearch. Also open
app/src/main/java/com/texthip/thip/ui/group/search/component/GroupRecentSearch.kt
and correct its package declaration to package
com.texthip.thip.ui.group.search.component so it matches the file path and the
updated import.
app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt
Outdated
Show resolved
Hide resolved
app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt
Show resolved
Hide resolved
app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt
Show resolved
Hide resolved
| <string name="card_item_end">" 모집 마감"</string> | ||
| <string name="time_remaining_after">뒤</string> | ||
| <string name="time_remaining_left">남음</string> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
따옴표와 선행 공백 제거, 포맷 리소스 추가로 조합 일관성 확보 권장
" 모집 마감"처럼 값에 따옴표와 선행 공백이 포함되어 있어 화면에 그대로 노출될 수 있습니다.- 토큰을 분리한 목적대로라면, UI에서는 포맷 리소스로 안전하게 결합하는 편이 낫습니다.
아래처럼 조정해 주세요.
적용 diff:
- <string name="card_item_end">" 모집 마감"</string>
+ <string name="card_item_end">모집 마감</string>추가(선택 영역 밖, 지원용 코드): 카드에서 사용할 포맷 문자열
<string name="card_item_deadline_format">%1$s %2$s</string>참고: 기존 "card_item_end_date"의 따옴표/후행 공백("%1$s일 뒤 ")도 동일한 이슈가 있으니, 사용하지 않는다면 제거하거나 따옴표/공백을 정리하는 것을 권장합니다.
🤖 Prompt for AI Agents
In app/src/main/res/values/strings.xml around lines 49-51, remove the stray
quotation marks and leading space from the value for card_item_end (change from
"\" 모집 마감\"" to "모집 마감"), ensure time_remaining_after and time_remaining_left
remain plain tokens, and add a format resource string
(card_item_deadline_format) to combine tokens safely in UI (use %1$s %2$s when
formatting) so UI concatenation uses the format resource rather than relying on
embedded spaces/quotes; also review and clean up similar quotes/trailing spaces
in card_item_end_date if unused.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt (1)
28-28: GroupRecentSearch import 경로 수정 확인이전 리뷰에서 지적된 잘못된 import 경로가 올바르게 수정되었습니다.
🧹 Nitpick comments (7)
app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt (7)
12-13: collectAsState 대신 collectAsStateWithLifecycle 사용 권장Compose 화면 Lifecycle에 맞춘 안전한 수집을 위해
collectAsStateWithLifecycle()사용을 권장합니다.다음 변경을 제안합니다:
- import androidx.compose.runtime.collectAsState + import androidx.lifecycle.compose.collectAsStateWithLifecycle ... - val uiState by viewModel.uiState.collectAsState() + val uiState by viewModel.uiState.collectAsStateWithLifecycle()Also applies to: 43-43
47-48: toDisplayStrings 계산 최적화(불필요한 재계산 방지)recomposition마다 매번 계산하지 않도록
remember로 메모이징하는 편이 좋습니다.- val genreDisplayNames = uiState.genres.toDisplayStrings() + val genreDisplayNames = remember(uiState.genres) { uiState.genres.toDisplayStrings() }
97-99: 검색 즉시 키보드 숨김으로 UX 개선검색 완료 시점이 아니라 버튼을 누르는 즉시 키보드를 숨기면 사용자 체감이 더 자연스럽습니다. 완료 시점에 한 번 더
clearFocus()가 호출되어도 부작용 없습니다.- onSearch = { - viewModel.onSearchButtonClick() - } + onSearch = { + viewModel.onSearchButtonClick() + focusManager.clearFocus() + }
104-122: 최근 검색 분기 중복 제거빈 목록/비빈 목록에 따라 동일 컴포넌트를 두 번 호출하고 있어 불필요한 분기가 존재합니다.
GroupRecentSearch자체가 빈 목록 처리 UI를 포함하므로 단일 호출로 단순화할 수 있습니다.- uiState.isInitial -> { - if (uiState.recentSearches.isEmpty()) { - GroupRecentSearch( - recentSearches = emptyList(), - onSearchClick = {}, - onRemove = {} - ) - } else { - GroupRecentSearch( - recentSearches = uiState.recentSearches.map { it.searchTerm }, - onSearchClick = { keyword -> - viewModel.updateSearchQuery(keyword) - viewModel.onSearchButtonClick() - }, - onRemove = { keyword -> - viewModel.deleteRecentSearchByKeyword(keyword) - } - ) - } - } + uiState.isInitial -> { + GroupRecentSearch( + recentSearches = uiState.recentSearches.map { it.searchTerm }, + onSearchClick = { keyword -> + viewModel.updateSearchQuery(keyword) + viewModel.onSearchButtonClick() + }, + onRemove = { keyword -> + viewModel.deleteRecentSearchByKeyword(keyword) + } + ) + }
146-161: selectedGenreIndex 중복 계산 제거 및 안전 인덱싱이미 계산한
selectedGenreIndex를 재활용하면 가독성과 안전성이 좋아집니다. 또한getOrNull로 인덱스 범위를 안전하게 처리할 수 있습니다.- onGenreSelect = { index -> - val currentSelectedIndex = if (uiState.selectedGenre != null) { - uiState.genres.indexOf(uiState.selectedGenre) - } else -1 - - val selectedGenre = if (index == currentSelectedIndex) { - // 같은 장르를 다시 터치하면 선택 해제 - null - } else if (index >= 0 && index < uiState.genres.size) { - // 새로운 장르 선택 - uiState.genres[index] - } else { - null - } - viewModel.updateSelectedGenre(selectedGenre) - }, + onGenreSelect = { index -> + val selectedGenre = + if (index == selectedGenreIndex) null + else uiState.genres.getOrNull(index) + viewModel.updateSelectedGenre(selectedGenre) + },
125-140: 라이브 검색 중 로딩 상태 피드백 추가 제안
isLiveSearching상태에서showEmptyState == false이고hasResults == false인 구간에 아무 UI가 노출되지 않습니다. 로딩 인디케이터(스피너 등)를 추가하면 사용자 피드백이 개선됩니다.원한다면
uiState에 로딩 플래그가 있는지 확인하여 조건부로CircularProgressIndicator를 렌더링하는 패치를 제안드릴 수 있습니다.
176-179: FilterButton 위치 하드코딩(196.dp) 의존성상단 앱바/검색창 높이가 변경되면 정렬 버튼 위치가 어긋날 수 있습니다. 상단 영역의 실제 높이에 앵커링하거나, 상단 Column 내 해당 섹션 바로 아래에 배치하는 방식으로 레이아웃 의존성을 줄이는 것을 권장합니다.
원하시면 Insets/측정(Modifier.onGloballyPositioned) 기반으로 안전하게 위치시키는 예시 코드를 드리겠습니다.
📜 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 (3)
app/src/main/java/com/texthip/thip/ui/group/search/component/GroupRecentSearch.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/src/main/java/com/texthip/thip/ui/group/search/viewmodel/GroupSearchViewModel.kt
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/src/main/java/com/texthip/thip/ui/group/search/screen/GroupSearchScreen.kt (5)
app/src/main/java/com/texthip/thip/ui/group/search/component/GroupRecentSearch.kt (1)
GroupRecentSearch(27-62)app/src/main/java/com/texthip/thip/ui/group/search/component/GroupEmptyResult.kt (1)
GroupEmptyResult(18-40)app/src/main/java/com/texthip/thip/ui/group/search/component/GroupLiveSearchResult.kt (1)
GroupLiveSearchResult(27-88)app/src/main/java/com/texthip/thip/ui/group/search/component/GroupFilteredSearchResult.kt (1)
GroupFilteredSearchResult(37-136)app/src/main/java/com/texthip/thip/ui/common/buttons/FilterButton.kt (1)
FilterButton(37-128)
🔇 Additional comments (1)
app/src/main/java/com/texthip/thip/ui/group/search/component/GroupRecentSearch.kt (1)
1-1: LGTM – 잔여 import 경로 없음 확인됨
com.texthip.thip.ui.group.myroom.component.GroupRecentSearch로 남아있는 import가 전체 검색 결과 존재하지 않습니다.- Compose 버전이 1.5 미만이라면 FlowRow 사용 시
@OptIn(ExperimentalLayoutApi::class)어노테이션을 빌드 에러 발생 시점에 추가해 주세요.
➕ 이슈 링크
🔎 작업 내용
📸 스크린샷
😢 해결하지 못한 과제
[] TASK
📢 리뷰어들에게
Summary by CodeRabbit
신기능
UI 변경
기타