Conversation
…into api/THIP-TextHip#81-comments # Conflicts: # app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt
Walkthrough댓글 기능을 위한 전체 계층을 추가·변경: Retrofit 서비스/요청·응답 모델/리포지토리/DI 바인딩/뷰모델/컴포저블 갱신 및 바텀시트 연동. 방/사용자 색상 필드 추가와 전달, 네비게이션의 roomId 동적화, IME 패딩 유틸과 매니페스트 입력 모드, 이미지 로더 교체 포함. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as GroupNoteScreen
participant Sheet as CommentBottomSheet
participant VM as CommentsViewModel
participant Repo as CommentsRepository
participant Svc as CommentsService
UI->>VM: initialize(postId, postType)
VM->>Repo: getComments(postId, postType, cursor)
Repo->>Svc: GET /comments/{postId}?postType&cursor
Svc-->>Repo: BaseResponse<CommentsResponse>
Repo-->>VM: Result<CommentsResponse>
VM-->>Sheet: uiState(comments, isLoading=false)
Sheet->>VM: onEvent(LoadMoreComments)
VM->>Repo: getComments(..., cursor=nextCursor)
Repo->>Svc: GET /comments/{postId}?cursor=...
Svc-->>Repo: BaseResponse<CommentsResponse>
Repo-->>VM: Result<CommentsResponse>
VM-->>Sheet: uiState(append, isLast)
Sheet->>VM: onEvent(LikeComment(commentId))
VM->>Repo: likeComment(commentId, type=toggle)
Repo->>Svc: POST /comments/{id}/likes { type }
Svc-->>Repo: BaseResponse<CommentsLikesResponse>
Repo-->>VM: Result<...>
VM-->>Sheet: uiState(update isLike)
Sheet->>VM: onEvent(CreateComment(content, parentId))
VM->>Repo: createComment(postId, content, isReply, parentId)
Repo->>Svc: POST /comments/{postId} { content, ... }
Svc-->>Repo: BaseResponse<CommentsCreateResponse>
Repo-->>VM: Result<...>
VM->>Repo: getComments(refresh)
Repo->>Svc: GET /comments/{postId}
Svc-->>Repo: BaseResponse<CommentsResponse>
Repo-->>VM: Result<CommentsResponse>
VM-->>Sheet: uiState(refresh list)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related issues
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: 15
🔭 Outside diff range comments (1)
app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt (1)
41-57: 프로필 이미지 로딩 안정화: 공백 처리, 크로핑, 크로스페이드 추가
- 빈 문자열("")이 들어와도 네트워크 요청이 발생하지 않도록 isNullOrBlank 체크로 변경
- 원형 클리핑 시 테두리 잔여 공간이 보이지 않게 ContentScale.Crop 적용
- 로딩 UX와 깜빡임 완화를 위해 Coil crossfade 적용
다음 변경을 제안합니다:
- Row { - if (profileImage != null) { - AsyncImage( - model = profileImage, - contentDescription = "프로필 이미지", - modifier = Modifier - .size(24.dp) - .clip(CircleShape) - ) + Row { + if (!profileImage.isNullOrBlank()) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(profileImage) + .crossfade(true) + .build(), + contentDescription = null, + modifier = Modifier + .size(24.dp) + .clip(CircleShape), + contentScale = ContentScale.Crop + ) } else { Box( modifier = Modifier .size(24.dp) .clip(CircleShape) .background(colors.Grey) ) }필요 import:
+import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import coil.request.ImageRequest
🧹 Nitpick comments (25)
app/src/main/AndroidManifest.xml (1)
20-20: adjustResize와 Compose/insets 동작 상호작용 확인 필요edge-to-edge(예: WindowCompat.setDecorFitsSystemWindows(false)) 또는 투명 status/navigation bar를 쓰는 경우 adjustResize가 기대대로 동작하지 않거나 imePadding 처리와 중복될 수 있습니다. 댓글 작성 화면처럼 입력창이 하단에 있는 화면에서 키보드 오버랩이 없는지 실제 기기(삼성/픽셀), 제스처/버튼 내비, 다크모드 조합으로 스모크 테스트 권장합니다. 필요 시 테마 레벨로 이동해 일관성도 확보하세요.
app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsLikesRequest.kt (1)
1-8: Boolean 필드명 'type'은 의미 모호 — 가독성 향상과 확장성 확보 제안불리언에 'type'은 의미가 불분명합니다. 로컬 이름을 의미 있게 개선하고, 서버 키는 @SerialName으로 유지하는 것을 권장합니다. 장기적으로는 enum(Like, Cancel 등)도 고려해 주세요.
package com.texthip.thip.data.model.comments.request import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName @Serializable data class CommentsLikesRequest( - val type: Boolean + @SerialName("type") + val like: Boolean )선택: 서버가 Boolean 대신 문자열/정수로 액션을 받도록 바뀐다면 enum + @SerialName으로의 전환이 자연스럽습니다.
app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsLikesResponse.kt (1)
5-9: 의미 문서화: isLiked의 "처리 후 최종 상태" 명시 필요API 소비자가 혼동하지 않도록 isLiked가 “토글 처리 이후의 최종 상태”인지 “요청값”인지 KDoc으로 명시해 주세요.
예시:
@Serializable +/** + * 댓글 좋아요 처리 결과 + * @param commentId 대상 댓글 ID + * @param isLiked 서버 처리 이후의 최종 좋아요 상태(true=좋아요 상태) + */ data class CommentsLikesResponse(app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt (1)
45-45: 접근성(a11y): contentDescription 재검토동일 행에 닉네임 텍스트가 있어 스크린리더가 중복 낭독할 가능성이 있습니다. 장식용 이미지라면 contentDescription = null을 권장합니다. 반대로 이미지 자체에 의미가 있다면 닉네임을 포함해 구체화해 주세요(예: "$nickname 프로필 이미지").
app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsCreateResponse.kt (1)
6-8: 향후 서버 응답 확장 대비PR 설명대로 서버가 생성된 댓글 전체 데이터를 반환하도록 변경되면, 이 응답 모델을 해당 도메인 모델(예: CommentItem/CommentList)로 교체하거나 포함하도록 확장하면 화면 전체 리렌더 없이 신규 아이템만 갱신할 수 있습니다. 준비되면 데이터 모델/매퍼 변경 작업 도와드릴게요.
app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt (1)
6-13: 입력 포맷 허용 범위 확장: '#' 누락/0x 프리픽스 대응백엔드/디자인 시스템에서 '#RRGGBB' 대신 'RRGGBB' 또는 '0xRRGGBB' 형태로 내려오는 경우를 방어하면 런타임 예외를 줄일 수 있습니다.
아래처럼 정규화한 뒤 파싱하도록 제안합니다:
fun hexToColor(hex: String): Color { return try { - Color(hex.toColorInt()) + val normalized = hex.trim().let { + var h = it + if (h.startsWith("0x", true)) h = h.substring(2) + if (!h.startsWith("#")) h = "#$h" + h + } + Color(normalized.toColorInt()) } catch (e: IllegalArgumentException) { //잘못된 형식이면 기본 색 Color.White } }추가로 3자리 축약형(#RGB)이나 알파 포함(#AARRGGBB) 지원 범위를 문서화해두면 호출부에서의 혼선을 줄일 수 있습니다.
app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt (1)
10-10: postType 문자열 오타 방지postType은 허용 값이 정해진 도메인 값으로 보입니다. 문자열 직접 사용은 오타 리스크가 있어, enum(@serializable + @SerialName) 또는 상수 객체로 중앙집중 관리하는 것을 권장합니다.
예시(값은 실제 서버 스펙에 맞게 교체 필요):
@Serializable enum class PostType { @SerialName("GROUP_NOTE") GROUP_NOTE, @SerialName("ROOM_FEED") ROOM_FEED, }또는
object PostTypes { const val GROUP_NOTE = "GROUP_NOTE" const val ROOM_FEED = "ROOM_FEED" }원하시면 enum/매퍼 적용 PR 보조해 드리겠습니다.
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt (2)
35-35: hex 색상 파싱 실패 대비 및 리컴포지션 비용 최적화 제안
- 현재 매 렌더링마다
hexToColor(member.aliasColor)가 호출됩니다.remember로 메모이즈하면 불필요한 변환을 줄일 수 있습니다.- 백엔드에서
#RRGGBB형식이 아닌 값(예: 빈 문자열,RRGGBB,#RGB)이 오면hexToColor에서Color.White로 떨어지는데, 배경과의 대비가 낮아질 수 있습니다. 기본값을 테마 컬러로 넘기거나hexToColor에서 입력 정규화(자동#부착 등)를 권장합니다.적용 예시(이 파일 내 변경):
+import androidx.compose.runtime.remember @@ - bottomTextColor = hexToColor(member.aliasColor), + bottomTextColor = remember(member.aliasColor) { hexToColor(member.aliasColor) },
HexToColor.kt(참고용, 별도 파일)도 아래처럼 방어적으로 개선 가능합니다:// app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt fun hexToColor(hex: String): Color { val raw = hex.trim() if (raw.isEmpty()) return Color.White val normalized = if (raw.startsWith("#")) raw else "#$raw" return try { Color(normalized.toColorInt()) } catch (_: IllegalArgumentException) { Color.White } }
61-70: 프리뷰 다양화로 시각 검증성 향상두 사용자 모두 같은
aliasColor를 사용하고 있어 프리뷰 상 변화가 드러나지 않습니다. 서로 다른 색상을 부여해 대비, 가독성, 디자인 의도를 시각적으로 검증하는 것을 권장합니다.예시:
- aliasColor= "#A0F8E8", + aliasColor= "#FFD47E",app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt (1)
41-44: DI 제공 함수 네이밍 일관성다른 함수들은
provideXxxService를 사용하고 있습니다.providesCommentsService도provideCommentsService로 맞추면 일관성이 좋아집니다.- fun providesCommentsService(retrofit: Retrofit): CommentsService = + fun provideCommentsService(retrofit: Retrofit): CommentsService = retrofit.create(CommentsService::class.java)app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomHeader.kt (1)
40-41: categoryColor를 String으로 노출하는 API 설계 — Color 타입으로의 전환 고려현재는 서버 응답 연동을 위해 String을 받지만, UI 컴포저블의 관점에선 Color로 받는 편이 테스트/재사용성에 유리합니다. 선택지:
- 최소 변경: 현 상태 유지 + 호출부에서만 String을 전달
- 권장(선택): GroupRoomHeader가 Color를 받고, 호출부에서 hexToColor 변환 수행
가능한 변경 예시(해당 범위 내 차이만 표기):
- categoryColor: String = "#A0F8E8", + categoryColor: Color = Color(0xFFA0F8E8),그리고 Line 176의 사용부도 다음과 같이 단순화됩니다:
- color = hexToColor(categoryColor) + color = categoryColor참고: 이 경우 GroupRoomScreen 등 호출부에서
hexToColor(roomDetails.categoryColor)로 변환해 전달해야 합니다.app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt (2)
70-74: 문자열 결합 대신 다국어 포맷 문자열 사용 권장"@작성자 " 형태를 문자열 리소스에 플레이스홀더로 정의해 결합하세요. 현재처럼 여러 stringResource를 더하는 방식은 지역화/공백 처리에서 리스크가 있습니다.
예시(해당 범위 내 차이만 표기 — 새로운 문자열 리소스 추가 필요):
- append( - stringResource(R.string.annotation) + data.parentCommentCreatorNickname + stringResource( - R.string.space_bar - ) - ) + append(stringResource(R.string.reply_annotation_format, data.parentCommentCreatorNickname))리소스 예:
<!-- values/strings.xml --> <string name="reply_annotation_format">@%1$s </string>필요하다면 strings.xml 추가/변경 패치도 도와드리겠습니다.
90-93: 좋아요 영역 접근성 보완: 역할/설명 추가Column에 clickable만 적용하면 접근성 서비스에서 버튼으로 인식하지 못할 수 있습니다. Role.Button과 contentDescription을 부여하세요.
예시(해당 범위 내 차이만 표기 — semantics import 필요):
- Column( - modifier = Modifier.clickable(onClick = onLikeClick), + Column( + modifier = Modifier + .semantics { contentDescription = stringResource(R.string.like) } + .clickable(role = Role.Button, onClick = onLikeClick),필요 import:
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.Roleapp/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (1)
487-495: 전송 문자열 트리밍 및 공백 방지UI에서 isNotBlank 체크만 하고 있어 공백이 섞인 입력이 그대로 전달됩니다. 전송 전 trim을 적용해 의도치 않은 공백 전송을 막는 편이 안전합니다.
아래와 같이 개선을 제안합니다.
- onSendReply = { text, parentId, _ -> - if (text.isNotBlank()) { + onSendReply = { text, parentId, _ -> + val trimmed = text.trim() + if (trimmed.isNotEmpty()) { commentsViewModel.onEvent( CommentsEvent.CreateComment( - content = text, + content = trimmed, parentId = parentId ) ) } }app/src/main/java/com/texthip/thip/data/service/CommentsService.kt (2)
23-28: 파라미터 명명 일관성: request로 통일likeComment의 @Body 파라미터명이 response로 되어 있어 createComment와 일관성이 떨어집니다. request로 맞춰 주세요.
- suspend fun likeComment( - @Path("commentId") commentId: Long, - @Body response: CommentsLikesRequest - ): BaseResponse<CommentsLikesResponse> + suspend fun likeComment( + @Path("commentId") commentId: Long, + @Body request: CommentsLikesRequest + ): BaseResponse<CommentsLikesResponse>
15-22: postType의 문자열 하드코딩을 타입 세이프하게 전환 제안"RECORD"/"VOTE" 문자열 오탈자로 인한 런타임 오류를 방지하기 위해 enum 또는 @stringdef 기반의 타입을 사용하면 안전합니다. Retrofit 직렬화는 @SerializedName 등으로 매핑 가능합니다.
원하시면 PostType enum과 어댑터(또는 직렬화 어노테이션)까지 포함한 변경안을 드리겠습니다.
Also applies to: 29-34
app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt (1)
26-34: Service 파라미터명 변경에 맞춰 인자명 수정 필요Service의 likeComment @Body 파라미터명을 request로 정리했다면, 여기서도 동일하게 맞춰 주세요.
- commentsService.likeComment( - commentId = commentId, - response = CommentsLikesRequest(type) - ).handleBaseResponse().getOrThrow() + commentsService.likeComment( + commentId = commentId, + request = CommentsLikesRequest(type) + ).handleBaseResponse().getOrThrow()추가로, 가독성을 위해 Repository 메서드 시그니처의 type: Boolean을 isLike: Boolean으로 변경하는 것도 권장합니다.
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt (1)
20-23: 사용되지 않는 onSendReply 파라미터 제거 제안CommentSection 내부에서 onSendReply를 사용하지 않습니다. API 표면을 줄여 혼동을 방지하는 편이 좋습니다. 미사용이라면 제거를 권장합니다.
- onSendReply: (String, Int?, String?) -> Unit = { _, _, _ -> }, onReplyClick: (commentId: Int, nickname: String) -> Unit, onEvent: (CommentsEvent) -> Unit = { _ -> }Preview에서도 해당 인자를 제거합니다.
- CommentSection( - commentItem = + CommentSection( + commentItem = ... - onSendReply = { _, _, _ -> }, onReplyClick = { commentId, nickname -> // Handle reply click } )Also applies to: 79-83
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt (1)
98-111: Preview 더미 데이터의 가독성 개선 소소한 제안aliasName= 등 일부 할당에 공백이 빠져 있어(코드 스타일) 일관성을 위해 정렬/공백을 맞추면 가독성이 향상됩니다. 기능에는 영향 없습니다.
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (3)
60-78: 댓글 작성 시 로딩 상태 표시 추가 권장댓글 작성 중 사용자가 중복 작성을 시도하거나 작성 진행 상황을 알 수 없는 문제가 있습니다. 작성 중 로딩 상태를 추가하여 UX를 개선하는 것이 좋겠습니다.
UiState에
isCreatingComment필드를 추가하고:data class CommentsUiState( val isLoading: Boolean = false, val isLoadingMore: Boolean = false, + val isCreatingComment: Boolean = false, val error: String? = null, val isLast: Boolean = false, val comments: List<CommentList> = emptyList() )createComment 메서드를 다음과 같이 수정:
private fun createComment(content: String, parentId: Int?) { if (content.isBlank()) return + if (_uiState.value.isCreatingComment) return viewModelScope.launch { val isReply = parentId != null + _uiState.update { it.copy(isCreatingComment = true) } commentsRepository.createComment( postId = currentPostId, content = content, isReplyRequest = isReply, parentId = parentId, postType = currentPostType ).onSuccess { + _uiState.update { it.copy(isCreatingComment = false) } fetchComments(isRefresh = true) }.onFailure { throwable -> - _uiState.update { it.copy(error = "댓글 작성 실패: ${throwable.message}") } + _uiState.update { it.copy(isCreatingComment = false, error = "댓글 작성 실패: ${throwable.message}") } } } }
80-105: 좋아요 처리 시 에러 메시지 표시 필요좋아요 실패 시 UI를 원상복구하지만 사용자에게 실패 이유를 알려주지 않습니다. 에러 메시지를 표시하면 더 나은 UX를 제공할 수 있습니다.
viewModelScope.launch { commentsRepository.likeComment(commentId.toLong(), !currentIsLiked) - .onFailure { + .onFailure { throwable -> _uiState.update { val originalComments = it.comments.toMutableList() originalComments[commentIndex] = comment - it.copy(comments = originalComments) + it.copy( + comments = originalComments, + error = "좋아요 처리 실패: ${throwable.message}" + ) } } }
107-140: 대댓글 좋아요 처리에도 에러 메시지 표시 필요
toggleCommentLike와 동일하게 대댓글 좋아요 실패 시에도 사용자에게 에러를 알려주는 것이 일관성 있습니다.viewModelScope.launch { commentsRepository.likeComment(replyId.toLong(), !currentIsLiked) - .onFailure { + .onFailure { throwable -> _uiState.update { val originalComments = it.comments.toMutableList() originalComments[parentCommentIndex] = parentComment - it.copy(comments = originalComments) + it.copy( + comments = originalComments, + error = "좋아요 처리 실패: ${throwable.message}" + ) } } }app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt (3)
132-145: 무한 스크롤 트리거 조건 최적화 필요현재 마지막 아이템이 보일 때 추가 로드를 트리거하는데, 사용자 경험을 개선하기 위해 마지막에서 2-3개 아이템 전에 미리 로드를 시작하는 것이 좋습니다.
val isScrolledToEnd by remember { derivedStateOf { val layoutInfo = lazyListState.layoutInfo if (layoutInfo.totalItemsCount == 0) return@derivedStateOf false val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0 - lastVisibleItemIndex >= layoutInfo.totalItemsCount - 1 + // 마지막에서 3개 아이템 전에 미리 로드 시작 + lastVisibleItemIndex >= layoutInfo.totalItemsCount - 3 } }
147-172: LazyColumn 성능 최적화 가능댓글이 많을 경우 성능 최적화를 위해
contentType파라미터를 추가하는 것이 좋습니다.LazyColumn(state = lazyListState) { items( items = commentList, - key = { it.commentId } + key = { it.commentId }, + contentType = { "comment" } ) { comment -> CommentSection( commentItem = comment, onReplyClick = onReplyClick, onEvent = onEvent ) } if (isLoadingMore) { - item { + item( + key = "loading", + contentType = "loading" + ) { Column(
204-229: Preview 함수에서 onEvent 처리 누락Preview에서
onEvent = {}로 빈 람다를 전달하고 있어 실제 이벤트 처리를 테스트할 수 없습니다. 디버깅을 위해 로그를 추가하면 도움이 됩니다.- onEvent = {}, + onEvent = { event -> + println("Event triggered: $event") + },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (27)
app/src/main/AndroidManifest.xml(1 hunks)app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt(2 hunks)app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsLikesRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsCreateResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsLikesResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPlayingResponse.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/repository/CommentsRepository.kt(1 hunks)app/src/main/java/com/texthip/thip/data/service/CommentsService.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/common/header/ProfileBar.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomHeader.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt(3 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(2 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt(5 hunks)app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt(1 hunks)app/src/main/java/com/texthip/thip/utils/rooms/AdvancedImePadding.kt(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomHeader.kt (1)
app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt (1)
hexToColor(6-13)
app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt (1)
app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt (1)
hexToColor(6-13)
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt (1)
app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt (1)
hexToColor(6-13)
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt (2)
app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt (1)
ProfileBarFeed(27-80)app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt (1)
hexToColor(6-13)
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt (2)
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (1)
onEvent(48-58)app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt (1)
ReplyItem(28-109)
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt (3)
app/src/main/java/com/texthip/thip/ui/common/bottomsheet/CustomBottomSheet.kt (1)
CustomBottomSheet(44-129)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (1)
onEvent(48-58)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt (1)
CommentSection(17-55)
🔇 Additional comments (25)
app/src/main/java/com/texthip/thip/ui/common/header/ProfileBar.kt (1)
35-35: 의미적 변화 없음(LGTM)기능적 변화가 없는 주석 제거 수준의 변경으로 보입니다. 기본값(colors.NeonGreen) 유지 문제 없습니다.
app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt (1)
28-34: Public API 시그니처 변경:profileImage: String?호출부 모두 확인 완료
rg검색 결과,ProfileBarFeed(호출부는 아래 세 곳으로 모두 파악되며 문자열 기반으로 이미 업데이트되어 누락된 부분이 없습니다.
- app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt
- app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt
- app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt (Preview)
따라서
Painter? → String?변경에 따른 추가 수정 사항은 없습니다.app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsResponse.kt (1)
28-41: ReplyList.commentId 의미가 모호합니다 — 식별자/관계 필드 확인 필요
ReplyList의commentId가 “대댓글 자체의 식별자(replyId)”인지, “부모 댓글의 식별자(parentCommentId)”인지 모호합니다. 삭제/신고/좋아요 등 액션에서 혼선이 생길 수 있습니다.권장:
- 서버 스키마 확인 후
- 대댓글 고유 식별자라면
replyId로 명시적 네이밍- 부모 댓글 참조라면
parentCommentId필드 별도 추가- 기존
commentId유지가 필요하다면 KDoc/주석으로 의미를 명확히 기록예(서버가 replyId를 내려주는 경우):
@Serializable data class ReplyList( - val commentId: Int, + val replyId: Int, val parentCommentCreatorNickname: String, val creatorId: Int, @@ )app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomMatesScreen.kt (1)
107-116: 프리뷰 데이터 갱신 LGTM
aliasColor프리뷰 반영으로 실제 UI 색상 바인딩을 확인할 수 있어 유용합니다. 본문GroupRoomMatesList에서도 정상 소비되고 있어 일관성 있습니다.app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (5)
208-219: roomId를 라우트 파라미터로 전달하도록 변경 — 적절합니다GroupRoomScreen에 roomId를 동적으로 전달하고, 이후 하위 네비게이션에서도 동일 roomId를 캡처해 사용하는 흐름이 명확합니다.
227-235: Mates 화면으로의 네비게이션에 roomId 전달 — 일관성 좋습니다GroupRoomMatesScreen에도 동일한 roomId를 전달하도록 정리되어 전체 내비게이션 컨텍스트가 일관됩니다.
250-277: 노트 화면에 roomId 전달 — ViewModel 스코프와 함께 올바른 사용hiltViewModel(backStackEntry)와 함께 roomId를 전달하여 화면/상태 스코프가 안정적으로 유지됩니다.
285-299: 노트 생성 화면에 roomId 전달 — 후속 결과 전달(savedStateHandle)과 잘 결합생성 후 탭 인덱스 결과 반영 로직과 충돌 없이 동작할 것으로 보입니다.
306-319: 투표 생성 화면에 roomId 전달 — 라우트 파라미터화 완료투표 생성 흐름에도 동일하게 적용되어 내비게이션 파라미터 관리가 통일되었습니다.
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomHeader.kt (2)
29-29: 서버 hex 문자열 → Color 변환 도입 — 유효하지 않은 값에 대한 폴백까지 OKhexToColor를 통해 서버에서 내려오는 hex 문자열을 안전하게 Color로 변환하고(유효하지 않으면 White), UI 안정성을 높였습니다.
174-177: 텍스트 색상에 서버 제공 카테고리 컬러 적용 — 일관성 있는 렌더링카테고리 텍스트에만 색을 적용해 가독성을 해치지 않으면서 강조 효과를 주고 있습니다. 폴백 Color.White 처리 덕분에 서버 값 이상에도 안전합니다.
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt (2)
165-167: Header에 categoryColor 전달 — 모델-UI 매핑이 깔끔합니다RoomsPlayingResponse.categoryColor → GroupRoomHeader.categoryColor 전달로 데이터-프레젠테이션 간 결합이 명확해졌습니다. hexToColor 쪽 폴백이 있어 안전합니다.
286-286: 프리뷰에 categoryColor 예시 추가 — 디자인 확인에 유용합니다샘플 hex 값을 포함하여 프리뷰에서 컬러 렌더링을 빠르게 검증할 수 있습니다.
app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt (4)
31-34: 상태 외부화: onReplyClick·onLikeClick 콜백과 isLike 바인딩 — 방향성 좋습니다내부 상태를 제거하고 data.isLike/likeCount로 단방향 바인딩하며, 액션을 콜백으로 위임한 설계는 테스트/상태관리 측면에서 바람직합니다.
49-54: ProfileBarFeed 바인딩 정합성 확인이미지 URL, 닉네임, 칭호/색상, 날짜가 새 데이터 모델(ReplyList)에 맞게 정상 매핑되었습니다. aliasColor는 hexToColor 변환으로 안전 처리됩니다.
95-98: isLike에 따른 아이콘 토글 렌더링 — 의도대로 동작filled/outlined 리소스 전환으로 상태 표현이 명확합니다. 서버 반영 후 UI 업데이트만 보장되면 UX도 자연스럽게 이어집니다.
122-134: 프리뷰 데이터가 실제 모델과 동기화되어 있음ReplyList 기반의 더미 데이터가 컴포넌트 기대 형태를 잘 보여줍니다. 디자인 확인에 유용합니다.
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (4)
59-61: 새 코멘트 아키텍처로의 의존성 정리 좋습니다CommentsEvent/CommentsViewModel 도입 및 import 추가가 변경된 흐름(이벤트 기반)과 일관됩니다.
366-369: 댓글 시트 오픈 처리 OK게시물 선택과 바텀시트 표시를 함께 처리해 UX 흐름이 명확합니다.
Also applies to: 380-383
473-479: 초기화 타이밍 적절하나, 상태 초기화(or 중복 로딩 방지) 여부 확인 필요선택된 게시물 변경 시 initialize를 트리거하는 구조는 맞습니다. 다만 ViewModel이 기존 목록을 잠깐 보여주는 플리커가 없도록 초기화(로딩 상태 전환, 이전 댓글 캐시 클리어 등)가 이 이벤트에서 보장되는지 확인이 필요합니다. 빠르게 게시물 전환 시 중복 네트워크 요청이 겹치지 않도록 취소 처리도 고려해 주세요.
원하시면 CommentsViewModel에 초기화/취소 로직(예: 기존 Job cancel, UI 상태 reset)을 위한 보일러플레이트를 제안드릴 수 있습니다.
481-483: BottomSheet에 상태/이벤트 주입 방식 적절합니다단방향 데이터 흐름을 유지하면서 UI-VM 경계를 명확히 했습니다.
app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt (1)
14-24: 런타임 예외를 Result로 감싸는 패턴 적절합니다handleBaseResponse().getOrThrow()를 runCatching으로 감싼 구조로, 호출부에서 성공/실패를 일관되게 다룰 수 있습니다.
Also applies to: 36-52
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt (1)
33-36: 이벤트 라우팅과 식별자 전달이 명확합니다
- 최상위 댓글 좋아요: CommentsEvent.LikeComment(commentId)
- 대댓글 좋아요: CommentsEvent.LikeReply(parentCommentId, replyId)
- 대댓글 쓰기: (parent commentId, nickname) 전달
이벤트 기반 상호작용이 깔끔하게 정리되어 있습니다.
Also applies to: 38-49
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt (1)
37-43: 데이터 바인딩과 상호작용 연결 상태 양호
- 프로필/별칭/색상/날짜/내용 바인딩이 새로운 CommentList 모델과 일치합니다.
- 좋아요 아이콘은 data.isLike에 맞춰 토글되고, 클릭 시 외부 onLikeClick을 호출합니다.
- 답글 쓰기 클릭은 작성자 닉네임을 콜백으로 전달해 상위에서 parentId/mention 관리가 용이합니다.
Also applies to: 59-63, 66-75
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (1)
142-177: 무한 스크롤 동시 요청 방지 로직 적절
기존 구현에서 아래 사항이 모두 만족되어 추가 검증이 불필요합니다:
isLoading/isLoadingMore플래그로 빠른 스크롤 시 중복 요청이 차단됩니다.- 네트워크 오류(
onFailure) 발생 시nextCursor를 그대로 유지해 올바른 페이지 재시도가 가능합니다.- 에러 시에도
_uiState.comments가 초기화되지 않아 캐시된 데이터와의 동기화가 유지됩니다.위 로직으로 보아 현재 구현은 edge case를 올바르게 처리하고 있어, 더 이상의 수정은 필요하지 않습니다.
| val content: String, | ||
| val isReplyRequest: Boolean, | ||
| val parentId: Int? = null, | ||
| val postType: String, | ||
| ) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
타입 일관성 및 무결성 보강(빈 콘텐츠/답글 규칙 검증)
- parentId는 댓글 ID이므로 Long?으로 일관화 권장
- 잘못된 조합 전송 방지를 위해 기본 무결성 검증을 추가(빈 content 금지, isReplyRequest에 따른 parentId 존재성)
아래 변경 제안은 컴파일 타임에 깨지는 부분이 없는지 전체 빌드 확인이 필요합니다.
@Serializable
data class CommentsCreateRequest(
val content: String,
val isReplyRequest: Boolean,
- val parentId: Int? = null,
+ val parentId: Long? = null,
val postType: String,
-)
+) {
+ init {
+ require(content.isNotBlank()) { "content must not be blank" }
+ if (isReplyRequest) {
+ require(parentId != null) { "parentId is required when isReplyRequest = true" }
+ } else {
+ require(parentId == null) { "parentId must be null when isReplyRequest = false" }
+ }
+ }
+}무결성 체크를 모델에 두는 것이 부담이라면 Repository 레이어에서 요청 생성 전에 동일한 검증을 수행하는 것도 괜찮습니다.
🏁 Script executed:
#!/bin/bash
set -e
echo "🔍 Searching for usages of CommentsCreateRequest in the codebase"
rg "CommentsCreateRequest" -n .
echo "🔍 Searching for all occurrences of parentId to see how it’s consumed"
rg "parentId" -n .Length of output: 2313
🏁 Script executed:
#!/bin/bash
set -e
echo "🔍 CommentsService.kt 상단 확인"
sed -n '1,200p' app/src/main/java/com/texthip/thip/data/service/CommentsService.kt
echo
echo "🔍 CommentsRepository.kt 주요 메서드 시그니처 확인"
sed -n '1,200p' app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.ktLength of output: 3197
CommentsCreateRequest의 parentId 타입 일관화 및 무결성 검증 추가 필요
parentId가 댓글 ID이므로 Int→Long으로 변경 시, 요청 모델뿐 아니라 호출부 시그니처도 함께 수정해야 컴파일 오류를 방지할 수 있습니다. 또한 빈 content 전송과 isReplyRequest/parentId 조합을 런타임에 검증하도록 init 블록을 추가하세요.
수정이 필요한 위치:
- app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt
- app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt (
createComment메서드의 parentId: Int? → Long?) - app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (CreateComment 이벤트와 createComment 함수 파라미터)
- app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (
onSendReply콜백의 parentId 타입)
예시 변경안 (CommentsCreateRequest.kt):
@Serializable
data class CommentsCreateRequest(
val content: String,
val isReplyRequest: Boolean,
- val parentId: Int? = null,
+ val parentId: Long? = null,
val postType: String,
) {
init {
require(content.isNotBlank()) { "content must not be blank" }
if (isReplyRequest) {
require(parentId != null) { "parentId is required when isReplyRequest = true" }
} else {
require(parentId == null) { "parentId must be null when isReplyRequest = false" }
}
}
}변경 후 전체 빌드 및 테스트를 반드시 확인해주세요.
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt
around lines 7-11, change parentId type from Int? to Long?, add an init block
that validates content is not blank and enforces consistency between
isReplyRequest and parentId (if isReplyRequest==true then parentId must be
non-null, if false then parentId must be null) and throw
IllegalArgumentException on violation; then update all call sites' signatures to
use Long? — specifically update
app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt
(createComment parentId: Int? → Long?),
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt
(CreateComment event and createComment function parameter types), and
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt
(onSendReply callback parentId type) so types match, rebuild and run tests to
ensure no compilation errors.
|
|
||
| @Serializable | ||
| data class CommentsCreateResponse( | ||
| val commentId: Int, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
ID 타입 일관성: Int → Long으로 변경 권장
댓글·게시글 ID 전반을 Long으로 일관되게 사용하는 편이 안전합니다(특히 Service/Repository에서 Long 사용 중). 응답 commentId도 Long으로 맞추는 것을 제안드립니다.
data class CommentsCreateResponse(
- val commentId: Int,
+ val commentId: 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.
| val commentId: Int, | |
| data class CommentsCreateResponse( | |
| val commentId: Long, | |
| ) |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsCreateResponse.kt
around line 7, the commentId is declared as Int but your codebase uses Long for
IDs; change the type to Long to keep ID type consistent, update any
constructor/serialization annotations if needed, and adjust mapping code that
builds CommentsCreateResponse so it assigns the Long id from the entity/service
rather than converting to Int; also update any unit tests or callers expecting
Int to use Long.
|
|
||
| @Serializable | ||
| data class CommentsLikesResponse( | ||
| val commentId: Int, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
ID 타입 일관성: Int → Long으로 변경 권장
Service 레이어에서 path param이 Long(예: likeComment(commentId: Long))으로 사용되고 있어 응답 모델의 commentId도 Long으로 맞추는 편이 안전합니다. 타입 불일치가 있으면 매핑·캐스팅 비용과 잠재적 오버플로/트렁케이션 이슈가 생길 수 있습니다.
다음과 같이 변경을 제안합니다:
data class CommentsLikesResponse(
- val commentId: Int,
+ val commentId: Long,
val isLiked: 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.
| val commentId: Int, | |
| data class CommentsLikesResponse( | |
| val commentId: Long, | |
| val isLiked: Boolean, | |
| ) |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsLikesResponse.kt
around line 7, the commentId field is declared as Int but the service layer
expects Long (e.g., likeComment(commentId: Long)); change the commentId type
from Int to Long to ensure type consistency. Update any related constructors,
data class properties, serialization/deserialization annotations,
equals/hashCode or copy usages, and adjust unit tests or mapping code that
reference this field to use Long so all layers align.
| data class CommentsResponse( | ||
| val commentList: List<CommentList>, | ||
| val nextCursor: String?, | ||
| val isLast: Boolean, | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
디코딩 탄탄함 확보: 기본값으로 방어적 직렬화
네트워크 응답에서 일부 필드가 누락될 수 있는 상황(서버 진화, 실험 플래그 등)을 고려해 commentList, nextCursor에 기본값을 두면 파싱 실패를 줄일 수 있습니다.
@Serializable
data class CommentsResponse(
- val commentList: List<CommentList>,
- val nextCursor: String?,
+ val commentList: List<CommentList> = emptyList(),
+ val nextCursor: String? = null,
val isLast: Boolean,
)🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsResponse.kt
around lines 6 to 10, the data class lacks defensive defaults so missing fields
in network responses can cause parsing failures; update the primary constructor
to give commentList a default emptyList(), nextCursor a default null (even if
nullable), and isLast a sensible default (e.g., false) so deserialization
succeeds when those fields are absent.
| @Serializable | ||
| data class CommentList( | ||
| val commentId: Int, | ||
| val creatorId: Int, | ||
| val creatorProfileImageUrl: String, | ||
| val creatorNickname: String, | ||
| val aliasName: String, | ||
| val aliasColor: String, | ||
| val postDate: String, | ||
| val content: String, | ||
| val likeCount: Int, | ||
| val isDeleted: Boolean, | ||
| val isLike: Boolean, | ||
| val replyList: List<ReplyList>, | ||
| ) | ||
|
|
||
| @Serializable | ||
| data class ReplyList( | ||
| val commentId: Int, | ||
| val parentCommentCreatorNickname: String, | ||
| val creatorId: Int, | ||
| val creatorProfileImageUrl: String, | ||
| val creatorNickname: String, | ||
| val aliasName: String, | ||
| val aliasColor: String, | ||
| val postDate: String, | ||
| val content: String, | ||
| val likeCount: Int, | ||
| val isLike: Boolean, | ||
| ) No newline at end of file |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
서버 키 네이밍(camelCase/snake_case) 및 직렬화 설정 점검
프로퍼티들이 camelCase입니다. 서버가 snake_case로 응답한다면 @SerialName 매핑 또는 Retrofit의 Kotlinx Serialization 설정이 필요합니다.
다음 스크립트로 프로젝트 내 직렬화 설정과 @SerialName 사용 여부를 점검해 주세요. 예상:
- Kotlinx Json 설정에서
ignoreUnknownKeys = true등 옵션 확인 - Retrofit에
asConverterFactory로 Kotlinx Serialization 연결 여부 확인 - 기존 모델들에서
@SerialName사용 패턴 확인
🏁 Script executed:
#!/bin/bash
# 직렬화 설정/사용 패턴 점검
echo "[Json 설정 탐색]"
rg -n "Json\\(" -A 5
echo
echo "[Retrofit + Kotlinx Serialization 연결(asConverterFactory) 탐색]"
rg -n "asConverterFactory|kotlinx.serialization" -A 3
echo
echo "[@SerialName 사용 사례 탐색]"
rg -n "@SerialName\\(" -A 1
echo
echo "[CommentsService/Response 사용처 탐색]"
rg -n "CommentsService|CommentsResponse|CommentList|ReplyList" -A 3Length of output: 63158
서버 응답 필드 네이밍 일관성 검토 및 직렬화 설정 보완 필요
현재 NetworkModule.provideJson() 에는
• isLenient, prettyPrint, encodeDefaults, explicitNulls 만 설정되어 있으며
• ignoreUnknownKeys 및 snake_case ↔ camelCase 자동 변환(namingStrategy)이 빠져 있습니다.
또한 CommentsResponse.kt 의 CommentList/ReplyList 에는 @SerialName 어노테이션이 없어,
서버가 snake_case 로 응답할 경우 필드 매핑 에러가 발생할 수 있습니다.
다음 중 한 가지 방법으로 수정해주세요.
- NetworkModule.provideJson() 에
ignoreUnknownKeys = true및
json { …; namingStrategy = JsonNamingStrategy.SnakeCase }추가 CommentList/ReplyList각 프로퍼티에@SerialName("snake_case_name")어노테이션 부여
Retrofit-Kotlinx-Serialization 연결(asConverterFactory)은 정상 확인됐습니다.
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsResponse.kt
around lines 12 to 41, the data classes lack explicit serialization mapping for
snake_case server responses; either enable snake_case handling globally or
annotate fields—Preferred fix: update NetworkModule.provideJson() to set
ignoreUnknownKeys = true and configure namingStrategy =
JsonNamingStrategy.SnakeCase so server snake_case names map to camelCase
properties automatically; alternative fix: add @SerialName("snake_case_name") to
each property in CommentList and ReplyList to match the server field names.
| fun CommentBottomSheet( | ||
| commentResponse: List<CommentItem>, | ||
| uiState: CommentsUiState, | ||
| onEvent: (CommentsEvent) -> Unit, | ||
| onDismiss: () -> Unit, | ||
| onSendReply: (String, Int?, String?) -> Unit | ||
| onSendReply: (text: String, parentCommentId: Int?, replyToNickname: String?) -> Unit | ||
| ) { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
onSendReply 콜백이 CommentsEvent.CreateComment와 중복
onSendReply 파라미터와 CommentsEvent.CreateComment가 동일한 목적을 가지고 있어 중복됩니다. 일관성을 위해 onEvent를 통해 처리하는 것이 좋습니다.
fun CommentBottomSheet(
uiState: CommentsUiState,
onEvent: (CommentsEvent) -> Unit,
- onDismiss: () -> Unit,
- onSendReply: (text: String, parentCommentId: Int?, replyToNickname: String?) -> Unit
+ onDismiss: () -> Unit
) {그리고 Line 101-110 부분을 다음과 같이 수정:
onSendClick = {
- onSendReply(
- inputText,
- replyingToCommentId,
- replyingToNickname
- )
+ onEvent(CommentsEvent.CreateComment(
+ content = inputText,
+ parentId = replyingToCommentId
+ ))
inputText = ""
replyingToCommentId = null
replyingToNickname = null
},📝 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 CommentBottomSheet( | |
| commentResponse: List<CommentItem>, | |
| uiState: CommentsUiState, | |
| onEvent: (CommentsEvent) -> Unit, | |
| onDismiss: () -> Unit, | |
| onSendReply: (String, Int?, String?) -> Unit | |
| onSendReply: (text: String, parentCommentId: Int?, replyToNickname: String?) -> Unit | |
| ) { | |
| // Updated function signature (remove onSendReply) | |
| fun CommentBottomSheet( | |
| uiState: CommentsUiState, | |
| onEvent: (CommentsEvent) -> Unit, | |
| onDismiss: () -> Unit | |
| ) { |
| fun CommentBottomSheet( | |
| commentResponse: List<CommentItem>, | |
| uiState: CommentsUiState, | |
| onEvent: (CommentsEvent) -> Unit, | |
| onDismiss: () -> Unit, | |
| onSendReply: (String, Int?, String?) -> Unit | |
| onSendReply: (text: String, parentCommentId: Int?, replyToNickname: String?) -> Unit | |
| ) { | |
| // Updated send-click handler to use onEvent instead of onSendReply | |
| onSendClick = { | |
| onEvent(CommentsEvent.CreateComment( | |
| content = inputText, | |
| parentId = replyingToCommentId | |
| )) | |
| inputText = "" | |
| replyingToCommentId = null | |
| replyingToNickname = null | |
| }, |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt
around lines 40 to 45, the onSendReply parameter duplicates the purpose of
CommentsEvent.CreateComment and should be removed; change the function signature
to drop onSendReply and inside the sheet where replies are sent (around lines
101-110) dispatch a CommentsEvent.CreateComment via onEvent with the text,
parentCommentId and replyToNickname, and update any callers of
CommentBottomSheet to no longer pass onSendReply and instead rely on onEvent for
creating comments.
| Box(modifier = Modifier.weight(1f)) { | ||
| if (uiState.isLoading) { | ||
| Column( | ||
| modifier = Modifier.fillMaxSize(), | ||
| verticalArrangement = Arrangement.Center, | ||
| horizontalAlignment = Alignment.CenterHorizontally | ||
| ) { | ||
| CircularProgressIndicator() | ||
| } | ||
| } else if (uiState.comments.isEmpty()) { | ||
| EmptyCommentView() | ||
| } else { | ||
| CommentLazyList( | ||
| commentList = uiState.comments, | ||
| isLoadingMore = uiState.isLoadingMore, | ||
| isLastPage = uiState.isLast, | ||
| onLoadMore = { onEvent(CommentsEvent.LoadMoreComments) }, | ||
| onReplyClick = { commentId, nickname -> | ||
| replyingToCommentId = commentId | ||
| replyingToNickname = nickname | ||
| }, | ||
| onEvent = onEvent | ||
| ) | ||
| } | ||
| } else { | ||
| CommentList( | ||
| commentList = commentResponse, | ||
| onSendReply = { replyText, commentId, replyTo -> | ||
| onSendReply(replyText, commentId, replyTo) | ||
| inputText = "" | ||
| }, | ||
| onReplyClick = { replyItem -> | ||
| replyingTo = replyItem | ||
| } | ||
| ) | ||
| } |
There was a problem hiding this comment.
에러 상태 처리 누락
CommentsUiState에 error 필드가 있지만 UI에서 에러를 표시하지 않고 있습니다. 사용자에게 에러 상황을 알려주는 것이 중요합니다.
Box(modifier = Modifier.weight(1f)) {
if (uiState.isLoading) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator()
}
+ } else if (uiState.error != null) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = uiState.error,
+ style = typography.copy_r400_s14,
+ color = colors.Red,
+ modifier = Modifier.padding(horizontal = 20.dp)
+ )
+ }
} else if (uiState.comments.isEmpty()) {
EmptyCommentView()
} else {📝 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.
| Box(modifier = Modifier.weight(1f)) { | |
| if (uiState.isLoading) { | |
| Column( | |
| modifier = Modifier.fillMaxSize(), | |
| verticalArrangement = Arrangement.Center, | |
| horizontalAlignment = Alignment.CenterHorizontally | |
| ) { | |
| CircularProgressIndicator() | |
| } | |
| } else if (uiState.comments.isEmpty()) { | |
| EmptyCommentView() | |
| } else { | |
| CommentLazyList( | |
| commentList = uiState.comments, | |
| isLoadingMore = uiState.isLoadingMore, | |
| isLastPage = uiState.isLast, | |
| onLoadMore = { onEvent(CommentsEvent.LoadMoreComments) }, | |
| onReplyClick = { commentId, nickname -> | |
| replyingToCommentId = commentId | |
| replyingToNickname = nickname | |
| }, | |
| onEvent = onEvent | |
| ) | |
| } | |
| } else { | |
| CommentList( | |
| commentList = commentResponse, | |
| onSendReply = { replyText, commentId, replyTo -> | |
| onSendReply(replyText, commentId, replyTo) | |
| inputText = "" | |
| }, | |
| onReplyClick = { replyItem -> | |
| replyingTo = replyItem | |
| } | |
| ) | |
| } | |
| Box(modifier = Modifier.weight(1f)) { | |
| if (uiState.isLoading) { | |
| Column( | |
| modifier = Modifier.fillMaxSize(), | |
| verticalArrangement = Arrangement.Center, | |
| horizontalAlignment = Alignment.CenterHorizontally | |
| ) { | |
| CircularProgressIndicator() | |
| } | |
| } else if (uiState.error != null) { | |
| Column( | |
| modifier = Modifier.fillMaxSize(), | |
| verticalArrangement = Arrangement.Center, | |
| horizontalAlignment = Alignment.CenterHorizontally | |
| ) { | |
| Text( | |
| text = uiState.error, | |
| style = typography.copy_r400_s14, | |
| color = colors.Red, | |
| modifier = Modifier.padding(horizontal = 20.dp) | |
| ) | |
| } | |
| } else if (uiState.comments.isEmpty()) { | |
| EmptyCommentView() | |
| } else { | |
| CommentLazyList( | |
| commentList = uiState.comments, | |
| isLoadingMore = uiState.isLoadingMore, | |
| isLastPage = uiState.isLast, | |
| onLoadMore = { onEvent(CommentsEvent.LoadMoreComments) }, | |
| onReplyClick = { commentId, nickname -> | |
| replyingToCommentId = commentId | |
| replyingToNickname = nickname | |
| }, | |
| onEvent = onEvent | |
| ) | |
| } | |
| } |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt
around lines 69-93, the UI ignores CommentsUiState.error so users never see
failures; add an explicit check for uiState.error inside the Box and render an
error UI (e.g., a small ErrorView/Text with the error message and a retry
button) before other branches (so error supersedes loading/empty/list), and wire
the retry button to call back into onEvent with an appropriate retry/refresh
event (or expose a retry lambda) so users can retry the load.
| val commentsViewModel: CommentsViewModel = hiltViewModel() | ||
| val commentsUiState by commentsViewModel.uiState.collectAsStateWithLifecycle() | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Preview 크래시 및 과도한 ViewModel 생명주기 범위: hiltViewModel()를 BottomSheet 조건 블록 안으로 이동하세요
현재 GroupNoteContent 내부에서 무조건 hiltViewModel()를 호출하고 있어 Compose Preview에서 Hilt 컨텍스트가 없어 크래시가 발생할 수 있고, 코멘트 시트가 열리지 않았는데도 ViewModel이 생성됩니다. 시트가 열릴 때만 ViewModel을 생성하도록 if 블록 안으로 이동하는 편이 안전합니다.
아래처럼 이동해 주세요.
@@
- val commentsViewModel: CommentsViewModel = hiltViewModel()
- val commentsUiState by commentsViewModel.uiState.collectAsStateWithLifecycle()
@@
- if (isCommentBottomSheetVisible && selectedPostForComment != null) {
+ if (isCommentBottomSheetVisible && selectedPostForComment != null) {
+ val commentsViewModel: CommentsViewModel = hiltViewModel()
+ val commentsUiState by commentsViewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(selectedPostForComment) {
commentsViewModel.initialize(
postId = selectedPostForComment!!.postId.toLong(),
postType = selectedPostForComment!!.postType
)
}
@@
CommentBottomSheet(
uiState = commentsUiState,
onEvent = commentsViewModel::onEvent,| if (currentPostId == postId) return | ||
| this.currentPostId = postId | ||
| this.currentPostType = postType | ||
| fetchComments(isRefresh = true) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
동일한 postId 체크 시 postType 변경도 고려 필요
현재 로직은 postId만 체크하여 중복 초기화를 방지하고 있습니다. 하지만 동일한 postId라도 postType이 변경될 수 있는 경우를 고려해야 합니다.
fun initialize(postId: Long, postType: String) {
- if (currentPostId == postId) return
+ if (currentPostId == postId && currentPostType == postType) return
this.currentPostId = postId
this.currentPostType = postType
fetchComments(isRefresh = true)
}📝 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 (currentPostId == postId) return | |
| this.currentPostId = postId | |
| this.currentPostType = postType | |
| fetchComments(isRefresh = true) | |
| } | |
| fun initialize(postId: Long, postType: String) { | |
| if (currentPostId == postId && currentPostType == postType) return | |
| this.currentPostId = postId | |
| this.currentPostType = postType | |
| fetchComments(isRefresh = true) | |
| } |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt
around lines 42 to 46, the early-return only checks currentPostId and ignores
postType so updates are skipped when postType changes; change the guard to
return only when both currentPostId == postId and currentPostType == postType,
and otherwise assign both this.currentPostId and this.currentPostType and call
fetchComments(isRefresh = true) so postType changes trigger reinitialization.
| fun Modifier.advancedImePadding() = composed { | ||
| var consumePadding by remember { mutableStateOf(0) } | ||
| onGloballyPositioned { coordinates -> | ||
| val rootCoordinate = coordinates.findRootCoordinates() | ||
| val bottom = coordinates.positionInWindow().y + coordinates.size.height | ||
|
|
||
| consumePadding = (rootCoordinate.size.height - bottom).toInt().coerceAtLeast(0) | ||
| } | ||
| .consumeWindowInsets(PaddingValues(bottom = (consumePadding / LocalDensity.current.density).dp)) | ||
| .imePadding() | ||
| } No newline at end of file |
There was a problem hiding this comment.
IME 패딩 계산 로직이 의도대로 동작하지 않음(부호/소비 순서 문제) — 실제로는 패딩 반영되지 않을 가능성 큼
문제점:
- overlap 계산이 반대로 되어, 키보드에 가려지는 경우에도 0으로 클램프되어 추가 패딩이 적용되지 않습니다.
- consumeWindowInsets(PaddingValues(...))를 imePadding() 전에 호출하는 구성은 imePadding에 영향을 주지 못합니다(특정 WindowInsets를 소비해야 하며, 현재는 수동 PaddingValues만 소비하고 있음).
- 계산된 값이 실제 패딩으로 적용되지 않습니다(consumeWindowInsets는 패딩 적용이 아니라 전파 차단 역할).
수정안: 겹침(overlap) 픽셀을 Dp로 변환해 실제 bottom padding으로 적용하고, 그 값을 소비한 뒤 imePadding을 추가하세요.
아래 diff를 적용하세요(해당 범위 내 변경). 추가로 파일 상단에 padding import가 필요합니다.
fun Modifier.advancedImePadding() = composed {
- var consumePadding by remember { mutableStateOf(0) }
- onGloballyPositioned { coordinates ->
- val rootCoordinate = coordinates.findRootCoordinates()
- val bottom = coordinates.positionInWindow().y + coordinates.size.height
-
- consumePadding = (rootCoordinate.size.height - bottom).toInt().coerceAtLeast(0)
- }
- .consumeWindowInsets(PaddingValues(bottom = (consumePadding / LocalDensity.current.density).dp))
- .imePadding()
+ val density = LocalDensity.current
+ var bottomPaddingDp by remember { mutableStateOf(0.dp) }
+ onGloballyPositioned { coordinates ->
+ val root = coordinates.findRootCoordinates()
+ val elementBottom = coordinates.positionInWindow().y + coordinates.size.height
+ val rootBottom = root.positionInWindow().y + root.size.height
+ val overlapPx = (elementBottom - rootBottom).coerceAtLeast(0f)
+ bottomPaddingDp = with(density) { overlapPx.toDp() }
+ }
+ .padding(bottom = bottomPaddingDp)
+ .consumeWindowInsets(PaddingValues(bottom = bottomPaddingDp))
+ .imePadding()
}추가 import(선택사항: 적용 시 필요):
import androidx.compose.foundation.layout.padding대안: 특별한 커스텀 계산이 필요 없다면 단순히 Modifier.windowInsetsPadding(WindowInsets.ime) 또는 현행 imePadding()만으로도 대부분의 케이스를 커버할 수 있습니다.
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/utils/rooms/AdvancedImePadding.kt around
lines 18 to 28, the overlap calculation and consumption order are incorrect so
padding isn't applied; change logic to compute the overlap (root height -
bottom), convert that pixel value to Dp, apply it as bottom padding via
.padding(bottom = overlapDp), then consume the corresponding Insets (so they
won’t be double-handled) before applying .imePadding(); also add import
androidx.compose.foundation.layout.padding at the top. Ensure you clamp overlap
to >= 0, convert using LocalDensity.current.density, apply .padding(bottom =
overlapDp) and then call .consumeWindowInsets(...) appropriately followed by
.imePadding().
There was a problem hiding this comment.
Actionable comments posted: 19
🔭 Outside diff range comments (4)
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt (1)
60-62: LaunchedEffect 키가 Unit 고정이라 roomId 변경 시 재조회되지 않습니다roomId가 바뀌어도 효과가 재실행되지 않아 이전 방 데이터가 그대로 노출될 수 있습니다. 키를 roomId로 변경하세요.
- LaunchedEffect(key1 = Unit) { + LaunchedEffect(key1 = roomId) { viewModel.fetchRoomsPlaying(roomId) }app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt (1)
37-81: 삭제된 댓글(isDeleted) UI 미처리: 내용/액션 숨김 처리 필요서버 모델에 isDeleted가 있는데, 현재 UI는 삭제 상태를 고려하지 않고 본문/답글/좋아요 UI를 그대로 노출합니다. 기본적으로 “삭제된 댓글입니다.” 같은 플레이스홀더를 표시하고 액션(답글/좋아요)을 비활성화하는 것이 안전합니다.
예시 수정안:
ProfileBarFeed( profileImage = data.creatorProfileImageUrl, nickname = data.creatorNickname, genreName = data.aliasName, genreColor = hexToColor(data.aliasColor), date = data.postDate ) - Row( - horizontalArrangement = Arrangement.spacedBy(20.dp) - ) { - Column( - modifier = Modifier - .weight(1f), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = data.content, - color = colors.Grey, - style = typography.feedcopy_r400_s14_h20, - ) - Text( - modifier = Modifier.clickable(onClick = { onReplyClick(data.creatorNickname) }), - text = stringResource(R.string.write_reply), - style = typography.menu_sb600_s12, - color = colors.Grey02, - ) - } - - Column( - modifier = Modifier.clickable(onClick = onLikeClick), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(2.dp), - ) { - Icon( - painter = painterResource(if (data.isLike) R.drawable.ic_heart_center_filled else R.drawable.ic_heart_center), - contentDescription = null, - tint = Color.Unspecified - ) - Text( - text = data.likeCount.toString(), - style = typography.navi_m500_s10, - color = colors.White, - ) - } - } + if (data.isDeleted) { + Text( + text = stringResource(R.string.comment_deleted_placeholder), // 새 리소스 필요 + color = colors.Grey01, + style = typography.feedcopy_r400_s14_h20, + ) + } else { + Row( + horizontalArrangement = Arrangement.spacedBy(20.dp) + ) { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = data.content, + color = colors.Grey, + style = typography.feedcopy_r400_s14_h20, + ) + Text( + modifier = Modifier.clickable { onReplyClick(data.creatorNickname) }, + text = stringResource(R.string.write_reply), + style = typography.menu_sb600_s12, + color = colors.Grey02, + ) + } + Column( + modifier = Modifier.clickable(onClick = onLikeClick), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + Icon( + painter = painterResource(if (data.isLike) R.drawable.ic_heart_center_filled else R.drawable.ic_heart_center), + contentDescription = null, + tint = Color.Unspecified + ) + Text( + text = data.likeCount.toString(), + style = typography.navi_m500_s10, + color = colors.White, + ) + } + } + }참고: stringResource(R.string.comment_deleted_placeholder) 신규 추가가 필요합니다.
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomHeader.kt (1)
139-149: 참여자 아이콘 버튼이 동작하지 않습니다: onNavigateToMates에 연결하세요우측 IconButton onClick이 TODO로 남아 있습니다. 상단 Row 전체가 클릭 가능한 것과 UX가 불일치합니다. onNavigateToMates로 통일하세요.
- IconButton( - onClick = { /* TODO: Navigate to participant list */ }, + IconButton( + onClick = onNavigateToMates, modifier = Modifier.size(24.dp) ) {app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt (1)
26-28: LazyColumn 아이템 내 fillMaxHeight 사용은 과도한 높이 측정 이슈를 유발할 수 있습니다.
CommentSection이LazyColumn의 아이템으로 쓰이는 맥락에서fillMaxHeight()는 각 아이템이 화면 높이를 점유하도록 유도, 스크롤/측정 비효율과 잘림을 야기할 수 있습니다. 제거를 권장합니다.- Column( - modifier = Modifier - .fillMaxHeight() - .padding(20.dp), + Column( + modifier = Modifier + .padding(20.dp), verticalArrangement = Arrangement.spacedBy(20.dp), horizontalAlignment = Alignment.Start ) {
🧹 Nitpick comments (21)
app/src/main/java/com/texthip/thip/ui/common/header/ProfileBar.kt (1)
47-53: 접근성/현지화: contentDescription 하드코딩 문자열 제거 제안"프로필 이미지" 문자열을 string 리소스로 이전해 현지화/일관성을 확보하는 것을 권장합니다.
적용 예시(해당 라인만 변경):
- contentDescription = "프로필 이미지", + contentDescription = stringResource(R.string.profile_image_content_description),strings.xml에 다음 키 추가가 필요합니다:
<string name="profile_image_content_description">프로필 이미지</string>app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsLikesRequest.kt (1)
5-8: 요청 필드 명확화: Boolean 의미가 드러나도록 이름 개선 제안Boolean 필드명이 type은 의미가 모호합니다. Kotlin 측 필드명을 isLike로 명확히 하고, 서버가 "type" 키를 요구한다면 @SerialName으로 매핑하는 방식을 권장합니다. 서버 스키마 변경 없이 가독성만 개선됩니다.
import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName @Serializable data class CommentsLikesRequest( - val type: Boolean + @SerialName("type") + val isLike: Boolean )app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt (1)
41-50: 빈 문자열 처리 및 로딩 품질 개선 제안 (crossfade, Crop, isNullOrBlank)
- URL이 빈 문자열("")인 경우를 함께 방지하는 편이 안전합니다.
- 원형 아바타는 보통 중앙 크롭(Crop)이 자연스럽습니다.
- Coil crossfade로 전환 시 품질을 개선할 수 있습니다.
- Row { - if (profileImage != null) { - AsyncImage( - model = profileImage, - contentDescription = "프로필 이미지", - modifier = Modifier - .size(24.dp) - .clip(CircleShape) - ) + Row { + if (!profileImage.isNullOrBlank()) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(profileImage) + .crossfade(true) + .build(), + contentDescription = "프로필 이미지", + modifier = Modifier + .size(24.dp) + .clip(CircleShape), + contentScale = ContentScale.Crop + )추가 import:
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import coil.request.ImageRequestapp/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt (1)
35-36: 잘못된/빈 색상값 대비용 폴백 색상 보강 제안hexToColor는 파싱 실패 시 Color.White를 반환하므로, 밝은 배경에서는 가독성 문제가 날 수 있습니다. 빈 문자열 방어와 함께 테마 색상으로 폴백하는 것이 안전합니다.
- bottomTextColor = hexToColor(member.aliasColor), + bottomTextColor = member.aliasColor + .takeIf { it.isNotBlank() } + ?.let { hexToColor(it) } + ?: colors.ScienceIt,app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt (1)
6-11: 서버 스펙과 필드명/케이싱 일치 여부 확인 (@SerialName 고려)서버가 snake_case 또는 다른 키명을 사용한다면, 현재 프로퍼티명(camelCase)이 그대로 직렬화되어 API와 불일치할 수 있습니다. 백엔드 스펙에 맞춰 @SerialName("...")를 명시적으로 부여하는 것을 권장합니다. 특히 isReplyRequest, postType, parentId의 키명을 백엔드 계약과 재확인해 주세요.
원하시면 서버 스펙을 기준으로 @SerialName 주석을 일괄 반영한 패치를 준비해 드리겠습니다.
app/src/main/java/com/texthip/thip/data/service/CommentsService.kt (2)
23-27: 파라미터 명칭 혼동: Body는 request로 명명 권장Body 파라미터 이름이 response로 되어 있어 혼동을 유발합니다. request로 변경해 가독성과 일관성을 높이세요.
- suspend fun likeComment( - @Path("commentId") commentId: Long, - @Body response: CommentsLikesRequest - ): BaseResponse<CommentsLikesResponse> + suspend fun likeComment( + @Path("commentId") commentId: Long, + @Body request: CommentsLikesRequest + ): BaseResponse<CommentsLikesResponse>
17-21: postType String 남용 → 타입 세이프 enum으로 교체 제안Query 파라미터를 String 리터럴로 유지하면 오타/스펙 변경에 취약합니다. enum(예: PostType)으로 치환해 컴파일 타임 안전성을 확보하는 것을 권장합니다. 레포지토리/모델과 함께 일괄 반영되어야 합니다.
적용 방향:
- enum 정의(예시)
enum class PostType { RECORD, DISCUSSION /* ... */ }
- 서비스 시그니처
- @Query("postType") postType: String = "RECORD", + @Query("postType") postType: PostType = PostType.RECORD,
- Retrofit은 기본적으로 enum을 name 문자열로 직렬화합니다. 서버가 다른 표기(예: "record")를 요구하면 enum에 @SerialName 또는 커스텀 toString을 적용하세요. 서버 표기를 확인해 주세요.
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt (1)
71-75: 아이콘 접근성: contentDescription 제공 권장하트 아이콘의 contentDescription이 null입니다. 스크린리더 사용자 접근성을 위해 상태에 따라 “좋아요”/“좋아요 취소” 등 설명을 제공해주세요. 새로운 문자열 리소스가 필요합니다.
- Icon( - painter = painterResource(if (data.isLike) R.drawable.ic_heart_center_filled else R.drawable.ic_heart_center), - contentDescription = null, - tint = Color.Unspecified - ) + Icon( + painter = painterResource(if (data.isLike) R.drawable.ic_heart_center_filled else R.drawable.ic_heart_center), + contentDescription = if (data.isLike) stringResource(R.string.cd_unlike) else stringResource(R.string.cd_like), + tint = Color.Unspecified + )app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsResponse.kt (3)
12-26: 타입명 혼동: CommentList/ReplyList는 “단일 아이템”을 의미합니다.
- 서버 응답의 “리스트”와 혼동될 수 있습니다. 유지보수성과 가독성을 위해
CommentList→Comment,ReplyList→Reply로의 리네임을 권장합니다. 이 변경은 전역 참조 수정이 필요하지만, 장기적으로 이득이 큽니다.적용 예시:
-@Serializable -data class CommentList( +@Serializable +data class Comment( val commentId: Int, val creatorId: Int, val creatorProfileImageUrl: String, val creatorNickname: String, val aliasName: String, val aliasColor: String, val postDate: String, val content: String, val likeCount: Int, val isDeleted: Boolean, val isLike: Boolean, - val replyList: List<ReplyList>, + val replyList: List<Reply>, ) @Serializable -data class ReplyList( +data class Reply( val commentId: Int, val parentCommentCreatorNickname: String, val creatorId: Int, val creatorProfileImageUrl: String, val creatorNickname: String, val aliasName: String, val aliasColor: String, val postDate: String, val content: String, val likeCount: Int, val isLike: Boolean, )리팩터 외부 영향:
- UI 컴포저블(ReplyItem, CommentSection, CommentBottomSheet), VM/리포지토리에서 타입명 교체 필요.
- import 경로는 동일, 식별자만 변경.
Also applies to: 28-41
14-16: ID 타입 일관성 검토 필요 (Int → Long 고려).
- 현재 모든 ID가
Int입니다. 상위 도메인(예: GroupNoteScreen.initialize)에서postId는Long으로 다뤄지고 있습니다. API가 long 범위의 ID를 반환할 가능성이 있다면, 댓글/답글의commentId,creatorId도Long으로 승격하는 것을 권장합니다. 불일치 시 오버플로/캐스팅 문제 또는 혼선을 유발할 수 있습니다.API 스펙 확인 후 필요 시 아래와 같이 변경:
- val commentId: Int, - val creatorId: Int, + val commentId: Long, + val creatorId: Long,Reply에도 동일 적용:
- val commentId: Int, - val creatorId: Int, + val commentId: Long, + val creatorId: Long,Also applies to: 30-33
5-10: JSON 키 네이밍(스네이크 vs 카멜) 확인 및 @SerialName 매핑 여부 검토.
- 서버가 snake_case를 사용한다면 현재 프로퍼티명(카멜)과 자동 매핑이 실패할 수 있습니다.
@SerialName으로 명시 매핑을 권장합니다. 확인 후 필요 시 반영해 주세요.예시:
@Serializable data class Comment( @SerialName("comment_id") val commentId: Int, @SerialName("creator_id") val creatorId: Int, @SerialName("creator_profile_image_url") val creatorProfileImageUrl: String?, @SerialName("creator_nickname") val creatorNickname: String, ... )참고:
creatorProfileImageUrl은 서버가 null을 줄 수 있으니String?고려도 추천.Also applies to: 12-26, 28-41
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt (1)
20-21: 미사용 파라미터 제거: onSendReply는 이 컴포저블에서 사용되지 않습니다.
- 실제 사용은 BottomSheet에서 일어나고, 여기서는 콜백을 전달만 하지 않습니다. 파라미터 제거로 API를 간결하게 유지하세요. 프리뷰도 정리 필요.
fun CommentSection( - commentItem: CommentList, - onSendReply: (String, Int?, String?) -> Unit = { _, _, _ -> }, + commentItem: CommentList, onReplyClick: (commentId: Int, nickname: String) -> Unit, onEvent: (CommentsEvent) -> Unit = { _ -> } ) @@ - ), - onSendReply = { _, _, _ -> }, + ), onReplyClick = { commentId, nickname -> // Handle reply click }Also applies to: 79-83
app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt (1)
90-99: 접근성(보이스오버) 개선 제안.
- 좋아요 아이콘/버튼에 대한
contentDescription또는semantics { role = Role.Button }등의 보강을 권장합니다. 현재contentDescription = null이라 스크린리더 노출이 되지 않습니다. 상태(좋아요 여부, 개수) 파악 가능하도록 간단한 설명을 추가해 주세요.예시:
- Icon의
contentDescription에 “좋아요”, “좋아요 취소” 등 상태 기반 라벨 제공- 클릭 영역의 Column에
semantics { role = Role.Button }부여app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (2)
473-479: LaunchedEffect 키를 postId로 좁혀 불필요한 재초기화 방지.
- 객체 전체를 키로 사용하면 얕은 변경에도 재실행됩니다.
postId/postType만 키로 쓰거나selectedPostForComment?.let{}블록으로 감싸는 편이 안전합니다.- LaunchedEffect(selectedPostForComment) { - commentsViewModel.initialize( - postId = selectedPostForComment!!.postId.toLong(), - postType = selectedPostForComment!!.postType - ) - } + LaunchedEffect(selectedPostForComment?.postId, selectedPostForComment?.postType) { + selectedPostForComment?.let { + initializeComments( + postId = it.postId.toLong(), + postType = it.postType + ) + } + }
481-497: 댓글 전송 이벤트 라우팅 OK, 단 중복 방지 가드 유지 권장.
text.isNotBlank()체크 후CommentsEvent.CreateComment디스패치 흐름이 명확합니다. 상위/하위에서 이중 체크가 생기지 않도록 일관된 레이어 하나에만 검증을 두는 것도 고려하세요.app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt (1)
132-145: 무한 스크롤 트리거 임계값 도입으로 초기 과호출 방지.
- 화면에 아이템이 1~2개인 초기 상태에도 “마지막 아이템이 보인다”로 간주되어 즉시
LoadMore가 발생할 수 있습니다. 1개 이상 프리페치 여유를 두어 과호출을 줄이세요.- val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0 - lastVisibleItemIndex >= layoutInfo.totalItemsCount - 1 + val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1 + // 끝에서 2개 이내로 접근 시 로드 (프리페치) + lastVisibleItemIndex >= layoutInfo.totalItemsCount - 2app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt (2)
14-52: 중복된 runCatching + handleBaseResponse 패턴 공통화 제안세 메서드 모두 동일한 예외 처리/응답 래핑 패턴을 사용합니다. 재사용 가능한
safeApiCall헬퍼로 추출하면 중복이 줄고 유지보수가 쉬워집니다.예시 요약:
- private suspend fun safeApiCall(block: suspend () -> BaseResponse): Result = runCatching { block().handleBaseResponse().getOrThrow() }
- 각 메서드에서 safeApiCall { commentsService.xxx(...) } 형태로 단순화.
14-24: 반환 타입 명시로 가독성 향상현재 반환 타입이 타입 추론에 의존합니다. Repository의 퍼블릭 API는
Result<...>를 명시하면 호출부 가독성과 유지보수성이 좋아집니다.예:
suspend fun getComments(...): Result<CommentsResponse>형태로 명시.Also applies to: 26-34, 36-52
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (3)
60-78: 댓글 작성: 성공 시 전체 새로고침 대신 부분 갱신 준비현재 서버 응답이 준비되면 단일 항목만 추가하도록 계획이라고 명시되어 있습니다. 응답을 받는 즉시 목록 최상단 prepend(또는 parentId 매칭 시 해당 쓰레드 아래에 append)하는 로직을 바로 수용할 수 있게 구조를 잡아두면 리렌더 비용을 줄일 수 있습니다. 또한 전송 중 상태(isCreating)도 UI에 노출하는 편이 UX에 도움이 됩니다.
원하시면 서버 응답 DTO에 맞춰 단일 삽입/스레드 삽입 로직과
isCreating/에러 초기화 처리까지 포함한 패치 제안을 드리겠습니다.
142-147: 불필요한 네트워크 호출 방지: 다음 커서(null) 시 LoadMore 차단추가 로딩 시
nextCursor == null이면 첫 페이지 재호출이 일어날 수 있습니다. 방어적으로 막아주세요.변경 제안:
- if (currentState.isLoading || currentState.isLoadingMore || (currentState.isLast && !isRefresh)) { + if (currentState.isLoading || currentState.isLoadingMore || + (currentState.isLast && !isRefresh) || + (!isRefresh && nextCursor == null) + ) { return }
60-78: 동일 항목 연속 토글과 응답 순서 역전(race) 대응낙관적 업데이트는 UX에 좋지만, 같은 항목에 대해 빠르게 여러 번 토글하면 이전 요청의 늦은 응답이 최신 상태를 덮어쓸 수 있습니다. 항목별 pending-like 세트를 두고, 요청 중에는 추가 토글을 막거나, 응답에 correlation id를 부여해 최신 상태만 반영하도록 개선을 권장합니다.
원하시면 pendingLikeIds 기반의 간단한 차단 로직 또는 응답 시퀀스 번호를 활용한 최신성 보장 패치를 제안드리겠습니다.
Also applies to: 96-106, 130-140, 148-176
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (27)
app/src/main/AndroidManifest.xml(1 hunks)app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt(2 hunks)app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsLikesRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsCreateResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsLikesResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPlayingResponse.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/repository/CommentsRepository.kt(1 hunks)app/src/main/java/com/texthip/thip/data/service/CommentsService.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/common/header/ProfileBar.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomHeader.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt(3 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(2 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt(5 hunks)app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt(1 hunks)app/src/main/java/com/texthip/thip/utils/rooms/AdvancedImePadding.kt(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt (1)
app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt (1)
hexToColor(6-13)
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt (4)
app/src/main/java/com/texthip/thip/ui/common/bottomsheet/CustomBottomSheet.kt (1)
CustomBottomSheet(44-129)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (1)
onEvent(48-58)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt (1)
CommentSection(17-55)app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
ThipTheme(41-69)
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomHeader.kt (1)
app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt (1)
hexToColor(6-13)
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt (2)
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (1)
onEvent(48-58)app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt (1)
ReplyItem(28-109)
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt (2)
app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt (1)
ProfileBarFeed(27-80)app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt (1)
hexToColor(6-13)
app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt (1)
app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt (1)
hexToColor(6-13)
🔇 Additional comments (25)
app/src/main/java/com/texthip/thip/ui/common/header/ProfileBar.kt (1)
35-35: 기능 변화 없음 확인 — 기본값 유지로 OKLine 35의 TODO 제거만 이뤄져 동작 변화는 없습니다. 기본값으로 colors.NeonGreen 유지되는 점 문제 없습니다.
app/src/main/AndroidManifest.xml (1)
20-20: IME 대응을 위한 설정 추가 적절adjustResize 추가로 키보드 표시 시 레이아웃 리사이즈가 보장되어 댓글 입력 플로우와 잘 맞습니다. AdvancedImePadding과 함께 충돌 소지도 없어 보입니다.
app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsCreateResponse.kt (1)
5-8: 서버 응답 키/타입 일치 여부 확인 필요
- 백엔드가 snake_case(예: comment_id)를 사용한다면 @SerialName이 필요합니다.
- commentId 범위가 Int를 넘을 가능성이 있다면 Long으로의 상향도 검토해주세요.
app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt (1)
29-34: ProfileBarFeed 호출부에서 Painter 인자 잔여 없음프로파일 이미지 파라미터를 모두
String?타입으로 전달하고 있어, 기존에Painter를 넘기던 호출부는 발견되지 않았습니다.
추가 수정이나 검토가 필요하지 않습니다.app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomMatesScreen.kt (1)
103-118: 프리뷰 데이터에 aliasColor 추가 변경사항 LGTM프리뷰가 실제 데이터 모델과 일치하도록 보강된 점 좋습니다. 런타임에서는 빈 문자열/형식 오류 시의 대체 색상만 일관되게 처리되면 충분합니다.
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt (1)
61-61: 프리뷰의 aliasColor 예시값 추가 LGTM프리뷰가 실제 렌더링 상태를 더 가깝게 보여주도록 개선되었습니다.
Also applies to: 69-69
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsUsersResponse.kt (1)
15-15: 새로운 aliasColor 필드 추가가 잘 구현되었습니다.사용자 별명에 색상을 적용하기 위한 필드가 적절히 추가되었으며, 관련 UI 컴포넌트들과 잘 연동될 것으로 보입니다.
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (5)
209-209: 동적 roomId 사용으로 변경된 것이 좋습니다.하드코딩된 roomId = 1 대신 route에서 받은 동적 roomId를 사용하도록 개선되어 실제 룸 컨텍스트에 맞는 화면 초기화가 가능해졌습니다.
228-228: 일관된 동적 roomId 전달이 잘 구현되었습니다.GroupRoomMatesScreen에도 동적 roomId가 올바르게 전달되어 룸별 멤버 정보를 정확히 표시할 수 있게 되었습니다.
251-251: GroupNoteScreen의 동적 roomId 연동이 적절합니다.댓글 기능과 연관된 GroupNoteScreen에서 올바른 roomId를 받아 댓글 시스템 초기화에 필요한 룸 컨텍스트를 제공하고 있습니다.
286-286: 노트 생성 화면의 roomId 전달이 정확합니다.GroupNoteCreateScreen에 동적 roomId가 전달되어 특정 룸에 대한 노트 생성이 가능해졌습니다.
307-307: 투표 생성 화면의 roomId 연동이 완료되었습니다.GroupVoteCreateScreen에도 동적 roomId가 올바르게 전달되어 룸별 투표 생성 기능이 정상 작동할 것입니다.
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPlayingResponse.kt (1)
15-15: 카테고리 색상 필드 추가가 적절합니다.UI에서 카테고리별 색상 구분을 위한 categoryColor 필드가 잘 추가되었으며, 관련 컴포넌트들에서 활용될 준비가 되었습니다.
app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt (2)
4-4: androidx.core.graphics.toColorInt 사용으로 개선되었습니다.Android의 Color.parseColor 대신 androidx.core.graphics.toColorInt를 사용하여 더 안정적이고 현대적인 방식으로 헥스 색상을 변환하도록 개선되었습니다.
8-8: 색상 변환 로직이 올바르게 수정되었습니다.hex.toColorInt()를 사용하여 Color 객체를 생성하는 방식으로 변경되어 Compose UI와 더 잘 호환됩니다.
app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt (2)
4-4: CommentsService import가 추가되었습니다.새로운 댓글 서비스에 대한 import가 올바르게 추가되어 DI 바인딩 준비가 완료되었습니다.
41-44: CommentsService DI 바인딩이 적절히 구현되었습니다.다른 서비스들과 일관된 패턴으로 CommentsService가 바인딩되어 댓글 관련 API 호출을 위한 의존성 주입이 준비되었습니다.
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt (1)
165-167: 카테고리 컬러 전달 연동 OKroomDetails.category와 categoryColor를 Header에 정확히 전달하고 있어 변경된 시그니처와 일치합니다. 미리보기에서도 categoryColor를 제공하고 있어 안정적입니다.
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomHeader.kt (1)
39-41: hex 컬러 적용 경로 합리적입니다categoryColor를 hex로 받아 hexToColor로 바로 적용하는 간결한 흐름으로 정리되어 좋습니다. 서버가 “#” 접두어를 항상 보장하는지만 백엔드와 합의되어 있으면 안정적입니다. (hexToColor는 예외 시 White로 폴백)
Also applies to: 176-176
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt (1)
34-36: 좋아요/답글 이벤트 연결 LGTM
- VM 이벤트(
LikeComment,LikeReply)로의 위임이 명확하고, parent/child 식별자 전달도 정확합니다.Also applies to: 41-49
app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt (1)
49-54: 데이터 바인딩 이행 정상 (ReplyList → UI).
- 프로필/닉네임/칭호/색상/작성일 매핑이 모델 필드에 정확히 대응합니다.
hexToColor예외 처리도 적절.app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt (1)
81-92: 상태 기반 렌더링/이벤트 위임 구조 전반 LGTM
- 로딩/빈 상태/리스트 상태 분기,
LoadMoreComments디스패치,replyTo상태 관리가 깔끔합니다.app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt (2)
10-13: Hilt/싱글턴 구성은 적절합니다DI 구성(@singleton, @Inject)과 의존성 주입 구조가 명확합니다.
26-34: 인자 이름response가 서비스 시그니처와 일치함 — 수정 불필요확인 결과
CommentsService.likeComment메서드의 두 번째 파라미터가@Body response: CommentsLikesRequest로 선언되어 있어, 레포지토리 쪽의
response = CommentsLikesRequest(type)사용이 올바릅니다. 제안된
request네임드 인자로의 변경은 불필요하므로 현재 코드 그대로 유지해 주세요.
- app/src/main/java/com/texthip/thip/data/service/CommentsService.kt
• @Body response: CommentsLikesRequest- app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt
• response = CommentsLikesRequest(type)Likely an incorrect or invalid review comment.
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (1)
22-27: Int → Long ID 타입 일관성: 전체 레이어 반영 검토 필요현재
CommentsEvent만Int → Long으로 변경할 경우, 다음을 비롯해 앱 전반에서 ID 타입 불일치가 발생합니다.
백엔드가 Long 기반 ID를 사용 중이라면, 아래 리스트에 있는 모든 위치를 함께 리팩터링해야 하며, API 계약 변경에 따른 호환성 검증이 필수입니다.· ViewModel
CommentsViewModel내 이벤트 파라미터 (LikeComment, LikeReply, CreateComment)toggleCommentLike,toggleReplyLike,createComment메서드 시그니처· UI 호출부
GroupNoteScreen.kt(CommentsEvent.CreateComment(parentId)호출)CommentBottomSheet.kt(onReplyClick: (commentId: Int, …)등)CommentSection.kt(onReplyClick,onSendReply,onEvent(CommentsEvent)등 콜백 시그니처)· 네트워크 모델
CommentsCreateRequest.parentId: Int?CommentsCreateResponse.commentId: IntCommentsLikesResponse.commentId: IntCommentsResponse.CommentList.commentId,ReplyList.commentId등· Repository
CommentsRepository메서드 파라미터 (parentId: Int?)· Mock / 데이터 모델
ReplyData.kt내CommentItem.commentId: Int,userId: Int위 전부를 Long으로 통일할 때, 각 레이어별로 다음 작업이 필요합니다:
- 백엔드 API 스펙(ID 타입)을 최종 확인
- DTO(request/response) → Repository → ViewModel → UI 콜백 순으로 파라미터 타입 변경
- 구버전 클라이언트와의 호환성(마이그레이션) 전략 수립
먼저 백엔드 ID 타입을 확정한 뒤, 범위가 큰 리팩터링인 만큼 단계별로 진행해주세요.
| data class CommentsCreateRequest( | ||
| val content: String, | ||
| val isReplyRequest: Boolean, | ||
| val parentId: Int? = null, | ||
| val postType: String, | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
ID 타입 불일치: parentId를 Long?으로 통일하세요
네트워크 계층(CommentsService)의 Path 파라미터들은 Long을 사용하고 있습니다. 여기에 매핑되는 parentId만 Int?인 것은 타입 불일치로, 장기적으로 오버플로 및 일관성 저하의 원인이 됩니다. 댓글/답글의 식별자는 전반적으로 Long을 사용하는 쪽으로 통일하는 것을 권장합니다.
적용 예시:
data class CommentsCreateRequest(
val content: String,
val isReplyRequest: Boolean,
- val parentId: Int? = null,
+ val parentId: Long? = null,
val postType: String,
)🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt
around lines 6 to 11, parentId is declared as Int? while network layer
(CommentsService) uses Long IDs; change parentId to Long? to align types, update
any constructors/usages and serialization/mapping code that create or consume
CommentsCreateRequest to accept/produce Long values, and run/build tests to
ensure no further Int usages remain.
| @Serializable | ||
| data class CommentsLikesResponse( | ||
| val commentId: Int, | ||
| val isLiked: Boolean, | ||
| ) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
ID 타입 일관성 검토 필요 — Int → Long로 통일 제안
Response의 commentId가 Int인데, Service 계층에서 commentId 매개변수를 Long으로 사용하는 경우가 많습니다. 도메인 전반에서 ID는 Long으로 통일하는 것을 권장합니다. 또한 서버 필드명이 snake_case(comment_id)라면 @SerialName 지정이 필요합니다.
제안 변경:
-import kotlinx.serialization.Serializable
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerialName
@Serializable
data class CommentsLikesResponse(
- val commentId: Int,
+ /* 서버가 snake_case를 사용한다면 다음과 같이 지정:
+ @SerialName("comment_id")
+ */
+ val commentId: Long,
val isLiked: Boolean,
)검증 스크립트(서비스 시그니처와 다른 모델의 commentId 타입을 조사):
🏁 Script executed:
#!/bin/bash
# CommentsService의 likeComment 시그니처와 commentId 타입을 확인
echo "=== CommentsService likeComment signatures ==="
rg -n -A 3 -B 2 $'interface\\s+CommentsService|likeComment\\s*\\('
echo
echo "=== Response/Model 내 commentId 타입 현황 ==="
rg -n -A 1 -B 1 $'commentId\\s*:\\s*(Int|Long)'
echo
echo "=== Repository 계층에서 likeComment 사용부 ==="
rg -n -A 3 -B 2 $'likeComment\\s*\\('Length of output: 11283
🏁 Script executed:
#!/bin/bash
# Search for JSON serializer configuration and naming strategies
echo "=== kotlinx.serialization.json.Json instantiations ==="
rg -n "kotlinx.serialization.json.Json" -A 3 -B 3
echo
echo "=== Any custom Json builders or module installs ==="
rg -n "Json\\s*\\{" -A 3 -B 3
echo
echo "=== SerialName usages across models ==="
rg -n "SerialName"
echo
echo "=== References to naming strategy or snake_case ==="
rg -n "snake" -A 2 -B 2Length of output: 22080
CommentsLikesResponse.commentId → Long 및 @SerialName("comment_id") 적용 제안
CommentsService와 CommentsRepository 모두 commentId를 Long으로 사용하고, 프로젝트 전반에서 ID를 Long으로 통일하고 있습니다. 또한 NetWorkModule에서 snake_case 자동 변환 설정을 사용하지 않으므로, 서버가 comment_id를 반환할 경우 @SerialName을 붙여야 합니다.
• 대상 파일
• app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsLikesResponse.kt
• 제안 변경사항
-import kotlinx.serialization.Serializable
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerialName
@Serializable
data class CommentsLikesResponse(
- val commentId: Int,
+ @SerialName("comment_id")
+ val commentId: Long,
val isLiked: 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 CommentsLikesResponse( | |
| val commentId: Int, | |
| val isLiked: Boolean, | |
| ) | |
| // app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsLikesResponse.kt | |
| import kotlinx.serialization.Serializable | |
| import kotlinx.serialization.SerialName | |
| @Serializable | |
| data class CommentsLikesResponse( | |
| @SerialName("comment_id") | |
| val commentId: Long, | |
| val isLiked: Boolean, | |
| ) |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsLikesResponse.kt
around lines 5 to 9, change the commentId property from Int to Long and annotate
it with @SerialName("comment_id") so the model matches
CommentsService/CommentsRepository ID types and the server's snake_case key;
also add the kotlinx.serialization.SerialName import if missing.
| suspend fun getComments( | ||
| postId: Long, | ||
| postType: String = "RECORD", | ||
| cursor: String? = null, | ||
| ) = runCatching { | ||
| commentsService.getComments( | ||
| postId = postId, | ||
| postType = postType, | ||
| cursor = cursor | ||
| ).handleBaseResponse().getOrThrow() | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
postType을 String 상수로 노출하지 말고 강타입으로 관리하세요
postType: String = "RECORD"는 오타/변경에 취약합니다. enum class(또는 sealed class)로 강타입화하고 직렬화 시 @SerializedName/@Json 어댑터로 문자열 매핑하는 방식을 권장합니다.
예시(요약):
- enum class PostType { RECORD, NOTICE, ... }
- Repository/Service는
postType: PostType을 받고, Retrofit/직렬화에서 문자열로 변환.
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt
around lines 14–24, the function currently exposes postType as a raw String
defaulting to "RECORD", which is error-prone; change the API to accept a
strong-typed PostType (enum or sealed class) instead of String, update the
repository signature to postType: PostType and convert to the wire string at the
serialization boundary (either annotate enum entries with @SerializedName /
@Json or add a Retrofit/Converter adapter) so Retrofit sends the correct string;
update the CommentsService interface and all callers to use PostType and ensure
serialization mapping is added so no runtime string literals remain.
| suspend fun createComment( | ||
| postId: Long, | ||
| content: String, | ||
| isReplyRequest: Boolean, | ||
| parentId: Int? = null, | ||
| postType: String = "RECORD", | ||
| ) = runCatching { | ||
| commentsService.createComment( | ||
| postId = postId, | ||
| request = CommentsCreateRequest( | ||
| content = content, | ||
| isReplyRequest = isReplyRequest, | ||
| parentId = parentId, | ||
| postType = postType | ||
| ) | ||
| ).handleBaseResponse().getOrThrow() | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
ID 타입 일관성 검토: parentId(Int?) vs commentId(Long)
createComment의 parentId: Int?와 likeComment의 commentId: Long이 혼재합니다. 서버가 Long ID를 사용한다면 전체 레이어(UI 모델/이벤트, ViewModel, Repository, Service, DTO)를 Long으로 통일하는 것이 안전합니다. 혼재 시 변환 과정에서 overflow/호환성 문제가 생길 수 있습니다.
원본 모델/서비스 시그니처의 타입을 점검한 뒤 통일을 제안드립니다. 검색 스크립트:
🏁 Script executed:
#!/bin/bash
# commentId/parentId 타입 선언을 전역 검색
rg -n -A 2 -B 2 $'commentId\\s*:\\s*(Int|Long)|parentId\\s*:\\s*(Int|Long)' app/src/main/javaLength of output: 8010
🏁 Script executed:
#!/bin/bash
# Search for createComment signature in CommentsService.kt
rg -n "createComment" app/src/main/java/com/texthip/thip/data/service/CommentsService.ktLength of output: 122
🏁 Script executed:
#!/bin/bash
# Get the definition of createComment in CommentsService.kt with context
rg -n -A3 -B3 "suspend fun createComment" app/src/main/java/com/texthip/thip/data/service/CommentsService.ktLength of output: 364
ID 타입 통일: 댓글 관련 ID를 모두 Long으로 변경 필요
Service(CommentsService)는 Long을 사용하지만, Request/Response DTO와 ViewModel/UI 이벤트는 Int를 사용하고 있어 변환 과정에서 오버플로우나 호환성 문제가 발생할 수 있습니다. 아래 파일들의 ID 타입을 Int → Long(nullable 포함)으로 변경하세요.
• app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt
- parentId: Int? → Long?
• app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsCreateResponse.kt - commentId: Int → Long
• app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsLikesResponse.kt - commentId: Int → Long
• app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsResponse.kt - CommentList.commentId, ReplyList.commentId 등 → Long
• app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt - createComment(parentId: Int?) → Long?
• app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt - data class LikeComment(val commentId: Int) → Long
- data class CreateComment(..., val parentId: Int?) → Long?
• 앱 레이어(UI 컴포넌트)의 이벤트 핸들러(onReplyClick,onEvent등) 파라미터 타입 수정
예시 diff:
--- CommentsCreateRequest.kt
- val parentId: Int? = null,
+ val parentId: Long? = null,
--- CommentsCreateResponse.kt
- val commentId: Int,
+ val commentId: Long,위 파일들을 포함해 댓글 ID를 다루는 모든 레이어(UI 모델/이벤트, ViewModel, Repository, Service, DTO)에서 Long으로 일관되게 변경해주세요.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt
around lines 36-52, the createComment signature and internal use still treat
parentId as Int? while the service uses Long; change the repository method
signature to accept parentId: Long? and return Long? (or propagate the service
Long), update the CommentsCreateRequest construction to pass parentId as Long?
(no Int conversions), and ensure the function's return type matches the
DTO/service (Long). Also update all related DTOs, ViewModel event/data classes,
and UI handlers listed in the review to use Long (nullable where applicable) so
ID types are consistent across repository → service → DTO → ViewModel → UI
layers.
| @POST("comments/{postId}") | ||
| suspend fun createComment( | ||
| @Path("postId") postId: Long, | ||
| @Body request: CommentsCreateRequest | ||
| ): BaseResponse<CommentsCreateResponse> |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
createComment의 타입 일관성 점검(IDs Long 통일)
createComment의 Path는 Long(postId)인데, 요청 바디의 parentId가 현재 Int?로 설계되어 있습니다. 상위 모델(CommentsCreateRequest)의 parentId를 Long?으로 정렬해야 합니다. 상단 파일 코멘트를 참고해 일괄 통일을 권장합니다.
🏁 Script executed:
#!/bin/bash
# Locate the CommentsCreateRequest definition to verify the type of parentId
rg "class CommentsCreateRequest" -n .Length of output: 165
🏁 Script executed:
#!/bin/bash
# 확인: CommentsCreateRequest 내 parentId 타입 조회
rg "parentId" -n app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.ktLength of output: 137
parentId 타입을 Long? 으로 통일해주세요
CommentsService.createComment API의 postId가 Long으로 정의되어 있으니, 요청 바디의 parentId도 Long? 으로 변경해 타입 일관성을 유지해야 합니다.
수정 대상:
- 파일: app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt
- 라인 9
제안된 변경:
- val parentId: Int? = null,
+ val parentId: Long? = null,📝 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.
| @POST("comments/{postId}") | |
| suspend fun createComment( | |
| @Path("postId") postId: Long, | |
| @Body request: CommentsCreateRequest | |
| ): BaseResponse<CommentsCreateResponse> | |
| // app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt | |
| data class CommentsCreateRequest( | |
| val postId: Long, | |
| val content: String, | |
| val parentId: Long? = null | |
| ) |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt
around line 9, the parentId property is not nullable while the API's postId is
defined as Long; update the parentId type to Long? (make it nullable) so the
request model matches the service expectations; ensure any usages and
constructor/defaults accommodate the nullable type.
| val reply = parentComment.replyList[replyIndex] | ||
| val currentIsLiked = reply.isLike | ||
| val newLikeCount = if (currentIsLiked) reply.likeCount - 1 else reply.likeCount + 1 | ||
|
|
||
| // 즉시 UI 업데이트 | ||
| val updatedReply = reply.copy(isLike = !currentIsLiked, likeCount = newLikeCount) | ||
| val newReplyList = | ||
| parentComment.replyList.toMutableList().apply { set(replyIndex, updatedReply) } | ||
| val updatedParentComment = parentComment.copy(replyList = newReplyList) | ||
| val newComments = | ||
| comments.toMutableList().apply { set(parentCommentIndex, updatedParentComment) } | ||
| _uiState.update { it.copy(comments = newComments) } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
대댓글 좋아요 수 음수 방지
대댓글도 동일하게 클램프가 필요합니다.
변경 제안:
- val currentIsLiked = reply.isLike
- val newLikeCount = if (currentIsLiked) reply.likeCount - 1 else reply.likeCount + 1
+ val currentIsLiked = reply.isLike
+ val newLikeCount = (if (currentIsLiked) reply.likeCount - 1 else reply.likeCount + 1).coerceAtLeast(0)📝 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.
| val reply = parentComment.replyList[replyIndex] | |
| val currentIsLiked = reply.isLike | |
| val newLikeCount = if (currentIsLiked) reply.likeCount - 1 else reply.likeCount + 1 | |
| // 즉시 UI 업데이트 | |
| val updatedReply = reply.copy(isLike = !currentIsLiked, likeCount = newLikeCount) | |
| val newReplyList = | |
| parentComment.replyList.toMutableList().apply { set(replyIndex, updatedReply) } | |
| val updatedParentComment = parentComment.copy(replyList = newReplyList) | |
| val newComments = | |
| comments.toMutableList().apply { set(parentCommentIndex, updatedParentComment) } | |
| _uiState.update { it.copy(comments = newComments) } | |
| val reply = parentComment.replyList[replyIndex] | |
| val currentIsLiked = reply.isLike | |
| val newLikeCount = (if (currentIsLiked) | |
| reply.likeCount - 1 | |
| else | |
| reply.likeCount + 1 | |
| ).coerceAtLeast(0) | |
| // 즉시 UI 업데이트 | |
| val updatedReply = reply.copy(isLike = !currentIsLiked, likeCount = newLikeCount) | |
| val newReplyList = | |
| parentComment.replyList.toMutableList().apply { set(replyIndex, updatedReply) } | |
| val updatedParentComment = parentComment.copy(replyList = newReplyList) | |
| val newComments = | |
| comments.toMutableList().apply { set(parentCommentIndex, updatedParentComment) } | |
| _uiState.update { it.copy(comments = newComments) } |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt
around lines 117 to 128, the reply like count can go negative when unliking;
clamp the computed newLikeCount to never drop below zero. Change the like count
calculation to compute the tentative value (reply.likeCount - 1 or +1) and then
apply a max(0, tentativeValue) before creating updatedReply, so the UI state
update uses the non-negative likeCount.
| _uiState.update { | ||
| val originalComments = it.comments.toMutableList() | ||
| originalComments[parentCommentIndex] = parentComment | ||
| it.copy(comments = originalComments) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
대댓글 롤백도 id 재탐색으로 안전하게 되돌리기
부모/자식 인덱스가 변했을 수 있으므로 id로 재탐색 후 반영하세요.
변경 제안:
- _uiState.update {
- val originalComments = it.comments.toMutableList()
- originalComments[parentCommentIndex] = parentComment
- it.copy(comments = originalComments)
- }
+ _uiState.update {
+ val list = it.comments.toMutableList()
+ val pIdx = list.indexOfFirst { c -> c.commentId == parentComment.commentId }
+ if (pIdx != -1) {
+ list[pIdx] = parentComment
+ }
+ it.copy(comments = list)
+ }📝 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.
| _uiState.update { | |
| val originalComments = it.comments.toMutableList() | |
| originalComments[parentCommentIndex] = parentComment | |
| it.copy(comments = originalComments) | |
| } | |
| } | |
| } | |
| _uiState.update { | |
| val list = it.comments.toMutableList() | |
| val pIdx = list.indexOfFirst { c -> c.commentId == parentComment.commentId } | |
| if (pIdx != -1) { | |
| list[pIdx] = parentComment | |
| } | |
| it.copy(comments = list) | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt
around lines 133-139, the code updates parent/child comments by using stored
indexes which may be stale; instead, locate the parent and child by their ids
before applying the rollback. Find the parent index with comments.indexOfFirst {
it.id == parentComment.id } and if found, replace that item; if a child rollback
is needed, locate the child within the parent's replies by id (e.g.,
parent.replies.indexOfFirst { it.id == childComment.id }) and replace it
immutably, then update the top-level comments list with the modified parent
object; handle not-found cases gracefully (no-op or log) to avoid
IndexOutOfBoundsExceptions.
| viewModelScope.launch { | ||
| _uiState.update { | ||
| if (isRefresh) it.copy(isLoading = true, comments = emptyList(), isLast = false) | ||
| else it.copy(isLoadingMore = true) | ||
| } | ||
|
|
||
| val cursorToFetch = if (isRefresh) null else nextCursor | ||
|
|
||
| commentsRepository.getComments(postId = currentPostId, cursor = cursorToFetch) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
리프레시 시작 시 nextCursor와 에러 상태 초기화
리프레시 시 커서를 리셋하고, 이전 에러 메시지를 지워야 UI가 깔끔합니다.
변경 제안:
- _uiState.update {
- if (isRefresh) it.copy(isLoading = true, comments = emptyList(), isLast = false)
- else it.copy(isLoadingMore = true)
- }
+ _uiState.update {
+ if (isRefresh) it.copy(isLoading = true, comments = emptyList(), isLast = false, error = null)
+ else it.copy(isLoadingMore = true, error = null)
+ }
+ if (isRefresh) nextCursor = null📝 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 { | |
| _uiState.update { | |
| if (isRefresh) it.copy(isLoading = true, comments = emptyList(), isLast = false) | |
| else it.copy(isLoadingMore = true) | |
| } | |
| val cursorToFetch = if (isRefresh) null else nextCursor | |
| commentsRepository.getComments(postId = currentPostId, cursor = cursorToFetch) | |
| viewModelScope.launch { | |
| _uiState.update { | |
| if (isRefresh) it.copy(isLoading = true, comments = emptyList(), isLast = false, error = null) | |
| else it.copy(isLoadingMore = true, error = null) | |
| } | |
| if (isRefresh) nextCursor = null | |
| val cursorToFetch = if (isRefresh) null else nextCursor | |
| commentsRepository.getComments(postId = currentPostId, cursor = cursorToFetch) |
| commentsRepository.getComments(postId = currentPostId, cursor = cursorToFetch) | ||
| .onSuccess { response -> | ||
| if (response != null) { | ||
| nextCursor = response.nextCursor | ||
| _uiState.update { | ||
| it.copy( | ||
| isLoading = false, | ||
| isLoadingMore = false, | ||
| // isRefresh일 경우 새 목록으로, 아닐 경우 기존 목록에 추가 | ||
| comments = if (isRefresh) response.commentList else it.comments + response.commentList, | ||
| isLast = response.isLast | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| .onFailure { throwable -> |
There was a problem hiding this comment.
postType 미전달 버그 + null 응답 시 로딩 플래그 해제 누락
- Repository의
getComments에postType을 전달하지 않아 항상"RECORD"로 호출됩니다. onSuccess에서response == null이면 로딩 플래그가 풀리지 않아 UI가 영구 로딩 상태가 됩니다.
변경 제안:
- commentsRepository.getComments(postId = currentPostId, cursor = cursorToFetch)
+ commentsRepository.getComments(
+ postId = currentPostId,
+ postType = currentPostType,
+ cursor = cursorToFetch
+ )
.onSuccess { response ->
- if (response != null) {
- nextCursor = response.nextCursor
- _uiState.update {
- it.copy(
- isLoading = false,
- isLoadingMore = false,
- // isRefresh일 경우 새 목록으로, 아닐 경우 기존 목록에 추가
- comments = if (isRefresh) response.commentList else it.comments + response.commentList,
- isLast = response.isLast
- )
- }
- }
+ if (response != null) {
+ nextCursor = response.nextCursor
+ _uiState.update {
+ it.copy(
+ isLoading = false,
+ isLoadingMore = false,
+ comments = if (isRefresh) response.commentList else it.comments + response.commentList,
+ isLast = response.isLast
+ )
+ }
+ } else {
+ // null 응답이라도 로딩 상태는 반드시 해제
+ _uiState.update { it.copy(isLoading = false, isLoadingMore = false) }
+ }
}
.onFailure { throwable ->
_uiState.update {
it.copy(isLoading = false, isLoadingMore = false, error = throwable.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.
| commentsRepository.getComments(postId = currentPostId, cursor = cursorToFetch) | |
| .onSuccess { response -> | |
| if (response != null) { | |
| nextCursor = response.nextCursor | |
| _uiState.update { | |
| it.copy( | |
| isLoading = false, | |
| isLoadingMore = false, | |
| // isRefresh일 경우 새 목록으로, 아닐 경우 기존 목록에 추가 | |
| comments = if (isRefresh) response.commentList else it.comments + response.commentList, | |
| isLast = response.isLast | |
| ) | |
| } | |
| } | |
| } | |
| .onFailure { throwable -> | |
| commentsRepository.getComments( | |
| postId = currentPostId, | |
| postType = currentPostType, | |
| cursor = cursorToFetch | |
| ) | |
| .onSuccess { response -> | |
| if (response != null) { | |
| nextCursor = response.nextCursor | |
| _uiState.update { | |
| it.copy( | |
| isLoading = false, | |
| isLoadingMore = false, | |
| comments = if (isRefresh) response.commentList else it.comments + response.commentList, | |
| isLast = response.isLast | |
| ) | |
| } | |
| } else { | |
| // null 응답이라도 로딩 상태는 반드시 해제 | |
| _uiState.update { it.copy(isLoading = false, isLoadingMore = false) } | |
| } | |
| } | |
| .onFailure { throwable -> | |
| _uiState.update { | |
| it.copy(isLoading = false, isLoadingMore = false, error = throwable.message) | |
| } | |
| } |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt
around lines 156 to 171, the call to commentsRepository.getComments omits the
postType parameter (causing a default "RECORD" call) and the onSuccess branch
fails to clear loading flags when response == null; update the getComments
invocation to pass the current postType (e.g., postType = currentPostType) and
modify the onSuccess null branch to update _uiState to set isLoading = false and
isLoadingMore = false (and keep other state unchanged) so the UI exits loading
state when the response is null.
| fun Modifier.advancedImePadding() = composed { | ||
| var consumePadding by remember { mutableStateOf(0) } | ||
| onGloballyPositioned { coordinates -> | ||
| val rootCoordinate = coordinates.findRootCoordinates() | ||
| val bottom = coordinates.positionInWindow().y + coordinates.size.height | ||
|
|
||
| consumePadding = (rootCoordinate.size.height - bottom).toInt().coerceAtLeast(0) | ||
| } | ||
| .consumeWindowInsets(PaddingValues(bottom = (consumePadding / LocalDensity.current.density).dp)) | ||
| .imePadding() | ||
| } No newline at end of file |
There was a problem hiding this comment.
consumeWindowInsets는 실제 패딩을 추가하지 않습니다 → padding으로 변경 필요
지금 구현은 onGloballyPositioned로 계산한 consumePadding을 레이아웃에 반영하지 않고, imePadding만 적용되고 있습니다. consumeWindowInsets(PaddingValues(...))는 하위로 전달되는 인셋을 “소비”할 뿐, 실제 패딩을 추가하진 않습니다. 의도대로라면 계산된 값을 padding(bottom = ...)으로 적용해야 합니다.
아래와 같이 수정하면 계산된 여백이 실제로 반영됩니다.
-fun Modifier.advancedImePadding() = composed {
- var consumePadding by remember { mutableStateOf(0) }
- onGloballyPositioned { coordinates ->
- val rootCoordinate = coordinates.findRootCoordinates()
- val bottom = coordinates.positionInWindow().y + coordinates.size.height
-
- consumePadding = (rootCoordinate.size.height - bottom).toInt().coerceAtLeast(0)
- }
- .consumeWindowInsets(PaddingValues(bottom = (consumePadding / LocalDensity.current.density).dp))
- .imePadding()
-}
+fun Modifier.advancedImePadding() = composed {
+ val density = LocalDensity.current
+ var bottomPaddingDp by remember { mutableStateOf(0.dp) }
+ onGloballyPositioned { coordinates ->
+ val rootCoordinate = coordinates.findRootCoordinates()
+ val bottom = coordinates.positionInWindow().y + coordinates.size.height
+ val px = (rootCoordinate.size.height - bottom).toInt().coerceAtLeast(0)
+ bottomPaddingDp = with(density) { px.toDp() }
+ }
+ .padding(bottom = bottomPaddingDp)
+ .imePadding()
+}추가 import (파일 상단에 필요):
import androidx.compose.foundation.layout.padding🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/utils/rooms/AdvancedImePadding.kt around
lines 18-28, the code currently calls consumeWindowInsets(...) which only
consumes insets and does not apply visual padding; replace the
.consumeWindowInsets(PaddingValues(bottom = (consumePadding /
LocalDensity.current.density).dp)) call with .padding(bottom = (consumePadding /
LocalDensity.current.density).dp) so the computed bottom spacing is actually
applied to the layout, keep .imePadding() as needed, and add the import
androidx.compose.foundation.layout.padding at the top of the file.
| } | ||
| } | ||
|
|
||
| private fun toggleReplyLike(parentCommentId: Int, replyId: Int) { |
There was a problem hiding this comment.
위와 동일하네요 확실히 서버에서 오는 응답이 느릴 수 도 있으니 ui를 업데이트 먼저하고 요청을 보내고 실패시 되돌리는 로직이 좋네요
| import androidx.compose.ui.layout.positionInWindow | ||
| import androidx.compose.ui.platform.LocalDensity | ||
| import androidx.compose.ui.unit.dp | ||
|
|
There was a problem hiding this comment.
키보드가 올라왔을때 패딩이 추가되는거라는데 실제로 잘 동작하나요?
➕ 이슈 링크
🔎 작업 내용
📸 스크린샷
KakaoTalk_Video_2025-08-14-03-06-18.mp4
😢 해결하지 못한 과제
댓글 삭제는 서버에서 api 수정해주면 작업 하겠습니다
현재 댓글을 작성하고 나서 화면이 전체 재랜더링 되는데 서버에 댓글 정보 반환해달라고 요청했습니다. 해당 부분 수정되면 하나면 재랜더링 되도록 수정하겠습니다
📢 리뷰어들에게
Summary by CodeRabbit
• 신기능
• 그룹 노트에 댓글 시스템 추가: 댓글/대댓글 조회, 페이지네이션, 좋아요, 작성이 가능한 바텀시트 UI 도입.
• 개선사항
• 피드 프로필 이미지를 URL 기반 비동기 로딩으로 전환.
• 그룹 룸 헤더와 멤버 목록에 동적 색상 적용(카테고리/별칭 색상 표시).
• 키보드 등장 시 화면 자동 리사이즈 및 IME 겹침 최소화를 위한 고급 패딩 처리.
• 내비게이션에서 동적 roomId 사용으로 이동 정확도 향상.
• 피드 댓글 화면에서 댓글/대댓글 표시 일시 비활성화.