Conversation
…into api/THIP-TextHip#71-rooms_posts
…into api/THIP-TextHip#71-rooms_posts # Conflicts: # app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt # app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt
…into api/THIP-TextHip#71-rooms_posts
…into api/THIP-TextHip#71-rooms_posts
WalkthroughRooms 관련 요청/응답 모델 다수 추가 및 Retrofit 서비스·레포지토리 엔드포인트 확장. 그룹 노트/투표 화면을 MVVM으로 리팩터링하고 네비게이션 라우트/익스텐션을 추가. UI 컴포넌트 시그니처 변경 및 문자열·정렬 타입 리소스가 추가/명칭 변경됨. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UI as GroupNoteScreen
participant VM as GroupNoteViewModel
participant Repo as RoomsRepository
participant Svc as RoomsService
User->>UI: 화면 진입
UI->>VM: initialize(roomId, page, isOverview)
VM->>Repo: getRoomsPosts(...), getRoomsBookPage(roomId)
Repo->>Svc: GET rooms/{roomId}/posts
Svc-->>Repo: RoomsPostsResponse
Repo-->>VM: Result(success)
Repo->>Svc: GET rooms/{roomId}/book-page
Svc-->>Repo: RoomsBookPageResponse
Repo-->>VM: Result(success)
VM-->>UI: uiState 업데이트(posts, recent/total)
sequenceDiagram
actor User
participant UI as VoteCommentCard
participant VM as GroupNoteViewModel
participant Repo as RoomsRepository
participant Svc as RoomsService
User->>UI: 투표 선택
UI->>VM: OnVote(postId, voteItemId, type)
VM->>Repo: postRoomsVote(roomId, voteId, voteItemId, type)
Repo->>Svc: POST rooms/{roomId}/vote/{voteId}
Svc-->>Repo: RoomsVoteResponse
Repo-->>VM: Result(success)
VM-->>UI: posts 갱신/재조회
sequenceDiagram
actor User
participant UI as GroupNoteCreateScreen
participant VM as GroupNoteCreateViewModel
participant Repo as RoomsRepository
participant Svc as RoomsService
User->>UI: 기록 작성 완료 클릭
UI->>VM: CreateRecordClicked
VM->>Repo: postRoomsRecord(roomId, content, isOverview, page)
Repo->>Svc: POST rooms/{roomId}/record
Svc-->>Repo: RoomsRecordResponse
Repo-->>VM: Result(success)
VM-->>UI: isSuccess=true (네비게이션 종료)
sequenceDiagram
actor User
participant UI as Text/VoteCommentCard
participant VM as GroupNoteViewModel
participant Repo as RoomsRepository
participant Svc as RoomsService
User->>UI: 좋아요 클릭
UI->>VM: OnLikeRecord(postId, postType)
VM-->>UI: 낙관적 isLiked 토글
VM->>Repo: postRoomsPostsLikes(postId, type, roomPostType)
Repo->>Svc: POST room-posts/{postId}/likes
Svc-->>Repo: RoomsPostsLikesResponse
Repo-->>VM: Result(success or failure)
VM-->>UI: 실패 시 롤백
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 18
🔭 Outside diff range comments (3)
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomMatesScreen.kt (1)
39-41: LaunchedEffect 키를 roomId로 변경해 재진입/파라미터 변경 대응.현재 Unit을 키로 사용하면 동일 컴포저블 내에서 roomId가 바뀌어도 재호출되지 않습니다.
아래처럼 수정 권장:
- LaunchedEffect(key1 = Unit) { + LaunchedEffect(roomId) { viewModel.fetchUsers(roomId) }app/src/main/java/com/texthip/thip/ui/group/note/component/TextCommentCard.kt (1)
45-53: 프로필 이미지 동적 바인딩 적용 필요
- 현재
profileImage에 하드코딩된 URL을 사용하고 있으므로 실제 API 응답(data.profileImageUrl)이 반영되지 않습니다.data.page는 non-nullableInt이므로 null 처리 없이 기존data.page.toString() + stringResource(R.string.page)방식을 그대로 사용해도 안전합니다.다음과 같이 수정해주세요:
ProfileBar( modifier = Modifier.padding(0.dp), - profileImage = "https://example.com/image1.jpg", + profileImage = data.profileImageUrl ?: "", topText = data.nickName, - bottomText = data.page.toString() + stringResource(R.string.page), + bottomText = data.page.toString() + stringResource(R.string.page), bottomTextColor = colors.Purple, showSubscriberInfo = false, hoursAgo = data.postDate )app/src/main/java/com/texthip/thip/ui/group/note/component/VoteCommentCard.kt (1)
51-57: 프로필 이미지 null 처리 및 페이지 표시 개선 필요
PostList.page는 non-null Int이므로 safe call 대신 직접 보간을 사용하고,profileImageUrl은 nullable String이므로orEmpty()등으로 기본값을 지정해야 합니다.
- 수정 위치:
app/src/main/java/com/texthip/thip/ui/group/note/component/VoteCommentCard.kt(약 51–57줄)- ProfileBar( - profileImage = "https://example.com/image1.jpg", - topText = data.nickName, - bottomText = data.page.toString() + stringResource(R.string.page), - bottomTextColor = colors.Purple, - showSubscriberInfo = false, - hoursAgo = data.postDate - ) + ProfileBar( + profileImage = data.profileImageUrl.orEmpty(), + topText = data.nickName, + bottomText = "${data.page}${stringResource(R.string.page)}", + bottomTextColor = colors.Purple, + showSubscriberInfo = false, + hoursAgo = data.postDate + )
🧹 Nitpick comments (30)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)
51-52: 중복 import 제거 필요: CommentItem/ReplyItem이 두 번 import되었습니다Lines 51-52가 55-56과 중복입니다. 중복 import는 불필요한 경고를 유발하고 가독성을 떨어뜨립니다. 아래처럼 51-52 라인을 제거하세요.
-import com.texthip.thip.ui.group.note.component.CommentItem -import com.texthip.thip.ui.group.note.component.ReplyItemapp/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPostsLikesResponse.kt (1)
6-9: 기본값 제공으로 역직렬화 탄력성 향상 제안서버가 필드를 누락하는 경우를 대비해 기본값을 두면 안전합니다. 강제 실패가 의도라면 그대로 두셔도 됩니다.
다음 변경을 고려해 보세요:
data class RoomsPostsLikesResponse( - val postId: Int, - val isLiked: Boolean, + val postId: Int = 0, + val isLiked: Boolean = false, )app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPostsLikesRequest.kt (1)
7-8: roomPostType 문자열 남용 대신 Enum 도입 제안허용 가능한 값이 한정적이라면 문자열 대신 Enum을 사용해 타입 안정성과 오타를 방지하는 것이 좋습니다. 서버 값이
"RECORD","VOTE"등이라면@SerialName으로 매핑하세요.이 파일 범위에서의 변경:
- val roomPostType: String, + val roomPostType: RoomPostType,Enum 추가(새 코드 블록):
package com.texthip.thip.data.model.rooms.request import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable enum class RoomPostType { @SerialName("RECORD") RECORD, @SerialName("VOTE") VOTE, // 필요 시 서버 스펙에 맞춰 확장 @SerialName("POST") POST, }원하시면 레포지토리/서비스/뷰모델 사용처 일괄 변경도 도와드리겠습니다.
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsDeleteRecordResponse.kt (1)
6-8: 역직렬화 안정성 향상(옵션): 기본값 추가필드 누락 시 실패를 피하려면 기본값을 두는 것을 고려할 수 있습니다.
data class RoomsDeleteRecordResponse( - val roomId: Int, + val roomId: Int = 0, )app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsRecordResponse.kt (1)
5-9: 백엔드 필드 네이밍(스네이크 vs 카멜)과 ID 범위 확인 필요 (@SerialName 매핑 제안)서버가
record_id,room_id같은 snake_case를 반환한다면 현재 모델은 디코딩에 실패합니다. 또한 ID 값 범위가 Int를 넘어설 가능성이 있으면 Long이 안전합니다. 아래와 같이@SerialName매핑(필요 시)과 타입 업사이징을 고려해 주세요.package com.texthip.thip.data.model.rooms.response import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName @Serializable data class RoomsRecordResponse( - val recordId: Int, - val roomId: Int, + @SerialName("record_id") val recordId: Int, + @SerialName("room_id") val roomId: Int, )
- 만약 서버가 이미
recordId,roomId로 주면@SerialName은 생략해도 됩니다.- ID 값이 서버/DB에서 Long 범위일 경우 Int → Long 변경을 검토해 주세요.
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsCreateVoteRequest.kt (1)
5-16: 요청 스키마 키 일치 여부(@SerialName)와 네이밍 충돌 가능성(VoteItem) 점검
- 서버가
page,is_overview,content,vote_item_list,item_name와 같은 snake_case를 기대한다면 현재 필드명과 불일치합니다. 매핑을 권장합니다.- 최상위
VoteItem는 도메인/다른 계층의VoteItem과 충돌하기 쉬워 가독성과 유지보수가 떨어질 수 있습니다.CreateVoteItemRequest등으로 명시적 네이밍을 고려해 주세요.package com.texthip.thip.data.model.rooms.request import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName @Serializable data class RoomsCreateVoteRequest( - val page: Int, - val isOverview: Boolean, - val content: String, - val voteItemList: List<VoteItem> + @SerialName("page") val page: Int, + @SerialName("is_overview") val isOverview: Boolean, + @SerialName("content") val content: String, + @SerialName("vote_item_list") val voteItemList: List<CreateVoteItemRequest> ) @Serializable -data class VoteItem( - val itemName: String +data class CreateVoteItemRequest( + @SerialName("item_name") val itemName: String )
- 만약 서버가 이미 카멜케이스를 사용한다면
@SerialName은 생략 가능합니다.- 타입명을 변경하면 레포지토리/서비스/뷰모델 사용처 일괄 변경이 필요합니다. 원하시면 변경 범위 파악과 일괄 수정 패치를 도와드릴게요.
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPostsRequestParams.kt (1)
5-14: GET 요청에 @Body 사용 여부 및 QueryMap 변환 유틸 확인 요청
- 코드베이스 전체 검색 결과,
@Body RoomsPostsRequestParams를 사용하는@GET인터페이스는 발견되지 않았습니다.
→ 서비스 레이어(Retrofit 인터페이스) 시그니처를 직접 확인해 주시고, 만약@Body로 전달 중이라면 반드시@Query또는@QueryMap으로 변경해야 합니다.- null 필드는 쿼리에서 자동으로 제외될 수 있도록 DTO에
toQueryMap()확장 함수를 두는 것을 권장드립니다.type,sort처럼 허용값이 제한적인 필드는 enum + @SerialName 매핑 방식을 도입하면 오타 및 유효성 검증 이슈를 줄일 수 있습니다.// 예시: RoomsQueryMaps.kt fun RoomsPostsRequestParams.toQueryMap(): Map<String, String> = buildMap { put("type", type) sort?.let { put("sort", it) } pageStart?.let { put("page_start", it.toString()) } pageEnd?.let { put("page_end", it.toString()) } isOverview?.let { put("is_overview", it.toString()) } isPageFilter?.let{ put("is_page_filter", it.toString()) } cursor?.let { put("cursor", it) } }// Retrofit 인터페이스 예시 @GET("rooms/{roomId}/posts") suspend fun getRoomsPosts( @Path("roomId") roomId: Int, @QueryMap params: Map<String, String> ): BaseResponse<RoomsPostsResponse>app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt (2)
35-35: 서버 제공 색상값 적용으로 TODO 해소 제안.현재 테마 상수로 고정돼 있어 서버 값 반영이 지연됩니다. 서버에서 hex 형태(예: "#AABBCC" 또는 "AABBCC")로 전달된다면 안전 파싱 후 fallback 하도록 준비해두는 편이 좋습니다.
아래와 같은 유틸을 두고, 값이 유효하지 않으면 기존 colors.ScienceIt를 사용하는 패턴을 권장합니다:
import androidx.compose.ui.graphics.Color import android.graphics.Color as AndroidColor fun parseHexColorOrNull(hex: String?): Color? = runCatching { if (hex.isNullOrBlank()) return null val normalized = if (hex.startsWith("#")) hex else "#$hex" Color(AndroidColor.parseColor(normalized)) }.getOrNull()사용 예:
bottomTextColor = parseHexColorOrNull(member.aliasColor) ?: colors.ScienceIt
22-47: 긴 목록 가능성 고려 시 LazyColumn으로 전환 권장.Column + 수동 Divider는 항목 수가 많아질 때 메모리/측정 비용이 커집니다. LazyColumn(itemsIndexed)으로 전환하면 성능과 스크롤 경험이 개선됩니다.
예시:
LazyColumn( verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth().padding(20.dp) ) { itemsIndexed(userList) { index, member -> ProfileBar( /* ... */ ) if (index != userList.lastIndex) { HorizontalDivider(color = colors.DarkGrey02, thickness = 1.dp) } } }app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomMatesScreen.kt (2)
76-93: 스크롤 가능한 콘텐츠는 LazyColumn 사용으로 성능 개선.Column + verticalScroll 조합은 항목 수가 많을 때 비효율적입니다. GroupRoomMatesList가 목록 렌더링을 담당한다면 상위는 LazyColumn을 쓰거나, 상위에서 LazyColumn을 쓰고 하위는 단일 아이템 컴포저블로 쪼개는 방식이 유지보수와 성능 모두에 유리합니다.
62-63: 에러 메시지 UI/UX 보완.단순 Text 표시는 가독성과 접근성이 낮습니다. 스낵바/에러 상태 컴포저블(아이콘, 재시도 버튼)로 개선하고, 문자열은 stringResource로 일원화하세요.
app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt (4)
64-70: 중복 라우팅 방지를 위해 launchSingleTop 적용 권장.같은 화면으로 연속 navigate 시 스택 중복을 방지합니다.
-) { - navigate(GroupRoutes.Note(roomId = roomId, page = page, isOverview = isOverview)) +) { + navigate(GroupRoutes.Note(roomId = roomId, page = page, isOverview = isOverview)) { + launchSingleTop = true + } }
73-87: 생성 화면도 singleTop 적용으로 스택 중복 방지.) { - navigate( + navigate( GroupRoutes.NoteCreate( roomId = roomId, recentBookPage = recentBookPage, totalBookPage = totalBookPage, isOverviewPossible = isOverviewPossible ) - ) + ) { + launchSingleTop = true + } }
90-104: 투표 생성 화면도 singleTop 적용 권장.) { - navigate( + navigate( GroupRoutes.VoteCreate( roomId = roomId, recentPage = recentPage, totalPage = totalPage, isOverviewPossible = isOverviewPossible ) - ) + ) { + launchSingleTop = true + } }
73-78: 파라미터 네이밍 일관성 제안(recentBookPage/totalBookPage vs recentPage/totalPage).두 확장 함수 간 네이밍이 상이해 사용 시 혼동 소지가 있습니다. 통일(예: recentPage/totalPage)하거나 도메인 의도가 있다면 KDoc로 명확히 해주세요.
Also applies to: 91-95
app/src/main/java/com/texthip/thip/utils/type/SortType.kt (1)
15-17: 광범위 호환성을 위해 values() 사용으로 변경 제안.- return entries.find { it.apiKey == key } ?: LATEST + return values().firstOrNull { it.apiKey == key } ?: LATESTapp/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt (1)
33-37: 필드 네이밍 일관성 제언
NoteCreate는recentBookPage/totalBookPage,VoteCreate는recentPage/totalPage로 혼용되고 있습니다. 도메인 모델 상 동일 의미라면 동일한 이름을 쓰는 편이 가독성과 검색성에 유리합니다.Also applies to: 43-45
app/src/main/java/com/texthip/thip/ui/group/note/component/FilterHeaderSection.kt (1)
81-83: 적용 콜백 호출 순서 적절 — UI 닫기 전에 외부 적용 로직 트리거
onApplyPageFilter()호출 후 입력 UI를 닫는 순서는 사용자 경험에 부합합니다. 외부에서 비동기 요청을 수행한다면 로딩 처리/에러 처리 경로를 함께 설계해 주세요.app/src/main/java/com/texthip/thip/ui/group/note/component/VoteCommentCard.kt (1)
108-123: 프리뷰 데이터 보강 제안프리뷰에서
voteItems = emptyList()로 두면 실제 투표 UI 검증이 어렵습니다. 2~3개의 더미voteItems와 하나의isVoted=true항목을 넣어 UI 상태를 확인할 수 있게 하길 권장합니다.app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt (2)
33-41: 기본값 false로 인한 의도치 않은 서버 필터링 가능성getRoomsPosts의 isOverview/isPageFilter가 기본 false면, 필터 미사용 의도가 서버에서 ‘false’로 해석되어 동작이 달라질 수 있습니다. 기본값을 null로 두고 서버 디폴트에 맡기는 편이 안전합니다.
적용 diff:
suspend fun getRoomsPosts( roomId: Int, type: String = "group", sort: String? = "latest", pageStart: Int? = null, pageEnd: Int? = null, - isOverview: Boolean? = false, - isPageFilter: Boolean? = false, + isOverview: Boolean? = null, + isPageFilter: Boolean? = null, cursor: String? = null, ) = runCatching {
97-111: 불명확한 파라미터명(type: Boolean) → 의미 있는 이름으로 변경 제안Boolean 의미가 불분명한 type보다는 도메인 맥락에 맞는 이름이 가독성과 유지보수에 유리합니다. 예: isAgree/isLike 등.
적용 diff(예시):
- suspend fun postRoomsVote( + suspend fun postRoomsVote( roomId: Int, voteId: Int, voteItemId: Int, - type: Boolean + isAgree: Boolean ) = runCatching { roomsService.postRoomsVote( roomId = roomId, voteId = voteId, request = RoomsVoteRequest( voteItemId = voteItemId, - type = type + type = isAgree ) ).handleBaseResponse().getOrThrow() }- suspend fun postRoomsPostsLikes( + suspend fun postRoomsPostsLikes( postId: Int, - type: Boolean, + isLike: Boolean, roomPostType: String ) = runCatching { roomsService.postRoomsPostsLikes( postId = postId, request = RoomsPostsLikesRequest( - type = type, + type = isLike, roomPostType = roomPostType ) ).handleBaseResponse().getOrThrow() }Also applies to: 123-135
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (1)
221-225: 불필요한 상태 수집 제거val uiState by viewModel.uiState.collectAsStateWithLifecycle()가 사용되지 않습니다. 불필요한 재구성을 유발할 수 있으므로 제거를 권장합니다. (해당 import도 정리 권장)
적용 diff:
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupVoteCreateViewModel.kt (2)
86-90: 옵션 변경 로직 최적화 가능현재 전체 리스트를 복사하여 수정하고 있습니다. 더 효율적인 방법을 고려해보세요.
is GroupVoteCreateEvent.OptionChanged -> _uiState.update { - val newOptions = it.options.toMutableList() - newOptions[event.index] = event.text - it.copy(options = newOptions) + it.copy(options = it.options.mapIndexed { index, option -> + if (index == event.index) event.text else option + }) }
104-110: 옵션 제거 로직 최적화 가능옵션 제거 시에도 전체 리스트를 복사하여 수정하고 있습니다.
is GroupVoteCreateEvent.RemoveOptionClicked -> if (_uiState.value.options.size > 2) { _uiState.update { - val newOptions = it.options.toMutableList() - newOptions.removeAt(event.index) - it.copy(options = newOptions) + it.copy(options = it.options.filterIndexed { index, _ -> index != event.index }) } }app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteViewModel.kt (2)
159-165: 좋아요 개수 계산 로직 개선좋아요 개수를 토글할 때
coerceAtLeast(0)를 사용하고 있지만, 서버 응답과 불일치할 수 있습니다.val newIsLiked = !oldPost.isLiked -val newLikeCount = if (newIsLiked) oldPost.likeCount + 1 else oldPost.likeCount - 1 +val newLikeCount = if (newIsLiked) { + oldPost.likeCount + 1 +} else { + (oldPost.likeCount - 1).coerceAtLeast(0) +} val optimisticPost = oldPost.copy( isLiked = newIsLiked, - likeCount = newLikeCount.coerceAtLeast(0) + likeCount = newLikeCount )
213-214: 중복 조건 검사
isRefresh가 false일 때isLastPage검사가 중복됩니다.-if (currentState.isLoading || currentState.isLoadingMore || (currentState.isLastPage && !isRefresh)) return +if (currentState.isLoading || currentState.isLoadingMore) return +if (!isRefresh && currentState.isLastPage) returnapp/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (4)
111-119: 프로그레스 바 취소 로직 중복
progressJob?.cancel()호출 후 즉시progress.snapTo(1f)를 호출하는데, 이미 취소된 Job의 애니메이션이 완료되지 않을 수 있습니다.LaunchedEffect(uiState.isLoading) { if (!uiState.isLoading && showProgressBar) { - progressJob?.cancel() - progress.snapTo(1f) - delay(500) - showProgressBar = false + progressJob.value?.cancel() + progressJob.value = scope.launch { + progress.animateTo(1f, animationSpec = tween(200)) + delay(500) + showProgressBar = false + } } }
157-159: 선택된 게시물 상태 관리 통합 고려
selectedPostForComment와selectedPostForMenu를 별도로 관리하고 있는데, 하나의 상태로 통합하여 관리하는 것이 더 명확할 수 있습니다.- var selectedPostForComment by remember { mutableStateOf<PostList?>(null) } - var selectedPostForMenu by remember { mutableStateOf<PostList?>(null) } + var selectedPost by remember { mutableStateOf<PostList?>(null) } + var selectedPostAction by remember { mutableStateOf<String?>(null) } // "comment" or "menu"
362-367: 중복된 이벤트 핸들러 로직
TextCommentCard와VoteCommentCard에서 동일한 이벤트 핸들러들이 중복되고 있습니다. 공통 로직을 추출하여 재사용하면 좋겠습니다.+ val commonHandlers = remember { + mapOf( + "onCommentClick" to { post: PostList -> + selectedPostForComment = post + isCommentBottomSheetVisible = true + }, + "onLongPress" to { post: PostList -> selectedPostForMenu = post }, + "onPinClick" to { _: PostList -> isPinDialogVisible = true }, + "onLikeClick" to { postId: Int, postType: String -> + onEvent(GroupNoteEvent.OnLikeRecord(postId, postType)) + } + ) + } when (post.postType) { "RECORD" -> TextCommentCard( data = post, modifier = itemModifier, - onCommentClick = { isCommentBottomSheetVisible = true }, - onLongPress = { selectedPostForMenu = post }, - onPinClick = { isPinDialogVisible = true }, - onLikeClick = { postId, postType -> - onEvent(GroupNoteEvent.OnLikeRecord(postId, postType)) - } + onCommentClick = { commonHandlers["onCommentClick"]?.invoke(post) }, + onLongPress = { commonHandlers["onLongPress"]?.invoke(post) }, + onPinClick = { commonHandlers["onPinClick"]?.invoke(post) }, + onLikeClick = commonHandlers["onLikeClick"] as (Int, String) -> Unit )Also applies to: 376-381
468-470: 주석 처리된 코드 제거사용하지 않는 주석 처리된 코드는 제거해주세요.
-// selectedNoteRecord = null -// selectedNoteVote = null selectedPostForComment = null
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (36)
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsCreateVoteRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPostsLikesRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPostsRequestParams.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsRecordRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsVoteRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsBookPageResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsCreateVoteResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsDeleteRecordResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPostsLikesResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPostsResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsRecordResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsUsersResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsVoteResponse.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/buttons/GroupVoteButton.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/FilterHeaderSection.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/TextCommentCard.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/VoteCommentCard.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.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/note/screen/GroupVoteCreateScreen.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteCreateViewModel.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteViewModel.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupVoteCreateViewModel.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomBody.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomMatesScreen.kt(1 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(1 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/utils/type/SortType.kt(1 hunks)app/src/main/res/values/strings.xml(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt (3)
app/src/main/java/com/texthip/thip/ui/common/topappbar/InputTopAppBar.kt (1)
InputTopAppBar(25-69)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteCreateViewModel.kt (1)
onEvent(60-83)app/src/main/java/com/texthip/thip/ui/group/note/component/OpinionInputSection.kt (1)
OpinionInputSection(25-83)
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomBody.kt (2)
app/src/main/java/com/texthip/thip/ui/common/cards/CardChat.kt (1)
CardChat(23-61)app/src/main/java/com/texthip/thip/ui/common/cards/CardVote.kt (1)
CardVote(31-137)
app/src/main/java/com/texthip/thip/ui/group/note/component/VoteCommentCard.kt (1)
app/src/main/java/com/texthip/thip/ui/common/buttons/ActionBarButton.kt (1)
ActionBarButton(24-99)
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (6)
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteViewModel.kt (1)
onEvent(111-150)app/src/main/java/com/texthip/thip/ui/common/header/HeaderMenuBarTab.kt (1)
HeaderMenuBarTab(23-69)app/src/main/java/com/texthip/thip/ui/group/note/component/TextCommentCard.kt (1)
TextCommentCard(22-77)app/src/main/java/com/texthip/thip/ui/group/note/component/VoteCommentCard.kt (1)
VoteCommentCard(23-102)app/src/main/java/com/texthip/thip/ui/group/note/component/FilterHeaderSection.kt (1)
FilterHeaderSection(25-87)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt (1)
CommentBottomSheet(30-110)
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt (3)
app/src/main/java/com/texthip/thip/ui/common/topappbar/InputTopAppBar.kt (1)
InputTopAppBar(25-69)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupVoteCreateViewModel.kt (1)
onEvent(79-114)app/src/main/java/com/texthip/thip/ui/group/note/component/VoteInputSection.kt (1)
VoteInputSection(31-110)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (3)
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (1)
GroupNoteScreen(72-145)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt (1)
GroupNoteCreateScreen(39-66)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt (1)
GroupVoteCreateScreen(39-66)
🔇 Additional comments (38)
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPostsLikesResponse.kt (2)
6-9: Kotlin 버전 확인 필요 (1.4 이상)
현재 자동 스크립트로 Kotlin 플러그인 버전을 감지하지 못했습니다. 직접 아래 위치에서 Kotlin 버전이 1.4.0 이상인지 확인해 주세요:
- 프로젝트 루트의 build.gradle(.kts) 내
org.jetbrains.kotlin:kotlin-gradle-plugin버전- gradle.properties 또는 settings.gradle(.kts) 의
kotlin.version혹은ext.kotlin_version설정- libs.versions.toml 사용 시
kotlin = "<버전>"버전이 1.4.0 미만이라면 트레일링 콤마를 제거하거나 Kotlin을 업그레이드해야 컴파일 에러를 방지할 수 있습니다.
6-9: Boolean 응답 필드 키 매핑 확인 필요 (isLikedvsliked)
현재RoomsPostsLikesResponse는 다음과 같이 정의되어 있으며,NetworkModule.kt에서 GsonConverterFactory와 Kotlinx-Serialization Converter를 모두 등록하고 있어 JSON 키가 프로퍼티명과 정확히 일치해야 역직렬화가 성공합니다:@Serializable data class RoomsPostsLikesResponse( val postId: Int, val isLiked: Boolean, )서버가 실제로
"liked"키를 반환한다면, 아래와 같이 매핑을 명시해주세요:@@ data class RoomsPostsLikesResponse( - val isLiked: Boolean, + @SerialName("liked") + val isLiked: Boolean,Gson Converter를 위해서는 추가로 아래 어노테이션도 붙여주세요:
@field:SerializedName("liked") @SerialName("liked") val isLiked: Boolean확인할 사항:
room-posts/{postId}/likesAPI 응답 JSON 스키마에서 해당 필드 키app/src/main/java/com/texthip/thip/data/di/NetworkModule.kt내 ConverterFactory 등록 순서 및 설정app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsVoteResponse.kt (1)
6-10: 트레일링 콤마 적용 전 Kotlin 버전 점검 필요
Kotlin 1.4 이상에서만 지원되는 트레일링 콤마를 사용하고 있습니다. 프로젝트 설정에서 Kotlin 버전이 1.4 이상인지 확인해 주세요.확인 대상 파일:
- 루트 build.gradle(.kts) 또는 settings.gradle(.kts) 의 plugins 블록 또는 buildscript classpath 에서 org.jetbrains.kotlin:kotlin-gradle-plugin 버전
- gradle.properties 의 ext.kotlin_version
- 모듈별 build.gradle(.kts) 플러그인 적용부에서 명시적 버전
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPostsLikesRequest.kt (1)
5-9: 트레일링 콤마 호환성 검증 필요Kotlin의 트레일링 콤마 지원은 1.4.x 이상부터 안정화되었으므로, 빌드 환경에 적용된 Kotlin 플러그인 버전이 최소 1.4.0 이상인지 확인해 주세요.
– 확인 위치
• app/build.gradle(.kts) 또는 settings.gradle(.kts) 파일 내
– id("org.jetbrains.kotlin.jvm") version "…"
– kotlin("jvm") version "…"
– ext.kotlin_version = "…" (gradle.properties)
– 로컬 점검 예시./gradlew properties | grep "Kotlin"또는
./gradlew -q kotlinDslAccessorsReport버전이 1.4.0 미만이라면, 트레일링 콤마 제거 또는 Kotlin 플러그인 업그레이드를 고려해 주세요.
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsDeleteRecordResponse.kt (1)
6-8: 응답 모델·트레일링 콤마 지원 여부 수동 확인 필요
- API 명세/백엔드 샘플 응답에서
RoomsDeleteRecordResponse가roomId외 추가 필드를 반환하는지 검증해주세요.- 프로젝트의 Kotlin Gradle 플러그인 버전(또는
kotlin_version)이 1.4 이상인지 직접 확인해야 트레일링 콤마 사용이 안전합니다.위 두 사항을 확인하신 뒤, 문제가 없다면 본 모델 정의를 그대로 유지하셔도 됩니다.
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsCreateVoteResponse.kt (1)
5-9: 직렬화 키 매핑(@SerialName) 필요 여부 확인서버 응답 키가
vote_id,room_id일 수 있습니다. 불일치 시 역직렬화 실패가 발생합니다. 필요 시 아래처럼 매핑해 주세요.import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName @Serializable data class RoomsCreateVoteResponse( - val voteId: Int, - val roomId: Int + @SerialName("vote_id") val voteId: Int, + @SerialName("room_id") val roomId: Int )app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsBookPageResponse.kt (1)
5-11: Boolean 프리픽스 필드(isXxx)와 서버 키 일치 여부 점검kotlinx.serialization은 프로퍼티명을 그대로 사용합니다. 서버가
is_overview_possible,total_book_page,recent_book_page등 snake_case를 사용한다면 매핑이 필요합니다. 특히isOverviewPossible은 키 불일치가 잦은 편이니 확인 부탁드립니다.import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName @Serializable data class RoomsBookPageResponse( - val totalBookPage: Int, - val recentBookPage: Int, - val isOverviewPossible: Boolean, - val roomId: Int, + @SerialName("total_book_page") val totalBookPage: Int, + @SerialName("recent_book_page") val recentBookPage: Int, + @SerialName("is_overview_possible") val isOverviewPossible: Boolean, + @SerialName("room_id") val roomId: Int, )app/src/main/res/values/strings.xml (2)
226-236: 새로운 문자열 리소스가 올바르게 추가되었습니다.기록장 기능과 관련된 UI 메시지들이 적절히 추가되었고, 기존 검색 필터 관련 리소스들도 일관성 있게 리네이밍되었습니다.
359-360: 리소스 키 변경이 올바르게 반영되었습니다.
search_filter_popular과search_filter_latest에서sort_like와sort_latest로 변경된 것이 코드에서의 사용과 일치합니다.app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt (1)
68-69: 문자열 리소스 참조가 올바르게 업데이트되었습니다.strings.xml에서 변경된 리소스 키에 맞춰
sort_like와sort_latest로 정확히 업데이트되었습니다.app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPostsResponse.kt (3)
1-13: RoomsPostsResponse 데이터 모델이 올바르게 정의되었습니다.페이지네이션을 위한
nextCursor와isLast필드, 총평 활성화 여부를 위한isOverviewEnabled필드가 적절히 포함되었습니다.
15-31: PostList 데이터 모델의 필드 구성이 적절합니다.게시글의 모든 필요한 정보(작성자, 좋아요, 댓글, 잠금 상태 등)가 포함되어 있고, 투표 항목을 위한
voteItems필드도 올바르게 정의되었습니다.
33-39: VoteItems 데이터 모델이 투표 기능에 적합하게 설계되었습니다.투표 항목의 ID, 이름, 득표율, 투표 여부 등 필요한 모든 정보가 포함되어 있습니다.
app/src/main/java/com/texthip/thip/ui/common/buttons/GroupVoteButton.kt (4)
26-29: import문과 데이터 타입 변경이 올바르게 적용되었습니다.
VoteItems타입으로 변경되었고, UI 클리핑을 위한clipimport가 추가되었습니다.
37-37: 매개변수 타입이 새로운 데이터 모델에 맞게 업데이트되었습니다.
List<VoteItems>로 변경되어 응답 모델과 일치합니다.
74-89: UI 렌더링 로직이 개선되었습니다.
clip과background를 분리하여 더 명확한 컴포즈 스타일로 변경되었고, 진행률 바의 배경색 설정도 단순화되었습니다.
125-128: Preview 데이터가 새로운 데이터 타입에 맞게 수정되었습니다.
VoteItems생성자를 사용하여 올바른 타입으로 테스트 데이터가 생성되었습니다.app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsUsersResponse.kt (1)
15-15: aliasName 필드 변경 검증 완료
.alias참조는 더 이상 존재하지 않으며, UI 컴포넌트(GroupRoomMatesList.kt, GroupRoomMatesScreen.kt)에서도 모두aliasName으로 업데이트된 것을 확인했습니다. 변경이 올바르게 반영되었습니다.app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt (3)
33-33: aliasName 필드로의 바인딩 변경, 적절합니다.데이터 모델 변경(alias → aliasName)에 맞춰 UI 바인딩이 정확히 반영되었습니다.
60-63: 프리뷰 데이터의 aliasName 반영, 적절합니다.
67-70: 두 번째 프리뷰 항목도 aliasName 반영, 확인했습니다.app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomMatesScreen.kt (1)
106-116: 프리뷰의 aliasName 반영, 일관성 좋습니다.app/src/main/java/com/texthip/thip/utils/type/SortType.kt (2)
6-13: 정의 및 리소스 매핑 전반적으로 적절합니다.UI 표기와 API 키를 분리해둔 점, 기본값(LATEST) 처리 모두 명확합니다.
14-18: SortType companion object의 entries 사용은 문제가 없습니다
버전 카탈로그에 설정된 Kotlin 플러그인 버전이 2.2.0으로, enum.entries API가 안정적으로 지원됩니다.values()로의 대체는 불필요하므로 현행 코드를 유지해 주세요.app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomBody.kt (2)
51-54: 투표 UI 콜백 전달 연동 LGTM
CardVote에currentVotes와onVoteClick을 그대로 위임하는 구성은 직관적이며 확장성도 좋습니다. 비어있는 리스트 처리도CardVote내부에서 커버되는 것으로 보입니다.
20-29: 모든 GroupRoomBody 호출부에 시그니처 변경이 반영되었습니다
미리보기(preview)와GroupRoomScreen에서 모두currentVotes,onNavigateToNote,onVoteClick파라미터를 정상적으로 전달하고 있어 누락으로 인한 컴파일 에러 우려가 없습니다.app/src/main/java/com/texthip/thip/ui/group/note/component/TextCommentCard.kt (1)
25-27: 좋아요 콜백 외부 위임 및 상태 비저장화 전환은 적절합니다내부
isLiked상태를 제거하고,ActionBarButton에data.isLiked를 바인딩한 뒤onLikeClick을 외부로 위임한 구조는 단방향 데이터 흐름에 부합합니다.Also applies to: 61-68
app/src/main/java/com/texthip/thip/ui/group/note/component/FilterHeaderSection.kt (1)
27-27: modifier 주입 가능하도록 개선한 점 좋습니다루트
Box에 외부modifier를 전달하여 조합성을 높인 것은 적절합니다. 다양한 화면 레이아웃에서 재사용이 수월해집니다.Also applies to: 41-45
app/src/main/java/com/texthip/thip/ui/group/note/component/VoteCommentCard.kt (1)
33-35: GroupVoteButton 콜백 시그니처 검증 완료
onOptionSelected: (Int?) -> Unit형태임을 확인했습니다.
null 인덱스를 이용한 투표 해제 설계는 의도대로 동작하므로 추가 조치 필요 없습니다.
- 확인 경로:
app/src/main/java/com/texthip/thip/ui/common/buttons/GroupVoteButton.ktapp/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt (1)
55-59: 성공 이벤트 1회성 소비 확인 요청uiState.isSuccess에 의한 뒤로가기 내비게이션이 1회성으로 소비되는지(예: 처리 후 isSuccess 초기화, 혹은 One-shot Event 사용) 확인 부탁드립니다. 재구성 상황에서 중복 처리 가능성을 사전에 차단하는 것이 안전합니다.
app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt (1)
55-69: 개요 기록(isOverview=true)일 때 page=0 전송 여부 확인 필요postRoomsRecord가 page 기본값 0을 전송합니다. API가 0을 유효 페이지로 취급하면 원치 않는 결과가 나올 수 있습니다. isOverview=true일 때 page를 null로 보내는지, 또는 서버가 무시하는지 확인 부탁드립니다. 필요 시 page를 Int?로 변경하는 리팩터가 필요할 수 있습니다.
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt (2)
54-55: 노트 내비게이션 콜백 추가는 적절합니다상위에서 주입 가능한 콜백으로 확장하여 하위 컴포넌트까지 연결한 방향 좋습니다. 기본 no-op으로 하위 호환도 지켜집니다.
177-187: 투표 클릭 시 page null 가능성에 대한 가드 확인CurrentVote에서 isOverview=false인 경우 page가 항상 존재한다는 계약이 보장되는지 확인 부탁드립니다. 계약이 불명확하면 NPE를 예방하기 위한 가드를 추가하는 편이 안전합니다.
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (1)
191-194: navigateToGroupNote null 허용 확인 완료
navigateToGroupNote확장 함수와GroupRoutes.Note데이터 클래스 모두
page: Int? = null, isOverview: Boolean? = null
로 선언되어 있어 null 전달이 허용됩니다. 추가 검증이나 수정 없이 안전합니다.app/src/main/java/com/texthip/thip/data/service/RoomsService.kt (2)
39-39: 기본값 확인 필요
sort파라미터의 기본값이"latest"로 설정되어 있습니다. API 문서를 확인하여 이 값이 올바른지 검증해주세요.
44-44: 불필요한 trailing comma 제거Line 44의
cursor파라미터 뒤에 trailing comma가 있습니다. 이는 컴파일 오류를 일으킬 수 있습니다.- @Query("cursor") cursor: String? = null, + @Query("cursor") cursor: String? = nullLikely an incorrect or invalid review comment.
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupVoteCreateViewModel.kt (1)
92-98: 페이지 텍스트 저장/복원 로직 개선 필요
GeneralReviewToggled이벤트에서 체크박스를 반복적으로 토글할 때 원래 페이지 값이 손실될 수 있습니다. 체크박스를 해제할 때 항상savedPageText를 복원하지만, 체크 상태가 아닐 때 페이지 텍스트가 변경되면 이전 값이 복원되지 않을 수 있습니다.is GroupVoteCreateEvent.GeneralReviewToggled -> _uiState.update { if (event.isChecked) { - it.copy(isGeneralReview = true, savedPageText = it.pageText, pageText = "") + // 일반 리뷰 모드로 전환 시 현재 페이지 텍스트 저장 + it.copy(isGeneralReview = true, savedPageText = it.pageText, pageText = "") } else { - it.copy(isGeneralReview = false, pageText = it.savedPageText) + // 일반 리뷰 모드 해제 시 저장된 페이지 텍스트 복원 + it.copy(isGeneralReview = false, pageText = it.savedPageText, savedPageText = "") } }Likely an incorrect or invalid review comment.
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (1)
88-89: 프로그레스 관련 상태 관리 개선 필요
progressJob이MutableState로 관리되고 있지만, Compose의 재구성 시 상태가 손실될 수 있습니다.rememberCoroutineScope와 함께 사용하는 것이 더 안전합니다.- var progressJob by remember { mutableStateOf<Job?>(null) } + val progressJob = remember { mutableStateOf<Job?>(null) }Likely an incorrect or invalid review comment.
| @Serializable | ||
| data class RoomsPostsLikesRequest( | ||
| val type: Boolean, | ||
| val roomPostType: String, | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
불리언 type의 의미를 드러내는 이름으로 변경 (isLike) + @SerialName로 API 호환
좋아요/해제 의미가 분명하도록 isLike 등으로 변경하고 JSON 키는 유지하세요.
다음 변경을 제안합니다:
package com.texthip.thip.data.model.rooms.request
import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerialName
@Serializable
data class RoomsPostsLikesRequest(
- val type: Boolean,
+ @SerialName("type")
+ val isLike: Boolean,
val roomPostType: String,
)📝 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.
| @Serializable | |
| data class RoomsPostsLikesRequest( | |
| val type: Boolean, | |
| val roomPostType: String, | |
| ) | |
| package com.texthip.thip.data.model.rooms.request | |
| import kotlinx.serialization.Serializable | |
| import kotlinx.serialization.SerialName | |
| @Serializable | |
| data class RoomsPostsLikesRequest( | |
| @SerialName("type") | |
| val isLike: Boolean, | |
| val roomPostType: String, | |
| ) |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPostsLikesRequest.kt
around lines 5-9, rename the Boolean property `type` to a meaningfully named
`isLike` and keep JSON compatibility by annotating it with @SerialName("type");
update the data class property declaration accordingly (retain @Serializable)
and then search/replace any usages or constructor calls to use `isLike` (or map
incoming JSON) so compilation and API contract remain correct.
| val type: Boolean, | ||
| val roomPostType: String, |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
roomPostType 유효값 검증 위치 확인
문자열을 유지한다면 어디선가 유효값 검증이 필요합니다(예: 레포지토리에서 화이트리스트). 누락 시 서버 4xx/5xx 유발 가능.
레포지토리에서 검증/정규화가 있는지 점검해 주세요:
🏁 Script executed:
#!/bin/bash
set -euxo pipefail
rg -n --glob '!**/build/**' 'roomPostType|RoomsPostsLikesRequest' app | sed -n '1,200p'Length of output: 1210
roomPostType 값 검증 추가 필요
현재 RoomsPostsLikesRequest 에 전달되는 roomPostType 은 String 형태로만 정의되어 있으며, 호출 지점이나 repository/service 계층 어디에서도 허용 가능한 값(화이트리스트 등)을 검증하는 로직이 없습니다. 검증이 빠질 경우 잘못된 값 전송 시 서버에서 4xx/5xx 에러가 발생할 수 있습니다.
검토가 필요한 주요 위치:
- app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt:126 (
RoomsPostsLikesRequest생성부) - app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteViewModel.kt:174 (
postType전달부)
repository 또는 service 계층에서 enum 클래스, 화이트리스트 체크 등으로 유효값 검증/정규화를 추가해 주세요.
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPostsLikesRequest.kt
around lines 7-8, roomPostType is an unchecked String which can cause 4xx/5xx
errors downstream; add validation/normalization in the repository or service
when constructing RoomsPostsLikesRequest (see RoomsRepository.kt line ~126 and
GroupNoteViewModel.kt line ~174): define an enum or whitelist of allowed post
types, validate the incoming value against it, map/normalize valid values, and
reject/throw a clear IllegalArgumentException (or return a controlled error) for
invalid values so only validated values are passed into RoomsPostsLikesRequest.
| @Serializable | ||
| data class RoomsRecordRequest( | ||
| val page: Int, | ||
| val isOverview: Boolean = false, | ||
| val content: String, | ||
| ) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
직렬화 키 네이밍과 선택적 필드 포함 여부 검증 필요.
- 백엔드가 snake_case를 요구한다면 @SerialName("is_overview") 등 명시가 필요합니다.
- isOverview=true일 때 page를 보내지 말아야 한다면(또는 반대) 현재 모델(page: Int)이 항상 포함되는 형태라 계약 위반 가능성이 있습니다. 필요 시 page를 Int?로 두고(또는 @EncodeDefault Mode.NEVER) Json 설정(explicitNulls=false)과 함께 조건부 포함을 보장해야 합니다.
다음 스크립트로 기존 코드베이스의 직렬화 관례와 Json 설정을 확인해 주세요:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1) @SerialName 사용 현황:"
rg -n '@SerialName\(' -A2 app/src/main/java
echo
echo "2) Request 모델들 직렬화 어노테이션 요약:"
fd -t f -e kt 'Request.kt' app/src/main/java | while read -r f; do
echo "=== $f ==="
rg -n '@Serializable|@SerialName|data class' "$f"
done
echo
echo "3) kotlinx.serialization Json 설정(explicitNulls 등) 확인:"
rg -n 'Json\s*\{' -A5 -U
rg -n 'explicitNulls\s*=\s*(true|false)' -ULength of output: 17285
직렬화 키 네이밍 일관성 및 선택적 필드 포함 처리
-
이 프로젝트의 JSON 키는 snake_case가 아닌 camelCase(
roomId,isPublic등)로 사용되며, 대부분의 Request 모델에서
@SerialName("camelCaseKey")를 명시하고 있습니다.
→RoomsRecordRequest의 모든 프로퍼티에도 아래처럼@SerialName을 추가해 주세요.@Serializable data class RoomsRecordRequest( @SerialName("page") val page: Int, @SerialName("isOverview") val isOverview: Boolean = false, @SerialName("content") val content: String, )
-
NetworkModule설정(encodeDefaults = true,explicitNulls = false) 상으로는- 기본값(default)인
isOverview = false도 항상 직렬화됩니다. - null인 프로퍼티만 JSON에서 생략되므로,
isOverview == true일 때page를 보내지 않으려면처럼@SerialName("page") val page: Int? = null
page를 nullable로 선언하고 기본값을null로 두면 됩니다.
- 기본값(default)인
-
최종적으로 Backend 계약에 따라
- camelCase가 아닌 snake_case를 요구하는지
isOverview에 따라page의 선택적 포함 여부
등을 재확인해 주세요.
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsRecordRequest.kt
around lines 5-10, add explicit @SerialName annotations for every property to
match the project’s camelCase JSON naming (e.g., @SerialName("page"),
@SerialName("isOverview"), @SerialName("content")), and if the API expects
omitting page when isOverview == true change page to a nullable property with a
default of null (val page: Int? = null) so it won’t be serialized unless
present; finally, verify with the backend whether keys should be camelCase or
snake_case and whether page must be optional based on isOverview.
| @Serializable | ||
| data class RoomsVoteRequest( | ||
| val voteItemId: Int, | ||
| val type: Boolean | ||
| ) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
호출부 빌드 안정성 확인 (명명 인자 사용 여부)
프로퍼티명을 변경하면 RoomsVoteRequest(type = ...)처럼 명명 인자를 쓰는 호출부가 깨질 수 있습니다. 포지셔널 인자만 쓰는 경우는 안전합니다.
호출부를 점검해 주세요:
🏁 Script executed:
#!/bin/bash
set -euxo pipefail
# RoomsVoteRequest 생성 시 명명 인자 사용 여부 확인
rg -n --glob '!**/build/**' 'RoomsVoteRequest\(' -A2 app | sed -n '1,200p'Length of output: 751
명명 인자 사용으로 인한 빌드 불안정 확인 필요
RoomsVoteRequest 호출부에서 명명 인자(voteItemId = …, type = …)를 사용 중이므로, 데이터 클래스 프로퍼티명을 변경하면 해당 호출부가 깨집니다. 호출부를 아래 위치에서 확인하고, 프로퍼티명 변경 시 함께 수정하거나 포지셔널 인자로 전환해 주세요.
· app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt
– 라인 106–108:
request = RoomsVoteRequest(
voteItemId = voteItemId,
type = type
)🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsVoteRequest.kt
around lines 5–9, the data class uses property names referenced via named
arguments at call sites (see RoomsRepository.kt lines 106–108), so renaming
properties will break builds; to fix, either keep the current property names
unchanged, or add stable serial/JSON names (via @SerialName) while keeping the
Kotlin property names, or update all call sites to use positional arguments
instead of named args so renames won’t break callers. Ensure whichever approach
you choose is applied consistently across the repository and update
RoomsRepository.kt if you change to positional args or change property names.
🛠️ Refactor suggestion
의미 모호한 불리언 필드명 type → 도메인 의미 반영 (isSelected) + @SerialName로 API 호환 유지
불리언 type은 의미가 모호합니다. 읽기성과 유지보수를 위해 의미가 드러나는 이름으로 바꾸고, 서버 JSON 키는 @SerialName("type")으로 유지하세요.
다음 변경을 제안합니다:
package com.texthip.thip.data.model.rooms.request
import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerialName
@Serializable
data class RoomsVoteRequest(
val voteItemId: Int,
- val type: Boolean
+ @SerialName("type")
+ val isSelected: Boolean,
)호출부에서 명명 인자를 사용하고 있다면 함께 수정이 필요합니다.
📝 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.
| @Serializable | |
| data class RoomsVoteRequest( | |
| val voteItemId: Int, | |
| val type: Boolean | |
| ) | |
| package com.texthip.thip.data.model.rooms.request | |
| import kotlinx.serialization.Serializable | |
| import kotlinx.serialization.SerialName | |
| @Serializable | |
| data class RoomsVoteRequest( | |
| val voteItemId: Int, | |
| @SerialName("type") | |
| val isSelected: Boolean, | |
| ) |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsVoteRequest.kt
around lines 5 to 9, rename the ambiguous boolean property `type` to a
descriptive name such as `isSelected` and annotate it with @SerialName("type")
to preserve JSON compatibility; update the data class property name, add the
@SerialName import/annotation, and search & update all call sites that use named
arguments to use the new property name (or keep positional args unchanged) so
compilation and API behavior remain correct.
| @Serializable | ||
| data class RoomsVoteResponse( | ||
| val voteItemId: Int, | ||
| val roomId: Int, | ||
| val type: Boolean, | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
응답 불리언 필드 type의 의미 명확화 (isSelected) + @SerialName 유지
요청/응답 모델 모두 동일한 도메인 용어를 사용하면 이해가 쉬워집니다. 요청과 동일하게 isSelected로 정렬하고 서버 JSON 키는 유지하세요.
다음 변경을 제안합니다:
package com.texthip.thip.data.model.rooms.response
import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerialName
@Serializable
data class RoomsVoteResponse(
val voteItemId: Int,
val roomId: Int,
- val type: Boolean,
+ @SerialName("type")
+ val isSelected: Boolean,
)🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsVoteResponse.kt
around lines 5 to 10, the boolean property name `type` is unclear; rename the
Kotlin property to `isSelected` to match request models and domain terminology,
and annotate it with @SerialName("type") so the JSON key remains `type` for
compatibility; update any references/usages to the new property name and ensure
kotlinx.serialization.SerialName is imported.
| if (pageNumber == null || !currentState.isFormFilled) { | ||
| _uiState.update { it.copy(error = "페이지 번호를 정확히 입력해주세요.") } | ||
| return | ||
| } |
There was a problem hiding this comment.
에러 메시지가 정확하지 않음
!currentState.isFormFilled 조건에서도 "페이지 번호를 정확히 입력해주세요." 메시지를 표시하고 있습니다. 폼이 완전히 채워지지 않았을 때는 다른 메시지가 필요합니다.
- if (pageNumber == null || !currentState.isFormFilled) {
- _uiState.update { it.copy(error = "페이지 번호를 정확히 입력해주세요.") }
+ if (!currentState.isFormFilled) {
+ _uiState.update { it.copy(error = "모든 필드를 입력해주세요.") }
+ return
+ }
+ if (pageNumber == null) {
+ _uiState.update { it.copy(error = "페이지 번호를 정확히 입력해주세요.") }
return
}📝 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.
| if (pageNumber == null || !currentState.isFormFilled) { | |
| _uiState.update { it.copy(error = "페이지 번호를 정확히 입력해주세요.") } | |
| return | |
| } | |
| if (!currentState.isFormFilled) { | |
| _uiState.update { it.copy(error = "모든 필드를 입력해주세요.") } | |
| return | |
| } | |
| if (pageNumber == null) { | |
| _uiState.update { it.copy(error = "페이지 번호를 정확히 입력해주세요.") } | |
| return | |
| } |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteCreateViewModel.kt
around lines 92 to 95, the code shows the same error text when pageNumber is
null or when the form is incomplete; split the condition into two branches so
each case sets a specific message: if pageNumber == null set the error to "페이지
번호를 정확히 입력해주세요.", else if !currentState.isFormFilled set a different message
such as "모든 필드를 입력해주세요."; update _uiState.update in each branch and keep the
early return after setting the error.
| viewModelScope.launch { | ||
| val postsJob = async { loadPosts(isRefresh = true) } | ||
| val bookPageJob = async { loadBookPageInfo() } | ||
| awaitAll(postsJob, bookPageJob) | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
동시 작업 처리 시 에러 처리 개선 필요
async와 awaitAll을 사용하여 동시에 작업을 실행하고 있지만, 한 작업이 실패해도 다른 작업은 계속 진행됩니다. 에러 처리를 개선해야 합니다.
viewModelScope.launch {
- val postsJob = async { loadPosts(isRefresh = true) }
- val bookPageJob = async { loadBookPageInfo() }
- awaitAll(postsJob, bookPageJob)
+ try {
+ val postsJob = async { loadPosts(isRefresh = true) }
+ val bookPageJob = async { loadBookPageInfo() }
+ awaitAll(postsJob, bookPageJob)
+ } catch (e: Exception) {
+ _uiState.update { it.copy(error = e.message ?: "오류가 발생했습니다.") }
+ }
}📝 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.
| viewModelScope.launch { | |
| val postsJob = async { loadPosts(isRefresh = true) } | |
| val bookPageJob = async { loadBookPageInfo() } | |
| awaitAll(postsJob, bookPageJob) | |
| } | |
| } | |
| viewModelScope.launch { | |
| try { | |
| val postsJob = async { loadPosts(isRefresh = true) } | |
| val bookPageJob = async { loadBookPageInfo() } | |
| awaitAll(postsJob, bookPageJob) | |
| } catch (e: Exception) { | |
| _uiState.update { it.copy(error = e.message ?: "오류가 발생했습니다.") } | |
| } | |
| } |
| CardNote( | ||
| currentPage = currentPage, | ||
| percentage = userPercentage | ||
| ) {} | ||
| ) { | ||
| onNavigateToNote() | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
CardNote 트레일링 람다의 의미 확인 및 onClick 명시 전달 권장
해당 람다가 CardNote의 onClick 파라미터를 받는 트레일링 람다가 맞다면 현재 구현은 OK입니다. 만약 슬롯(Composable content)이라면 컴포지션 시점에 네비게이션이 즉시 발생하는 버그가 됩니다. 명시적 인자 전달을 권장합니다.
아래와 같이 명시 인자로 전달하면 오해의 소지가 줄어듭니다:
- CardNote(
- currentPage = currentPage,
- percentage = userPercentage
- ) {
- onNavigateToNote()
- }
+ CardNote(
+ currentPage = currentPage,
+ percentage = userPercentage,
+ onClick = onNavigateToNote
+ )CardNote 시그니처를 확인하려면:
🏁 Script executed:
#!/bin/bash
# CardNote 시그니처 확인 (마지막 파라미터가 onClick인지 확인)
rg -n "fun\\s+CardNote\\s*\\(" -A 20Length of output: 2166
CardNote onClick 람다 확인 및 명시적 전달 권장
CardNote의 시그니처를 확인한 결과, 마지막 파라미터가 onClick으로 정의되어 있어 현재 trailing lambda 방식도 onClick으로 정상 동작합니다. 다만, onClick을 명시적으로 전달하면 가독성이 높아지고 오해의 소지를 줄일 수 있습니다.
- 대상 파일:
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomBody.kt (약 39–44행)
수정 제안:
- CardNote(
- currentPage = currentPage,
- percentage = userPercentage
- ) {
- onNavigateToNote()
- }
+ CardNote(
+ currentPage = currentPage,
+ percentage = userPercentage,
+ onClick = onNavigateToNote
+ )📝 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.
| CardNote( | |
| currentPage = currentPage, | |
| percentage = userPercentage | |
| ) {} | |
| ) { | |
| onNavigateToNote() | |
| } | |
| CardNote( | |
| currentPage = currentPage, | |
| percentage = userPercentage, | |
| onClick = onNavigateToNote | |
| ) |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomBody.kt
around lines 39–44, the CardNote call uses a trailing lambda which relies on the
last parameter being onClick; update the invocation to explicitly pass the
onClick parameter (e.g. onClick = { onNavigateToNote() }) while keeping
currentPage and percentage as before so the intent is clear and avoids
ambiguity.
| // roomId = roomId, | ||
| roomId = 1, | ||
| resultTabIndex = result, | ||
| initialPage = page, | ||
| initialIsOverview = isOverview, | ||
| onResultConsumed = { |
There was a problem hiding this comment.
roomId 하드코딩(=1) 제거: 실제 route.roomId 사용으로 교체 필요
테스트용으로 보이는 roomId=1 하드코딩은 릴리즈 전에 반드시 정리해야 합니다. 현재 상태로는 잘못된 방으로 화면이 구성될 수 있습니다.
적용 diff:
GroupNoteScreen(
-// roomId = roomId,
- roomId = 1,
+ roomId = roomId,
resultTabIndex = result,
initialPage = page,
initialIsOverview = isOverview,- GroupNoteCreateScreen(
- roomId = 1,
+ GroupNoteCreateScreen(
+ roomId = roomId,
recentPage = route.recentBookPage,
totalPage = route.totalBookPage,
isOverviewPossible = route.isOverviewPossible,- GroupVoteCreateScreen(
-// roomId = roomId,
- roomId = 1,
+ GroupVoteCreateScreen(
+ roomId = roomId,
recentPage = route.recentPage,
totalPage = route.totalPage,
isOverviewPossible = route.isOverviewPossible,Also applies to: 262-266, 283-286
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt
around lines 227-232 (also update same pattern at 262-266 and 283-286), remove
the test hardcoded "roomId = 1" and wire the real route roomId instead (e.g.,
roomId = route.roomId or the local roomId variable passed into the composable).
Ensure you use the correct type (convert from String to Int if route.roomId is a
string) and remove the temporary hardcode so the screen receives the actual room
id from the navigation route.
| @Serializable | ||
| data class NoteCreate( | ||
| val roomId: Int, | ||
| val recentBookPage: Int, | ||
| val totalBookPage: Int, | ||
| val isOverviewPossible: Boolean | ||
| ) | ||
|
|
||
| @Serializable | ||
| data class VoteCreate( | ||
| val roomId: Int, | ||
| val recentPage: Int, | ||
| val totalPage: Int, | ||
| val isOverviewPossible: Boolean | ||
| ) |
There was a problem hiding this comment.
sealed subclass 지정 누락으로 컴파일 에러 발생
NoteCreate, VoteCreate가 GroupRoutes를 상속하지 않아 sealed 클래스 하위 타입으로 인식되지 않습니다. 네비게이션 매칭/when 분기 시 컴파일 에러가 발생합니다.
다음 수정이 필요합니다:
@Serializable
data class NoteCreate(
val roomId: Int,
val recentBookPage: Int,
val totalBookPage: Int,
val isOverviewPossible: Boolean
- )
+ ) : GroupRoutes()
@Serializable
data class VoteCreate(
val roomId: Int,
val recentPage: Int,
val totalPage: Int,
val isOverviewPossible: Boolean
- )
+ ) : GroupRoutes()📝 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.
| @Serializable | |
| data class NoteCreate( | |
| val roomId: Int, | |
| val recentBookPage: Int, | |
| val totalBookPage: Int, | |
| val isOverviewPossible: Boolean | |
| ) | |
| @Serializable | |
| data class VoteCreate( | |
| val roomId: Int, | |
| val recentPage: Int, | |
| val totalPage: Int, | |
| val isOverviewPossible: Boolean | |
| ) | |
| @Serializable | |
| data class NoteCreate( | |
| val roomId: Int, | |
| val recentBookPage: Int, | |
| val totalBookPage: Int, | |
| val isOverviewPossible: Boolean | |
| ) : GroupRoutes() | |
| @Serializable | |
| data class VoteCreate( | |
| val roomId: Int, | |
| val recentPage: Int, | |
| val totalPage: Int, | |
| val isOverviewPossible: Boolean | |
| ) : GroupRoutes() |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt around
lines 32 to 46, the data classes NoteCreate and VoteCreate are missing the
sealed-subclass declaration so they are not recognized as subclasses of
GroupRoutes; update each declaration to extend GroupRoutes (e.g., add ":
GroupRoutes" to NoteCreate and VoteCreate) so they become valid sealed
subclasses and the when/matching compilation errors are resolved.
There was a problem hiding this comment.
Actionable comments posted: 16
🔭 Outside diff range comments (5)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)
82-82: 로컬 변수 명명/섀도잉 수정: CommentList → commentListState로컬 변수명이 PascalCase이고, 파라미터(commentList)와 대소문자만 다른 이름이라 가독성과 유지보수성이 크게 떨어집니다. Kotlin 컨벤션(lowerCamelCase)과 섀도잉 방지 차원에서 이름을 바꾸고, 사용처도 함께 변경하세요.
권장 변경(핵심 라인 및 대표 사용처):
- val CommentList = commentList ?: remember { mutableStateListOf<FeedCommentItem>() } + val commentListState = commentList ?: remember { mutableStateListOf<FeedCommentItem>() }사용처 갱신(대표):
- if (CommentList.isEmpty()) { + if (commentListState.isEmpty()) { - CommentList.forEachIndexed { index, commentItem -> + commentListState.forEachIndexed { index, commentItem -> - if (index == CommentList.lastIndex) { + if (index == commentListState.lastIndex) { - CommentList.add( + commentListState.add( - commentId = CommentList.size + 1, + commentId = commentListState.size + 1, - val parentIndex = - CommentList.indexOfFirst { it.nickName == replyTargetNickname } + val parentIndex = + commentListState.indexOfFirst { it.nickName == replyTargetNickname } - val parentComment = CommentList[parentIndex] + val parentComment = commentListState[parentIndex] - CommentList[parentIndex] = + commentListState[parentIndex] =참고: 위 외의 동일한 사용처도 모두 변경 필요합니다.
app/src/main/java/com/texthip/thip/ui/group/note/component/TextCommentCard.kt (1)
45-53: 프로필 이미지 placeholder 하드코딩 제거 필요현재
ProfileBar에 고정 URL을 넘기고 있어 실제 사용자 이미지가 표시되지 않습니다.PostList.profileImageUrl을 사용하도록 교체해 주세요.- profileImage = "https://example.com/image1.jpg", + profileImage = data.profileImageUrl,app/src/main/java/com/texthip/thip/ui/group/note/component/VoteCommentCard.kt (1)
51-57: 프로필 이미지 placeholder 하드코딩 제거 필요실제 사용자 이미지를 표시하도록
profileImageUrl사용으로 교체해 주세요.- ProfileBar( - profileImage = "https://example.com/image1.jpg", + ProfileBar( + profileImage = data.profileImageUrl, topText = data.nickName, bottomText = data.page.toString() + stringResource(R.string.page), bottomTextColor = colors.Purple, showSubscriberInfo = false, hoursAgo = data.postDate )app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (2)
184-184: 하드코딩된 roomId=1 제거 — 실제 route 파라미터 사용해야 함현재
GroupRoomScreen에roomId = 1이 전달되어 잘못된 방으로 진입합니다. 라우트에서 파싱한 값을 그대로 전달해 주세요.- roomId = 1, + roomId = roomId,
204-205: 하드코딩된 roomId=1 제거 (GroupRoomMatesScreen)동일 문제입니다. 실제
roomId를 사용해 주세요.- roomId = 1, + roomId = roomId,
♻️ Duplicate comments (3)
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPostsLikesRequest.kt (1)
7-9: 후행 쉼표 스타일 일관성(관련 코멘트 참고)본 PR 내 유사 파일 간 후행 쉼표 사용이 섞여 있습니다. 한 가지 스타일로 통일해 주세요.
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsBookPageResponse.kt (1)
7-11: 후행 쉼표 스타일 일관성(관련 코멘트 참고)본 파일도 후행 쉼표가 포함되어 있어, 동일 패키지 내 스타일이 섞여 있습니다. 하나로 통일해 주세요.
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsRecordResponse.kt (1)
7-9: 후행 쉼표 스타일 일관성(관련 코멘트 참고)후행 쉼표 사용이 파일마다 달라 일관성이 깨져 있습니다. 팀 규칙에 맞춰 정리해 주세요.
🧹 Nitpick comments (36)
app/src/main/java/com/texthip/thip/ui/group/note/component/FilterHeaderSection.kt (3)
42-45: modifier 체인 우선순위 확인 제안현재는 전달받은 modifier 뒤에 fillMaxWidth/padding을 덧붙여 내부 제약이 호출자 제약보다 우선 적용됩니다. 호출자가 padding/size를 재정의 가능하게 하려면 then(modifier) 패턴을 고려해 보세요.
적용 예시:
- modifier = modifier - .fillMaxWidth() - .padding(horizontal = 20.dp), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .then(modifier),
- 현행 유지: 내부 제약(가로 꽉 채움/20dp padding)을 강제하고 싶을 때 적합
- 제안안: 호출자에게 재정의 여지를 주고 싶을 때 적합
80-83: 페이지 필터 적용 타이밍은 적절함. 다만 ‘닫기/초기화’ 경로에서도 동일 이벤트를 발생시키는 일관성 고려onFinishClick에서 onApplyPageFilter()를 먼저 호출하고 입력 UI를 닫는 순서는 OK입니다. 추가로, 필터 칩의 onCloseClick에서도 입력값 초기화와 적용 이벤트를 발생시키면 UX 일관성이 좋아집니다(필터 해제 즉시 목록이 갱신).
제품/디자인 의도에 따라 다를 수 있으니 의도 확인 후 적용해 주세요. 적용 시 onCloseClick 업데이트 예시는 아래와 같습니다(선택사항).
onCloseClick = { // 필터 초기화 + 즉시 적용 + 입력 UI 닫기 onFirstPageChange("") onLastPageChange("") onApplyPageFilter() isPageInputVisible = false }
96-105: Preview: totalEnabled 기본값(true) 명시는 불필요 — 호출 간결화 제안Preview에서 totalEnabled는 기본값이 true이므로 명시하지 않아도 됩니다. 불필요한 인자 제거로 가독성을 개선하세요.
- totalEnabled = true, onApplyPageFilter = {},app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (4)
223-356: LazyColumn 아이템 렌더링 개선: forEachIndexed + item 대신 itemsIndexed 사용 권장현재 LazyColumn 안에서
CommentList.forEachIndexed { ... item { ... } }패턴을 사용하고 있습니다. Compose에서는items/itemsIndexed로 리스트를 직접 바인딩하는 것이 성능/재활용/키 처리 측면에서 더 적합합니다.예시:
itemsIndexed(commentListState, key = { _, item -> item.commentId }) { index, commentItem -> // 기존 item 블록 내부 내용을 그대로 이동 }
- key를 제공하면 스크롤/재조합 시 안정성이 올라갑니다(commentId 사용).
- 마지막 아이템 간격 처리도 itemsIndexed 블록 내부에서 index 비교로 동일하게 처리 가능합니다.
88-88: 미사용 상태 제거: feed 변수
val feed = remember { mutableStateOf(feedItem) }는 이후에 사용되지 않습니다. 제거하여 경고를 없애고 불필요한 상태 보관을 피하세요.적용 diff:
- val feed = remember { mutableStateOf(feedItem) }
67-81: 미사용 파라미터 정리 제안(bookImage, feedType, onLikeClick)다음 파라미터들이 본문에서 사용되지 않습니다:
bookImage,feedType,onLikeClick. 유지 목적(향후 사용)이라면 주석/애너테이션으로 의도를 명확히 하거나, 당장 필요 없다면 삭제하세요.옵션:
- 실제 사용 추가
- 불필요 시 시그니처에서 제거
- 불가피하면
@Suppress("UnusedParameter")로 명시적 억제
388-407: 대댓글 parent 매칭 안정성 개선: 닉네임 기반 → ID 기반 권장현재 parent 매칭이
nickName문자열로 이루어집니다(Line 389). 동일 닉네임 사용자가 여럿일 경우 오매칭 위험이 큽니다.commentId등 고유 ID 기반으로 전환을 권장합니다.제안:
replyTo를Int?(parent commentId)로 변경onReplyClick콜백이 parent commentId를 전달하도록CommentItem컴포넌트/호출부 수정- parent 탐색은
indexOfFirst { it.commentId == replyTo }형태로 변경app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPostsLikesRequest.kt (2)
3-9: 요청 필드 명확화: Boolean 'type' → 'isLike'로 리네임(+ @SerialName 유지)'type'은 의미가 모호합니다. 도메인 의도를 드러내는 이름으로 바꾸되, 서버 스키마와의 호환을 위해 @SerialName으로 JSON 키를 유지하는 것을 권장합니다.
import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName @Serializable data class RoomsPostsLikesRequest( - val type: Boolean, - val roomPostType: String, + @SerialName("type") + val isLike: Boolean, + val roomPostType: String, )
6-9: roomPostType는 Enum으로 강타입화 권장허용값이 제한된 필드(예: RECORD/POST/VOTE 등)라면 String 대신 @serializable enum을 사용해 오타·유효값을 컴파일 타임에 보장하는 편이 안전합니다. 서버 규격 문자열과 일치하도록 @SerialName 값을 맞춰 주세요.
해당 파일 변경(참고용):
data class RoomsPostsLikesRequest( @SerialName("type") val isLike: Boolean, - val roomPostType: String, + val roomPostType: RoomPostType, )새 enum 예시(패키지/값은 실제 서버 규격에 맞춰 조정):
package com.texthip.thip.data.model.rooms.request import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable enum class RoomPostType { @SerialName("RECORD") RECORD, @SerialName("POST") POST, @SerialName("VOTE") VOTE, }app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsDeleteRecordResponse.kt (1)
6-8: 후행 쉼표 스타일 일관성 제안동일 패키지의 다른 응답(예: RoomsCreateVoteResponse)은 후행 쉼표가 없고, 본 파일은 존재합니다. ktlint/팀 컨벤션에 맞춰 통일해 주세요. 후행 쉼표를 제거하는 방향 예시는 아래와 같습니다.
@Serializable data class RoomsDeleteRecordResponse( - val roomId: Int, + val roomId: Int )app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPostsLikesResponse.kt (1)
6-9: 직렬화 키 고정(@SerialName) 및 Kotlin 버전 호환(마지막 쉼표) 확인
- @SerialName으로 JSON 키를 고정하면 서버 스키마 변경/리팩터링 시 안정성이 높아집니다.
- 현재 마지막 프로퍼티 뒤의 쉼표는 Kotlin 1.4+ 에서만 허용됩니다. 프로젝트 Kotlin 버전을 확인하시고 필요 시 제거해주세요.
제안 패치:
package com.texthip.thip.data.model.rooms.response -import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable @Serializable data class RoomsPostsLikesResponse( - val postId: Int, - val isLiked: Boolean, + @SerialName("postId") val postId: Int, + @SerialName("isLiked") val isLiked: Boolean )app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsCreateVoteRequest.kt (2)
5-11: 필드 네이밍 명확화(voteItemList → voteItems) + @SerialName으로 API 키 고정컬렉션 접미사(List) 표기는 중복 정보라 voteItems처럼 의미 중심 네이밍이 가독성이 좋습니다. 서버와의 키는 @SerialName으로 고정하는 것을 권장합니다.
- 서버가 body로 page/isOverview를 받는지(혹은 path/query인지) 스펙 확인 부탁드립니다.
- voteItems 최소 개수(일반적으로 2+)와 content 비어있지 않음 등의 도메인 검증은 호출 전 레이어에서 보장되면 좋습니다.
package com.texthip.thip.data.model.rooms.request -import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable @Serializable data class RoomsCreateVoteRequest( val page: Int, val isOverview: Boolean, val content: String, - val voteItemList: List<VoteItem> + @SerialName("voteItemList") val voteItems: List<VoteItem> )
13-16: VoteItem 직렬화 키 고정(@SerialName)서버 키 안정성을 위해 @SerialName 명시를 권장합니다. UI/도메인 계층에 동명 타입(VoteItem)이 존재한다면 패키지/네이밍 충돌도 사전 점검 바랍니다.
@Serializable data class VoteItem( - val itemName: String + @SerialName("itemName") val itemName: String )app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPostsRequestParams.kt (1)
6-14: 파라미터 유효값 제약 강화(열거형/타입세이프) 및 사용처 정합성 확인
- type, sort가 임의 문자열이면 잘못된 값이 서버로 전달될 수 있습니다. enum(예: RoomsPostsType { GROUP, PERSONAL }, RoomsPostsSort { LATEST, POPULAR, OLDEST })로 타입세이프를 높이는 것을 권장합니다. Retrofit 사용 시 enum은 name/SerialName으로 직렬화 가능합니다.
- isOverview, isPageFilter가 쿼리로 직렬화될 때 서버가 true/false를 기대하는지(Y/N, 0/1 아님)를 확인해주세요.
- Repository 기본값(예: sort="latest", isOverview=false 등)과 본 모델의 기본값(null)이 불일치하면 혼선이 생길 수 있습니다. 어느 쪽을 소스 오브 트루스로 삼을지 결정해 정렬해주세요.
참고 예시(별도 적용용):
@Serializable enum class RoomsPostsSort { @SerialName("latest") LATEST, @SerialName("popular") POPULAR, @SerialName("oldest") OLDEST }app/src/main/res/values/strings.xml (1)
235-235: 말줄임표 통일 제안(타이포그래피 니트픽)앱 전반에서 말줄임표를 단일 문자(…)로 통일하면 시각적으로 더 깔끔합니다. 현재는 세 점(...)을 사용 중입니다.
다음과 같이 수정 제안드립니다.
- <string name="posting_in_progress">기록을 게시 중입니다...</string> + <string name="posting_in_progress">기록을 게시 중입니다…</string>app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt (1)
67-70: SortType enum을 이용해 단일 소스 유지(표시 문자열 생성)하드코딩 대신 SortType의 displayNameRes를 사용하면 리소스/도메인 매핑이 한 곳에서 관리돼 변경에 강해집니다.
다음과 같이 정렬 옵션을 생성하는 로직을 교체하는 것을 제안합니다.
- val filterOptions = listOf( - stringResource(R.string.sort_like), - stringResource(R.string.sort_latest) - ) + val filterOptions = listOf(SortType.LIKE, SortType.LATEST) + .map { stringResource(it.displayNameRes) }이 변경을 적용하려면 해당 파일 상단에 아래 import가 필요합니다.
import com.texthip.thip.utils.type.SortTypeapp/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPostsResponse.kt (3)
7-13: 모델 네이밍 정리 제안: PostList → Post, VoteItems → VoteItem클래스 이름이 복수형/리스트를 연상시켜 혼동을 줍니다. 도메인 객체 단수형으로 정리하면 가독성과 일관성이 좋아집니다.
아래와 같이 리네이밍을 제안드립니다.
data class RoomsPostsResponse( - val postList: List<PostList>, + val postList: List<Post>, val roomId: Int, val isbn: String, val isOverviewEnabled: Boolean, val nextCursor: String?, val isLast: Boolean, ) @Serializable -data class PostList( +data class Post( val postId: Int, val postDate: String, val postType: String, val page: Int, val userId: Int, - val nickName: String, + val nickName: String, val profileImageUrl: String?, val content: String, val likeCount: Int, val commentCount: Int, val isLiked: Boolean, val isWriter: Boolean, val isLocked: Boolean, - val voteItems: List<VoteItems>, + val voteItems: List<VoteItem>, ) @Serializable -data class VoteItems( +data class VoteItem( val voteItemId: Int, val itemName: String, val percentage: Int, val isVoted: Boolean, )참고: 이 변경은 해당 타입을 참조하는 서비스/레포지토리/뷰모델/UI 코드에도 반영되어야 합니다. 필요하시면 전역 변경에 대한 패치도 도와드릴게요.
Also applies to: 16-31, 33-39
3-3: Kotlin 네이밍 컨벤션 유지 + 직렬화 키 보존(@SerialName 적용)API 키가 nickName이더라도, Kotlin 필드명은 nickname으로 가져가고 @SerialName으로 매핑하는 편이 일관적입니다.
아래와 같이 수정 제안드립니다.
import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName @@ - val nickName: String, + @SerialName("nickName") val nickname: String,Also applies to: 22-22
18-19: 날짜 타입을 문자열에서 강타입으로 전환 고려(kotlinx-datetime)postDate를 ISO-8601 문자열로 받는다면 kotlinx-datetime의 Instant/LocalDateTime + 커스텀 serializer를 사용하면 파싱/정렬/표시에서 안정적입니다. 추후 정렬/필터에 날짜 연산이 들어간다면 검토를 권장합니다.
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsUsersResponse.kt (1)
11-17: 타입/命名 정합성 점검 제안(UserList 명칭)내부 클래스를 UserList라고 명명하면 혼동 여지가 큽니다(리스트 컨테이너 같이 보임). RoomUser 같은 실체 명칭으로의 리네이밍을 고려해 주세요. API 영향이 없다면 IDE 리팩터로 안전하게 변경 가능합니다.
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsRecordRequest.kt (1)
5-10: 요청 페이로드 키(server contract) 확인 필요 및 입력 검증 고려
- page, isOverview, content 키가 백엔드 스키마와 정확히 일치하는지 확인이 필요합니다. 만약 서버 필드명이 다르다면 @SerialName으로 명시해 주세요.
- content는 공백 허용 여부, page는 음수 허용 여부를 사전에 검증하는 편이 안전합니다(레포지토리 레벨에서 require/trim 권장).
필요 시 적용 예:
+import kotlinx.serialization.SerialName @Serializable data class RoomsRecordRequest( - val page: Int, - val isOverview: Boolean = false, - val content: String, + @SerialName("page") val page: Int, + @SerialName("isOverview") val isOverview: Boolean = false, + @SerialName("content") val content: String, )레포지토리에서의 간단한 입력 검증(파일 외 참고용):
require(page >= 0) { "page must be >= 0" } val sanitized = content.trim() require(sanitized.isNotEmpty()) { "content must not be blank" }app/src/main/java/com/texthip/thip/ui/group/note/component/TextCommentCard.kt (2)
25-27: 공개 API에 String postType 전달 → 타입 안정성 보강 제안
onLikeClick: (postId: Int, postType: String)는 런타임 오타에 취약합니다.postType을 enum/sealed class로 승격하거나, 최소한 허용 값(예: "group", "room", …)을 상수로 중앙화해 주세요. 전체 코드베이스에서 같은 타입을 일관되게 사용하면 서버/클라이언트 불일치로 인한 버그를 줄일 수 있습니다.
49-49: i18n: 문자열 이어붙이기 대신 포맷 리소스 사용 권장
"${data.page}"+stringResource(R.string.page)는 현지화/복수형 처리에 취약합니다.stringResource(R.string.page_format, data.page)형태의 포맷 리소스를 사용해 주세요. 리소스가 없다면 다음과 같이 추가를 고려해 주세요:page_format= "%d페이지".app/src/main/java/com/texthip/thip/ui/common/buttons/GroupVoteButton.kt (2)
29-38: 데이터 타입 명명 혼동: VoteItems → 단수 형태 권장
VoteItems가 개별 항목 타입 이름으로 보입니다. 관례상 단수(VoteItem)가 가독성이 높습니다. 모델 정의 파일에서 타입명을 단수형으로 변경하는 리팩터를 고려해 주세요. 기존 사용처가 많다면 타입 별칭을 통해 점진적 이전도 가능합니다.
37-41: 잠금/상태 전달을 위한 enabled 파라미터 추가 제안상위(예: 카드 컴포넌트)에서 잠금/진행 상태를 제어하려면 버튼 자체에
enabled를 노출하는 것이 재사용성에 유리합니다. 기본값을 true로 두면 기존 호출부 변경이 필요 없습니다.fun GroupVoteButton( modifier: Modifier = Modifier, voteItems: List<VoteItems>, selectedIndex: Int?, // 선택한 인덱스 hasVoted: Boolean = false, // 투표 여부 - onOptionSelected: (Int?) -> Unit + enabled: Boolean = true, + onOptionSelected: (Int?) -> Unit ) { ... - Box( + Box( modifier = Modifier .fillMaxWidth() .height(44.dp) .clip(RoundedCornerShape(12.dp)) - .background(color = backgroundColor) - .clickable { + .background(color = backgroundColor) + .clickable(enabled = enabled) { if (isSelected) { onOptionSelected(null) } else { onOptionSelected(index) } } ) {Also applies to: 70-83
app/src/main/java/com/texthip/thip/ui/group/note/component/VoteCommentCard.kt (2)
33-35: 재투표/취소 정책 확인 필요 (현재 로직은 재투표 허용)
hasVoted = selectedIndex != null상태에서도 다른 항목을 누르면onVote(..., true)가 호출되어 재투표가 허용됩니다. 제품/서버 정책에 따라 다음을 확인해 주세요:
- 재투표 금지: 선택된 항목이 이미 있을 때 다른 항목 클릭을 무시
- 재투표 허용: 이전 항목 취소 후 새 항목 투표(순차 호출) 또는 서버가 덮어쓰기 허용
예시 가드(재투표 금지):
- onOptionSelected = { index -> - if (!isLocked) { - if (index == null) { + onOptionSelected = { index -> + if (!isLocked) { + if (index == null) { selectedIndex?.let { val votedItemId = data.voteItems[it].voteItemId onVote(data.postId, votedItemId, false) // type: false (취소) } - } else { + } else if (!hasVoted || index == selectedIndex) { val votedItemId = data.voteItems[index].voteItemId onVote(data.postId, votedItemId, true) // type: true (투표) } } }또는 재투표 허용(명시적 취소 후 투표):
if (!isLocked) { if (index == null) { selectedIndex?.let { onVote(data.postId, data.voteItems[it].voteItemId, false) } } else { selectedIndex?.let { onVote(data.postId, data.voteItems[it].voteItemId, false) } onVote(data.postId, data.voteItems[index].voteItemId, true) } }Also applies to: 70-81
53-54: i18n: 페이지 표기 포맷 리소스 권장텍스트 결합 대신 포맷 리소스(
stringResource(R.string.page_format, data.page))를 사용하면 현지화/복수형 처리에 안전합니다.app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (1)
221-234: savedStateHandle 키 하드코딩 최소화 제안
"selected_tab_index"문자열 키는 오타/분산 위험이 있습니다. 상수로 중앙화해 사용하면 유지보수에 유리합니다.예: 파일 상단 companion object 등
private const val KEY_SELECTED_TAB_INDEX = "selected_tab_index"사용처:
val result = backStackEntry.savedStateHandle.get<Int>(KEY_SELECTED_TAB_INDEX) ... backStackEntry.savedStateHandle.remove<Int>(KEY_SELECTED_TAB_INDEX) ... navController.previousBackStackEntry?.savedStateHandle?.set(KEY_SELECTED_TAB_INDEX, 1)app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt (1)
125-126: roomPostType 파라미터 검증 필요
roomPostType: String파라미터가 어떤 값들을 받을 수 있는지 명확하지 않습니다. enum이나 sealed class로 타입 안전성을 보장하는 것이 좋습니다.// 별도 파일에 enum 정의 enum class RoomPostType(val value: String) { RECORD("record"), VOTE("vote"), COMMENT("comment"); companion object { fun fromValue(value: String): RoomPostType? = values().find { it.value == value } } }그리고 메서드 시그니처 변경:
suspend fun postRoomsPostsLikes( postId: Int, type: Boolean, - roomPostType: String + roomPostType: RoomPostType ) = runCatching { roomsService.postRoomsPostsLikes( postId = postId, request = RoomsPostsLikesRequest( type = type, - roomPostType = roomPostType + roomPostType = roomPostType.value ) ).handleBaseResponse().getOrThrow() }app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt (1)
181-187: 투표 네비게이션 로직 개선 가능현재
onVoteClick람다에서 조건부 로직을 처리하고 있는데, 이를 ViewModel이나 별도의 mapper 함수로 분리하면 가독성이 향상될 수 있습니다.GroupRoomBody( bookTitle = roomDetails.bookTitle, authorName = roomDetails.authorName, currentPage = roomDetails.currentPage, userPercentage = roomDetails.userPercentage, currentVotes = roomDetails.currentVotes, // 일반 노트 카드 클릭 시 필터 없이 이동 onNavigateToNote = { onNavigateToNote(null, null) }, // 투표 카드 클릭 시 필터 값과 함께 이동 - onVoteClick = { vote: CurrentVote -> - if (vote.isOverview) { - onNavigateToNote(null, true) - } else { - onNavigateToNote(vote.page, false) - } - } + onVoteClick = { vote: CurrentVote -> + val (page, isOverview) = when { + vote.isOverview -> null to true + else -> vote.page to false + } + onNavigateToNote(page, isOverview) + } )app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt (1)
116-117: Null 체크 후 강제 언래핑 사용
iconCoordinates.value가 null이 아님을 확인했지만!!연산자를 사용하는 것은 위험할 수 있습니다. 더 안전한 방법을 사용하는 것이 좋습니다.- val yOffsetDp = with(density) { - iconCoordinates.value!!.positionInRoot().y.toDp() + 32.dp - } + val yOffsetDp = iconCoordinates.value?.let { coordinates -> + with(density) { + coordinates.positionInRoot().y.toDp() + 32.dp + } + } ?: 0.dpapp/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteCreateViewModel.kt (1)
13-26: UI 상태 클래스의 공개 필드 캡슐화 고려
GroupNoteCreateUiState의 모든 필드가 공개되어 있습니다.isFormFilled처럼 파생된 속성은 괜찮지만, 기본 필드들은internal또는private으로 설정하고 ViewModel을 통해서만 수정 가능하도록 하는 것이 좋습니다.app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteViewModel.kt (1)
211-273: loadPosts 함수의 복잡도가 높음이 함수가 너무 많은 책임을 가지고 있습니다. 파라미터 생성, 상태 업데이트, API 호출 등을 분리하여 테스트 가능성과 가독성을 향상시킬 수 있습니다.
+private fun buildRequestParams(state: GroupNoteUiState, cursor: String?): RoomsPostsRequestParams { + val type = if (state.selectedTabIndex == 0) "group" else "mine" + return if (type == "mine") { + RoomsPostsRequestParams(type = type, cursor = cursor) + } else { + RoomsPostsRequestParams( + type = type, + sort = state.selectedSort.apiKey, + pageStart = state.pageStart.toIntOrNull(), + pageEnd = state.pageEnd.toIntOrNull(), + isOverview = state.isOverview, + isPageFilter = state.isPageFilter, + cursor = cursor + ) + } +} private fun loadPosts(isRefresh: Boolean = false) { val currentState = _uiState.value if (currentState.isLoading || currentState.isLoadingMore || (currentState.isLastPage && !isRefresh)) return viewModelScope.launch { _uiState.update { if (isRefresh) it.copy( isLoading = true, posts = emptyList(), error = null, isLastPage = false ) else it.copy(isLoadingMore = true, error = null) } val cursor = if (isRefresh) null else nextCursor - val type = if (currentState.selectedTabIndex == 0) "group" else "mine" - - val params = if (type == "mine") { - // "mine" 탭일 경우 필수 파라미터만 채워 넣음 - RoomsPostsRequestParams(type = type, cursor = cursor) - } else { - // "group" 탭일 경우 모든 필터 파라미터 포함 - RoomsPostsRequestParams( - type = type, - sort = currentState.selectedSort.apiKey, - pageStart = currentState.pageStart.toIntOrNull(), - pageEnd = currentState.pageEnd.toIntOrNull(), - isOverview = currentState.isOverview, - isPageFilter = currentState.isPageFilter, - cursor = cursor - ) - } + val params = buildRequestParams(currentState, cursor) roomsRepository.getRoomsPosts( roomId = roomId, type = params.type,app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (3)
87-119: 프로그레스 바 애니메이션 로직이 복잡함프로그레스 바 관련 상태와 로직이 복잡하게 얽혀있습니다. 별도의 커스텀 훅이나 컴포저블로 분리하면 재사용성과 테스트 가능성이 향상됩니다.
프로그레스 바 로직을 별도의
useProgressBar커스텀 훅으로 분리하는 것을 제안합니다. 필요하시면 리팩토링 코드를 생성해드릴까요?
462-476: 선택된 포스트 null 체크 중복Line 462에서 이미
selectedPostForComment != null체크를 하고 있으므로, Line 479의!!사용은 안전하지만 불필요합니다.-if (selectedPostForMenu != null) { - val isWriter = selectedPostForMenu!!.isWriter +selectedPostForMenu?.let { selectedPost -> + val isWriter = selectedPost.isWriter
165-169: 토스트 자동 숨김 지연 시간 상수화 필요하드코딩된 3000ms를 상수로 추출하면 유지보수가 용이해집니다.
+companion object { + private const val TOAST_DISPLAY_DURATION = 3000L +} LaunchedEffect(showToast) { if (showToast) { - delay(3000) + delay(TOAST_DISPLAY_DURATION) showToast = false } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (36)
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsCreateVoteRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPostsLikesRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPostsRequestParams.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsRecordRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsVoteRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsBookPageResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsCreateVoteResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsDeleteRecordResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPostsLikesResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPostsResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsRecordResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsUsersResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsVoteResponse.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/buttons/GroupVoteButton.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/FilterHeaderSection.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/TextCommentCard.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/VoteCommentCard.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.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/note/screen/GroupVoteCreateScreen.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteCreateViewModel.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteViewModel.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupVoteCreateViewModel.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomBody.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomMatesScreen.kt(1 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(1 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/utils/type/SortType.kt(1 hunks)app/src/main/res/values/strings.xml(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomBody.kt (1)
app/src/main/java/com/texthip/thip/ui/common/cards/CardVote.kt (1)
CardVote(31-137)
app/src/main/java/com/texthip/thip/ui/group/note/component/VoteCommentCard.kt (1)
app/src/main/java/com/texthip/thip/ui/common/buttons/ActionBarButton.kt (1)
ActionBarButton(24-99)
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt (3)
app/src/main/java/com/texthip/thip/ui/common/topappbar/InputTopAppBar.kt (1)
InputTopAppBar(25-69)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteCreateViewModel.kt (1)
onEvent(60-83)app/src/main/java/com/texthip/thip/ui/group/note/component/OpinionInputSection.kt (1)
OpinionInputSection(25-83)
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (9)
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupVoteCreateViewModel.kt (1)
onEvent(79-114)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteCreateViewModel.kt (1)
onEvent(60-83)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteViewModel.kt (1)
onEvent(111-150)app/src/main/java/com/texthip/thip/ui/common/header/HeaderMenuBarTab.kt (1)
HeaderMenuBarTab(23-69)app/src/main/java/com/texthip/thip/ui/group/note/component/TextCommentCard.kt (1)
TextCommentCard(22-77)app/src/main/java/com/texthip/thip/ui/group/note/component/VoteCommentCard.kt (1)
VoteCommentCard(23-102)app/src/main/java/com/texthip/thip/ui/common/buttons/FilterButton.kt (1)
FilterButton(37-128)app/src/main/java/com/texthip/thip/ui/group/note/component/FilterHeaderSection.kt (1)
FilterHeaderSection(25-87)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt (1)
CommentBottomSheet(30-110)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (3)
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (1)
GroupNoteScreen(72-145)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt (1)
GroupNoteCreateScreen(39-66)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt (1)
GroupVoteCreateScreen(39-66)
🔇 Additional comments (30)
app/src/main/java/com/texthip/thip/ui/group/note/component/FilterHeaderSection.kt (1)
27-37: ✅ 모든 호출부에 onApplyPageFilter 적용 확인모든
FilterHeaderSection호출부가 새로운 시그니처를 사용하며onApplyPageFilter콜백을 전달하고 있고,onDisabledClick과modifier의 기본값도 적절히 유지되고 있습니다. 추가 수정 불필요합니다.• app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt –
onApplyPageFilter전달
• app/src/main/java/com/texthip/thip/ui/group/note/component/FilterHeaderSection.kt –onApplyPageFilter전달,onDisabledClick기본값 사용app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsCreateVoteResponse.kt (1)
5-9: DTO 정의 적절 — 추가 조치 없음필드 구성·네이밍이 명확합니다. 서비스/레포지토리 레이어와의 연결도 자연스러워 보입니다.
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsDeleteRecordResponse.kt (1)
6-8: 삭제 응답 페이로드 검증 완료 (roomId만으로 충분)
- API(
DELETE rooms/{roomId}/record/{recordId})의 응답 모델인RoomsDeleteRecordResponse에는roomId만 정의되어 있으며,recordId는 반환되지 않습니다.- 클라이언트(
GroupNoteViewModel.deleteRecord)에서는 삭제 요청 시 인자로 넘긴recordId(postId)를 사용해 리스트를 필터링하고 있어, 응답 데이터는 실제로 소비되지 않습니다.
→ 현 로직 상 추가로recordId를 반환받지 않아도 삭제 처리 및 UI 업데이트가 정상적으로 수행되므로, 별도 수정은 필요 없습니다.app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsBookPageResponse.kt (1)
5-11: DTO 정의 적절 — 필드 의미가 분명합니다total/recent 페이지와 개요 가능 여부가 구분되어 있어 소비 측에서 이해하기 쉽습니다.
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsRecordResponse.kt (1)
5-9: DTO 정의 적절 — 추가 조치 없음recordId/roomId 조합이 직관적입니다.
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsVoteResponse.kt (1)
6-10: Boolean “type” 필드 의미 명확화 및 @SerialName 고정
- 응답 모델의 Boolean 필드
type은 도메인 의미를 드러내는isSelected등으로 변경을 권장합니다.@SerialName("voteItemId"),@SerialName("roomId"),@SerialName("type")애노테이션을 추가해 서버 API 키를 명시적으로 고정하세요.- 마지막 파라미터 뒤 쉼표(trailing comma)는 Kotlin 1.4 미만에서 문법 오류가 발생할 수 있으므로, 사용 중인 Kotlin 버전이 1.4 이상인지 확인하거나 쉼표를 제거해주세요.
적용 예시:
package com.texthip.thip.data.model.rooms.response -import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable @Serializable data class RoomsVoteResponse( - val voteItemId: Int, - val roomId: Int, - val type: Boolean, + @SerialName("voteItemId") val voteItemId: Int, + @SerialName("roomId") val roomId: Int, + @SerialName("type") val isSelected: Boolean )app/src/main/res/values/strings.xml (2)
226-236: 신규 상태/정렬/토스트 문자열 추가 적절신규 키들이 문맥에 맞고 일관된 네이밍을 따릅니다. 경과 메시지(posting_*)도 사용자 피드백에 유용합니다.
359-360: [검증 완료] Deprecated 리소스 키(search_filter_*) 잔존 참조 없음
- 전역 검색 결과
search_filter_popular및search_filter_latest참조 없음- 신규 키
sort_like/sort_latest정상 사용 위치 확인 (SortType.kt, SearchBookDetailScreen.kt)values폴더 내 중복 정의 없음추가 조치 불필요합니다.
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomMatesScreen.kt (1)
106-114: 프리뷰 데이터 alias → aliasName 반영 적절데이터 모델 변경(UserList.alias → aliasName)에 맞춘 프리뷰 갱신이 정확합니다.
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt (2)
33-36: UI 바인딩 필드 교체(alias → aliasName) 정확하위 텍스트 소스 변경이 데이터 모델과 일치합니다. TODO(서버 색상 적용)도 적절히 표시되어 있어 추후 반영만 남았습니다.
60-68: 프리뷰 데이터 aliasName 반영 일치프리뷰에서도 동일하게 aliasName을 사용해 일관성이 유지됩니다.
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsUsersResponse.kt (1)
15-15: @JsonNames 어노테이션 추가로 aliasName 역직렬화 호환성 보장프로젝트에 Kotlinx Serialization 플러그인(alias(libs.plugins.kotlin.serialization))과
kotlinx-serialization.json의존성이 이미 적용되어 있어, JsonNames 어노테이션 사용이 가능합니다. 다만, 사용 중인 kotlinx-serialization 버전이 JsonNames를 지원하는(≥1.4.0)지 확인해 주세요.
파일:
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsUsersResponse.kt(15행)적용 예시:
+import kotlinx.serialization.json.JsonNames @@ - val aliasName: String, + @JsonNames("alias", "aliasName") + val aliasName: String,라이브러리 버전 확인 예시:
fd -a 'libs.versions.toml' | xargs sed -n '1,200p' | rg -n 'serialization'app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomBody.kt (4)
26-29: 콜백 주입으로 네비게이션/액션 분리된 점 좋습니다UI-상위 계층으로 이벤트를 올려 Test/유지보수성이 좋아졌습니다. 기본값 제공으로 기존 호출부 호환성도 유지됩니다.
42-44: CardNote 클릭 → onNavigateToNote 위임 적절카드 인터랙션을 한 곳으로 라우팅해 책임 분리가 명확합니다.
52-54: 투표 카드 클릭 콜백 전달 적절CardVote의 onVoteClick을 외부로 노출하여 화면 간 네비게이션/상세 보기로 확장하기 용이합니다.
19-29: currentVotes 필수 파라미터 호출부 전파 확인 완료아래 모든 호출부에서
currentVotes인자를 전달하고 있어 컴파일/런타임 이슈가 없습니다.• app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomBody.kt (Preview)
• app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.ktapp/src/main/java/com/texthip/thip/ui/group/note/component/TextCommentCard.kt (1)
61-69: Stateless 좋아요 처리 변경 👍
isLiked = data.isLiked로 단방향 데이터 흐름을 유지하고, 액션만 외부로 위임하는 패턴이 적절합니다. ViewModel 상태 반영 시 UI 일관성이 좋아집니다.app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt (3)
63-70: navigateToGroupNote 추가 LGTM타입 세이프 라우트 객체를 그대로 전달하는 접근이 간결하고 안전합니다. 옵셔널 파라미터(null 허용) 설계도 적절해 보입니다.
73-87: navigateToGroupNoteCreate 추가 LGTM생성 플로우에 필요한 컨텍스트(recent/total/isOverviewPossible)를 명시적으로 전달하는 설계가 좋습니다.
90-104: navigateToGroupVoteCreate 추가 LGTM노트 생성과 대칭적인 API로 일관성이 유지됩니다.
app/src/main/java/com/texthip/thip/ui/common/buttons/GroupVoteButton.kt (1)
74-76: clip → background 순서 변경 적절컨테이너를 먼저 clip하고 배경과 진행 바를 그리는 방식은 모서리 라운딩을 안정적으로 보장합니다. 성능/시각적 일관성 측면에서 좋은 변경입니다.
app/src/main/java/com/texthip/thip/ui/group/note/component/VoteCommentCard.kt (1)
87-93: Stateless 좋아요 처리로 일관성 향상 👍
isLiked를 상태 원천으로 두고 액션만 외부로 위임하는 개선이 적절합니다.app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (1)
223-225: Lifecycle-aware 수집으로 변경 👍
collectAsStateWithLifecycle()로 전환해 생명주기 정합성을 확보한 점 좋습니다.app/src/main/java/com/texthip/thip/data/service/RoomsService.kt (2)
38-45: 쿼리 파라미터 null 허용 및 기본값 설정 일관성 확인 필요
type과sort는 non-null에 기본값이 지정되어 있는 반면, 나머지 파라미터들은 nullable로 선언되어 있습니다.
API 스펙(또는 서버 문서)에 따라 다음을 일관성 있게 처리해 주세요:
- 필수 값이라면 non-null + 기본값(또는 기본값 제거)
- 선택 값이라면 nullable + 기본값(null)
대상 위치
• app/src/main/java/com/texthip/thip/data/service/RoomsService.kt: 38–45행API 명세 파일이 저장소에 없거나 검색되지 않아 자동 확인이 불가합니다. 실제 API 스펙(서버팀 문서 등)을 참조하여 nullable 여부와 기본값 설정을 조율해 주시기 바랍니다.
77-81: API 엔드포인트 네이밍 일관성 확인 필요
- 파일:
app/src/main/java/com/texthip/thip/data/service/RoomsService.kt(77–81행)- 현재 한 곳만
@POST("room-posts/{postId}/likes")패턴을 사용하고 있습니다.- 다른 모든 엔드포인트는
rooms/{roomId}/…형식을 따르고 있으므로, 해당 엔드포인트도 의도된 설계인지 확인 부탁드립니다.
필요하다면 예를 들어rooms/{roomId}/posts/{postId}/likes또는rooms/posts/{postId}/likes등으로 경로를 일관성 있게 변경해 주세요.app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteViewModel.kt (1)
152-181: 좋아요 기능의 낙관적 업데이트 구현 우수낙관적 UI 업데이트와 실패 시 롤백 처리가 잘 구현되었습니다. 사용자 경험을 향상시키는 좋은 패턴입니다.
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupVoteCreateViewModel.kt (2)
92-98: 페이지 텍스트 저장/복원 로직이 명확함총평 토글 시 페이지 텍스트를 저장하고 복원하는 로직이 잘 구현되었습니다. 사용자 입력 값을 보존하는 좋은 UX 패턴입니다.
100-110: 옵션 추가/삭제 경계 조건 처리 우수최소 2개, 최대 5개 옵션 제한이 명확하게 구현되었습니다. 경계값 검증이 적절합니다.
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (2)
182-194: 무한 스크롤 로직 구현 우수
derivedStateOf를 사용한 스크롤 끝 감지와 자동 로딩 구현이 효율적입니다. 마지막 페이지 체크도 적절합니다.
349-384: 포스트 타입별 렌더링 분기 처리 적절함
postType에 따른 카드 컴포넌트 분기와 이벤트 핸들링이 명확하게 구현되었습니다.
| data class RoomsVoteRequest( | ||
| val voteItemId: Int, | ||
| val type: Boolean | ||
| ) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
불명확한 필드명(type) 개선 + API 키 고정(@SerialName) 제안
Boolean에 “type”은 의미가 모호합니다. 도메인 의미(선택/해제 등)가 드러나도록 프로퍼티명을 개선하고, 서버 키는 @SerialName으로 고정해 API 스키마와 도메인 모델을 분리하는 것을 권장합니다.
- 백엔드에서 실제로 "type"이 Boolean인지(문자열/정수 아님) 확인 부탁드립니다.
- ID가 Long 범위일 가능성이 있으면 Int → Long 전환 검토 바랍니다.
적용 예시:
package com.texthip.thip.data.model.rooms.request
-import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
@Serializable
data class RoomsVoteRequest(
- val voteItemId: Int,
- val type: Boolean
+ @SerialName("voteItemId") val voteItemId: Int,
+ @SerialName("type") val isSelected: Boolean
)🏁 Script executed:
#!/bin/bash
set -e
# 1. Locate usages of RoomsVoteRequest in Retrofit or service interfaces
rg "RoomsVoteRequest" -n
# 2. Search for vote-related endpoints to see how 'type' is used
rg "@POST" -n app/src
rg "vote" -n app/src
# 3. Check for existing @SerialName annotations in model packages for consistency
rg "@SerialName" -n app/srcLength of output: 22360
불명확한 필드명(type) 개선 및 @SerialName 적용 제안
Boolean 타입의 type은 “투표”인지 “취소”인지 도메인 의미가 드러나지 않아 가독성이 떨어집니다. 또한 API 스키마와 도메인 모델을 분리하기 위해 @SerialName을 명시적으로 적용하는 것이 좋습니다.
적용 예시 (app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsVoteRequest.kt):
-import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
@Serializable
data class RoomsVoteRequest(
- val voteItemId: Int,
- val type: Boolean
+ @SerialName("voteItemId") val voteItemId: Int,
+ @SerialName("type") val isSelected: Boolean
)검증 요청:
- 백엔드에서 실제로
"type"필드가 Boolean 형태로 내려오는지 확인 부탁드립니다. voteItemId가 Long 범위여서 Int로 처리하기 어려운 경우Int → Long전환을 검토해 주세요.
📝 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.
| data class RoomsVoteRequest( | |
| val voteItemId: Int, | |
| val type: Boolean | |
| ) | |
| import kotlinx.serialization.SerialName | |
| import kotlinx.serialization.Serializable | |
| @Serializable | |
| data class RoomsVoteRequest( | |
| @SerialName("voteItemId") val voteItemId: Int, | |
| @SerialName("type") val isSelected: Boolean | |
| ) |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsVoteRequest.kt
around lines 6-9, the Boolean field named `type` is unclear and the class lacks
explicit serialization name mapping; rename the field to a descriptive name such
as `isVote: Boolean` (or `isCancel: Boolean` if that matches domain semantics)
and add @SerialName("type") on that property to keep the API schema mapping, and
if backend uses 64-bit ids change `voteItemId: Int` to `voteItemId: Long`
(confirm with backend whether `"type"` is actually Boolean and whether IDs
exceed Int range).
| val postList: List<PostList>, | ||
| val roomId: Int, | ||
| val isbn: String, | ||
| val isOverviewEnabled: Boolean, | ||
| val nextCursor: String?, | ||
| val isLast: Boolean, | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
직렬화 안정성 강화: 기본값 부여로 MissingFieldException 예방
백엔드가 특정 필드를 조건부로 생략하는 경우를 대비해 컬렉션/옵셔널 필드에 기본값을 부여해 두는 것이 안전합니다.
다음과 같이 기본값을 제안합니다.
data class RoomsPostsResponse(
- val postList: List<PostList>,
+ val postList: List<PostList> = emptyList(),
@@
- val nextCursor: String?,
+ val nextCursor: String? = null,
- val isLast: Boolean,
+ val isLast: Boolean = false,
)
@@
- val profileImageUrl: String?,
+ val profileImageUrl: String? = null,
@@
- val voteItems: List<VoteItems>,
+ val voteItems: List<VoteItems> = emptyList(),비투표형 포스트에서 voteItems가 누락되더라도 디코딩이 안전해집니다.
Also applies to: 23-24, 30-31
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPostsResponse.kt
around lines 7-13 (and likewise update related DTOs referenced at lines 23-24
and 30-31), several collection/optional properties lack safe default values
which can cause MissingFieldException during deserialization; update the Kotlin
data classes to provide sensible defaults (e.g., val postList: List<PostList> =
emptyList(), nullable strings to = null or "" as appropriate, booleans to a safe
default like false, and optional cursors to null) so decoding remains safe when
backend omits conditional fields — apply the same defaulting pattern to the
other mentioned fields at lines 23-24 and 30-31.
| type: String = "group", | ||
| sort: String? = "latest", |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
기본 파라미터 값 검증 필요
sort 파라미터의 기본값이 "latest"로 설정되어 있지만, API가 지원하는 정렬 옵션들이 무엇인지 명확하지 않습니다. 잘못된 값이 전달될 경우를 대비한 검증이 필요할 수 있습니다.
🏁 Script executed:
#!/bin/bash
# Description: API가 지원하는 정렬 옵션 확인
# swagger 문서나 API 명세 파일 검색
fd -e json -e yaml -e md | xargs rg -A 5 "sort.*latest|sorting.*options"
# 정렬 관련 상수나 enum 확인
ast-grep --pattern 'enum $_SortType {
$$$
}'
# 정렬 관련 타입 정의 확인
rg -A 3 "enum.*Sort|object.*Sort|companion.*Sort"Length of output: 496
🏁 Script executed:
#!/bin/bash
# Display full enum entries for supported sort options
sed -n '1,200p' app/src/main/java/com/texthip/thip/utils/type/SortType.ktLength of output: 546
🏁 Script executed:
#!/bin/bash
# Show context around the sort parameter in RoomsRepository
sed -n '1,100p' app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.ktLength of output: 3014
🏁 Script executed:
#!/bin/bash
# Locate and display the service interface for getRoomsPosts
file="$(rg -l 'interface RoomsService' -g '*.kt')"
echo "RoomsService file: $file"
sed -n '1,200p' "$file" | sed -n '/getRoomsPosts/,/)/p'Length of output: 469
Sort 파라미터에 enum 기반 검증 추가 필요
현재 getRoomsPosts 메서드는 sort: String? = "latest"로 설정되어 있어 기본값 “latest”는 SortType enum에 들어있지만, 잘못된 값이 들어올 경우 API 호출 시 예기치 않은 결과를 초래할 수 있습니다. SortType enum(LATEST("latest"), LIKE("like"), COMMENT("comment"))을 활용해 타입 안전성을 보장하고 기본·잘못된 입력 모두를 검증하도록 수정하는 것을 제안합니다.
수정 포인트:
- app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt
•getRoomsPosts시그니처와 API 호출부 변경 - (선택) app/src/main/java/com/texthip/thip/utils/type/SortType.kt
• 이미 정의된fromApiKey메서드 활용 검토
예시 diff:
@@ RoomsRepository.kt
- suspend fun getRoomsPosts(
- roomId: Int,
- type: String = "group",
- sort: String? = "latest",
+ suspend fun getRoomsPosts(
+ roomId: Int,
+ type: String = "group",
+ sort: SortType = SortType.LATEST,
pageStart: Int? = null,
pageEnd: Int? = null,
isOverview: Boolean? = false,
isPageFilter: Boolean? = false,
cursor: String? = null,
) = runCatching {
roomsService.getRoomsPosts(
roomId = roomId,
type = type,
- sort = sort,
+ sort = sort.apiKey,
pageStart = pageStart,
pageEnd = pageEnd,
isOverview = isOverview,
isPageFilter = isPageFilter,
cursor = cursor
).handleBaseResponse().getOrThrow()
}이렇게 변경하면
- 기본값 ‘LATEST’는 enum으로 보장
- 외부 입력이 들어올 때도
SortType.fromApiKey(...)혹은 enum 파라미터 덕분에 유효성 검증이 이루어집니다. - API에 항상 올바른
apiKey값만 전달되어 안정성이 높아집니다.
📝 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.
| type: String = "group", | |
| sort: String? = "latest", | |
| // app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt | |
| suspend fun getRoomsPosts( | |
| roomId: Int, | |
| type: String = "group", | |
| sort: SortType = SortType.LATEST, | |
| pageStart: Int? = null, | |
| pageEnd: Int? = null, | |
| isOverview: Boolean? = false, | |
| isPageFilter: Boolean? = false, | |
| cursor: String? = null | |
| ) = runCatching { | |
| roomsService.getRoomsPosts( | |
| roomId = roomId, | |
| type = type, | |
| sort = sort.apiKey, | |
| pageStart = pageStart, | |
| pageEnd = pageEnd, | |
| isOverview = isOverview, | |
| isPageFilter = isPageFilter, | |
| cursor = cursor | |
| ) | |
| .handleBaseResponse() | |
| .getOrThrow() | |
| } |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt around
lines 35-36, replace the String sort parameter with the SortType enum (default
to SortType.LATEST) in getRoomsPosts, and ensure any external String input is
converted/validated via SortType.fromApiKey(...) before calling the API; update
the API call to send the enum's apiKey (e.g., sort.apiKey) so only validated
enum values go to the backend.
| type: Boolean | ||
| ) = runCatching { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Boolean 타입 파라미터명 일관성 부족
type: Boolean 파라미터명이 모호합니다. 다른 메서드들에서는 isOverview, isPageFilter 같은 명확한 boolean 네이밍을 사용하는데, 여기서만 type이라는 일반적인 이름을 사용합니다.
파라미터명을 더 명확하게 변경하는 것을 권장합니다:
suspend fun postRoomsVote(
roomId: Int,
voteId: Int,
voteItemId: Int,
- type: Boolean
+ isVoteFor: Boolean // 또는 isPositiveVote, isAgree 등
) = runCatching {
roomsService.postRoomsVote(
roomId = roomId,
voteId = voteId,
request = RoomsVoteRequest(
voteItemId = voteItemId,
- type = type
+ type = isVoteFor
)
).handleBaseResponse().getOrThrow()
}📝 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.
| type: Boolean | |
| ) = runCatching { | |
| suspend fun postRoomsVote( | |
| roomId: Int, | |
| voteId: Int, | |
| voteItemId: Int, | |
| isVoteFor: Boolean // 또는 isPositiveVote, isAgree 등 | |
| ) = runCatching { | |
| roomsService.postRoomsVote( | |
| roomId = roomId, | |
| voteId = voteId, | |
| request = RoomsVoteRequest( | |
| voteItemId = voteItemId, | |
| type = isVoteFor | |
| ) | |
| ).handleBaseResponse().getOrThrow() | |
| } |
| import com.texthip.thip.ui.group.note.component.CommentItem | ||
| import com.texthip.thip.ui.group.note.component.ReplyItem |
There was a problem hiding this comment.
🛠️ Refactor suggestion
중복 import 제거 (CommentItem/ReplyItem가 두 번 import됨)
Line 51–52는 Line 55–56과 완전히 동일하여 중복입니다. ktlint/IDE에서 경고가 나거나 스타일 체크에 걸릴 수 있으니 최근에 추가된 51–52를 제거하세요.
적용 diff:
-import com.texthip.thip.ui.group.note.component.CommentItem
-import com.texthip.thip.ui.group.note.component.ReplyItem📝 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.note.component.CommentItem | |
| import com.texthip.thip.ui.group.note.component.ReplyItem |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt around
lines 51–52, the imports for CommentItem and ReplyItem are duplicated (they are
also present at lines 55–56); remove the duplicate import statements at lines
51–52 so each class is imported only once, save the file and run ktlint/IDE
import organiser to verify no remaining duplicate-import warnings.
| // roomId = roomId, | ||
| roomId = 1, | ||
| resultTabIndex = result, |
There was a problem hiding this comment.
하드코딩된 roomId=1 제거 (GroupNoteScreen)
노트 화면 초기화에 잘못된 roomId가 사용되고 있습니다.
-// roomId = roomId,
- roomId = 1,
+ roomId = roomId,📝 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.
| // roomId = roomId, | |
| roomId = 1, | |
| resultTabIndex = result, | |
| roomId = roomId, | |
| resultTabIndex = result, |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt
around lines 227 to 229, the GroupNoteScreen call incorrectly hardcodes roomId =
1; remove the hardcoded value and pass the actual roomId variable that is
available in scope (e.g., roomId = roomId or the appropriate parameter/argument
that carries the selected room id), ensuring types/nullability match; update any
callers or lambda that construct this navigation state to provide the correct
room id if it's missing.
| GroupNoteCreateScreen( | ||
| roomId = 1, | ||
| recentPage = route.recentBookPage, | ||
| totalPage = route.totalBookPage, | ||
| isOverviewPossible = route.isOverviewPossible, |
There was a problem hiding this comment.
하드코딩된 roomId=1 제거 (GroupNoteCreateScreen)
노트 생성 화면에도 동일한 문제.
- roomId = 1,
+ roomId = roomId,🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt
around lines 262 to 266, the GroupNoteCreateScreen is being constructed with a
hardcoded roomId = 1; replace that hardcoded value with the actual room id
coming from the navigation route (e.g., route.roomId or the appropriate value
provided by the nav args/parent), ensure the property name and type match
GroupNoteCreateScreen's parameter, and propagate the correct roomId through the
call chain so the screen uses the real room context.
| GroupVoteCreateScreen( | ||
| // roomId = roomId, | ||
| roomId = 1, | ||
| recentPage = route.recentPage, |
There was a problem hiding this comment.
하드코딩된 roomId=1 제거 (GroupVoteCreateScreen)
투표 생성 화면에도 동일한 문제.
-// roomId = roomId,
- roomId = 1,
+ roomId = roomId,📝 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.
| GroupVoteCreateScreen( | |
| // roomId = roomId, | |
| roomId = 1, | |
| recentPage = route.recentPage, | |
| GroupVoteCreateScreen( | |
| roomId = roomId, | |
| recentPage = route.recentPage, |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt
around lines 283-286, remove the hardcoded roomId = 1 and restore passing the
real roomId (use the existing roomId variable or extract it from the route),
e.g. replace the hardcoded value with roomId = roomId (or route.roomId if that’s
where it comes from); ensure the value is the correct type
(convert/nullable-handle if needed) so the GroupVoteCreateScreen receives the
actual room id.
| @Serializable | ||
| data class NoteCreate( | ||
| val roomId: Int, | ||
| val recentBookPage: Int, | ||
| val totalBookPage: Int, | ||
| val isOverviewPossible: Boolean | ||
| ) | ||
|
|
||
| @Serializable | ||
| data class VoteCreate( | ||
| val roomId: Int, | ||
| val recentPage: Int, | ||
| val totalPage: Int, | ||
| val isOverviewPossible: Boolean | ||
| ) |
There was a problem hiding this comment.
중대: NoteCreate/VoteCreate가 GroupRoutes를 상속하지 않아 컴파일 불가
sealed class 내부의 서브타입은 반드시 명시적으로 상속해야 합니다. 현재 NoteCreate, VoteCreate는 GroupRoutes를 확장하지 않아 네비게이션 타입 안전성이 깨지고 빌드가 실패합니다.
적용 패치:
@Serializable
data class NoteCreate(
val roomId: Int,
val recentBookPage: Int,
val totalBookPage: Int,
val isOverviewPossible: Boolean
- )
+ ) : GroupRoutes()
@Serializable
data class VoteCreate(
val roomId: Int,
val recentPage: Int,
val totalPage: Int,
val isOverviewPossible: Boolean
- )
+ ) : GroupRoutes()추가 권고(선택): NoteCreate와 VoteCreate에서 recent/total 네이밍을 서로 일관되게 유지(recentBookPage vs recentPage)하면 API/화면 레이어에서 혼선을 줄일 수 있습니다.
📝 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.
| @Serializable | |
| data class NoteCreate( | |
| val roomId: Int, | |
| val recentBookPage: Int, | |
| val totalBookPage: Int, | |
| val isOverviewPossible: Boolean | |
| ) | |
| @Serializable | |
| data class VoteCreate( | |
| val roomId: Int, | |
| val recentPage: Int, | |
| val totalPage: Int, | |
| val isOverviewPossible: Boolean | |
| ) | |
| @Serializable | |
| data class NoteCreate( | |
| val roomId: Int, | |
| val recentBookPage: Int, | |
| val totalBookPage: Int, | |
| val isOverviewPossible: Boolean | |
| ) : GroupRoutes() | |
| @Serializable | |
| data class VoteCreate( | |
| val roomId: Int, | |
| val recentPage: Int, | |
| val totalPage: Int, | |
| val isOverviewPossible: Boolean | |
| ) : GroupRoutes() |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt around
lines 32 to 46, the NoteCreate and VoteCreate data classes do not extend the
sealed superclass GroupRoutes causing compilation failure; update both
declarations to explicitly inherit from GroupRoutes (e.g., make them subtypes of
the sealed class) so they are valid sealed-class variants, keeping their
existing properties, and optionally normalize the recent/total property names
between NoteCreate and VoteCreate (recentBookPage -> recentPage and
totalBookPage -> totalPage or vice versa) to maintain consistent naming across
APIs and UI layers.
| fun fromApiKey(key: String?): SortType { | ||
| return entries.find { it.apiKey == key } ?: LATEST | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Kotlin 버전 호환성 및 견고성: entries 사용과 대소문자 민감 비교
- entries는 Kotlin 1.9+에서 도입되었습니다. 프로젝트 Kotlin 버전이 낮으면 컴파일 실패합니다.
- API 키 비교는 대소문자/공백 실수에 취약합니다.
버전 호환 및 견고성을 위해 values() 기반 + normalize 적용을 권장합니다.
companion object {
fun fromApiKey(key: String?): SortType {
- return entries.find { it.apiKey == key } ?: LATEST
+ val normalized = key?.trim()?.lowercase()
+ return values().firstOrNull { it.apiKey == normalized } ?: LATEST
}
}Kotlin 버전 확인 스크립트:
🏁 Script executed:
#!/bin/bash
fd -a 'build.gradle*' | xargs -I{} sh -c "echo '--- {} ---'; sed -n '1,200p' {} | rg -n 'plugins|kotlin|org.jetbrains.kotlin.android|kotlin_version|kotlin-gradle-plugin' -n -H -S -N"
fd -a 'libs.versions.toml' | xargs -I{} sh -c "echo '--- {} ---'; sed -n '1,200p' {} | rg -n 'kotlin' -n -H -S -N"Length of output: 1852
API 키 비교 견고성 향상: 대소문자 무시 및 공백 제거
- 현재 Kotlin 버전이 2.2.0이므로
entries사용에 호환성 이슈가 없습니다. - 입력값에
trim()과ignoreCase = true를 적용해 대소문자·공백 실수를 방지하세요.
수정 예시:
companion object {
fun fromApiKey(key: String?): SortType {
- return entries.find { it.apiKey == key } ?: LATEST
+ val normalizedKey = key?.trim()
+ return entries.firstOrNull { it.apiKey.equals(normalizedKey, ignoreCase = true) } ?: LATEST
}
}📝 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.
| fun fromApiKey(key: String?): SortType { | |
| return entries.find { it.apiKey == key } ?: LATEST | |
| } | |
| companion object { | |
| fun fromApiKey(key: String?): SortType { | |
| val normalizedKey = key?.trim() | |
| return entries.firstOrNull { it.apiKey.equals(normalizedKey, ignoreCase = true) } ?: LATEST | |
| } | |
| } |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/utils/type/SortType.kt around lines 15-17,
the fromApiKey function should normalize the incoming key before matching;
update it to trim the input and perform a case-insensitive comparison (e.g.,
compute val normalized = key?.trim(); if normalized.isNullOrEmpty() return
LATEST; then use entries.find { it.apiKey.equals(normalized, ignoreCase = true)
} ?: LATEST) so that leading/trailing whitespace and letter-case differences do
not break matching.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/src/main/java/com/texthip/thip/ui/navigator/BottomNavigationBar.kt(3 hunks)
🔇 Additional comments (2)
app/src/main/java/com/texthip/thip/ui/navigator/BottomNavigationBar.kt (2)
10-10: 시스템 내비게이션 바 인셋 대응 import 추가, 적절합니다.하단 시스템 바와의 겹침을 피하려는 의도가 명확합니다. 전체 플로우(그룹 노트/투표 화면)와도 잘 맞습니다.
32-32: import 재배치 OK, 사용처도 유효합니다.navigateToTab는 Line 121에서 사용되고 있어 불필요한 import가 아닙니다. 기능적 변경 없음으로 판단합니다.
| Box( | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .navigationBarsPadding() |
There was a problem hiding this comment.
Modifier 순서로 인해 바 높이가 줄어들 수 있습니다. height 뒤에 navigationBarsPadding을 두세요.
현재 순서(.navigationBarsPadding() → .height(73.dp))에서는 총 높이가 73dp로 고정되고, 콘텐츠 영역만 인셋만큼 줄어듭니다. 3-버튼 네비게이션 디바이스에서 하단 인셋이 큰 경우 아이콘/라벨이 눌리거나 클리핑될 수 있습니다. .height 이후에 .navigationBarsPadding을 배치해 바 자체가 인셋만큼 키워지도록 하세요.
아래처럼 순서를 바꾸는 것을 제안합니다.
modifier = Modifier
.fillMaxWidth()
- .navigationBarsPadding()
- .height(73.dp)
+ .height(73.dp)
+ .navigationBarsPadding()
.clip(대안: WindowInsets API를 사용할 수 있다면 windowInsetsPadding(WindowInsets.navigationBars)도 고려해 볼 수 있습니다.
📝 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.
| .navigationBarsPadding() | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .height(73.dp) | |
| .navigationBarsPadding() | |
| .clip( | |
| // … | |
| ) |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/navigator/BottomNavigationBar.kt around
line 44, the Modifier order places navigationBarsPadding() before height(73.dp)
which keeps the total bar height fixed at 73dp and only reduces the content
area; move .height(73.dp) before .navigationBarsPadding() so the bar itself
grows by the inset and icons/labels won't be clipped on devices with large
navigation bar insets; alternatively consider using
windowInsetsPadding(WindowInsets.navigationBars) if available.
➕ 이슈 링크
🔎 작업 내용
📸 스크린샷
😢 해결하지 못한 과제
제 담당에서는 이제 댓글 api만 연결하면 됩니닷 ! (+ 서버에서 아직 개발 안한것들 ..)
📢 리뷰어들에게
Summary by CodeRabbit