[API] 오늘의 한마디 삭제, 방 나가기 api 연동#130
Conversation
…into api/THIP-TextHip#125-rooms_delete
Walkthrough방 나가기 및 오늘의 한마디 삭제 API 연동을 추가하고, 이에 따른 ViewModel 이벤트/토스트 흐름과 화면 로직을 확장했습니다. 문자열 리소스를 보강하고, 일부 UI 텍스트/프리뷰/토스트 처리와 불필요한 모의 데이터 및 컨텍스트 참조를 정리했습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant S as GroupRoomChatScreen
participant VM as GroupRoomChatViewModel
participant R as RoomsRepository
participant API as RoomsService / Backend
U->>S: "삭제" 선택
S->>VM: Event.DeleteGreeting(attendanceCheckId)
VM->>R: deleteRoomsDailyGreeting(roomId, attendanceCheckId)
R->>API: DELETE /rooms/{roomId}/daily-greeting/{attendanceCheckId}
API-->>R: BaseResponse<RoomsDeleteDailyGreetingResponse>
R-->>VM: Result.Success
VM->>VM: fetchDailyGreetings(isRefresh=true)
VM-->>S: Event.ShowToast(DELETE_GREETING_SUCCESS)
note over S: 토스트 노출 후 3초 자동 소거
sequenceDiagram
autonumber
actor U as User
participant S as GroupRoomScreen
participant VM as GroupRoomViewModel
participant R as RoomsRepository
participant API as RoomsService / Backend
U->>S: "방 나가기" 확인
S->>VM: leaveRoom(roomId)
VM->>R: leaveRoom(roomId)
R->>API: DELETE /rooms/{roomId}/leave
API-->>R: BaseResponse<Unit>
R-->>VM: Result.Success
VM-->>S: Event.ShowToast(LEAVE_ROOM_SUCCESS)
S->>S: 토스트 노출(3초)
S->>S: 토스트 종료 시 onBackClick()
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
✨ 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: 3
🧹 Nitpick comments (15)
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsDeleteDailyGreetingResponse.kt (1)
5-8: 서버 JSON 키 확인 및 직렬화 안정성 보강 제안서버가
roomId대신room_id를 반환한다면 역직렬화가 실패합니다. 계약을 확인하고 필요 시@SerialName을 명시하세요. 또한 팀의 ktlint/Detekt 설정에 따라 마지막 매개변수의 트레일링 콤마가 경고가 될 수 있으니 확인 바랍니다.적용 예시(계약이
roomId일 때도 명시적으로 두면 안전):-@Serializable -data class RoomsDeleteDailyGreetingResponse ( - val roomId: Int, -) +@Serializable +data class RoomsDeleteDailyGreetingResponse( + @SerialName("roomId") val roomId: Int +)추가 import(선택):
import kotlinx.serialization.SerialNameapp/src/main/res/values/strings.xml (1)
199-199: 문구 간결화 제안더 자연스럽고 일관된 톤으로 다듬으면 좋겠습니다.
- <string name="group_room_chat_delete_success">오늘의 한마디 삭제를 완료했어요.</string> + <string name="group_room_chat_delete_success">오늘의 한마디를 삭제했어요.</string>app/src/main/java/com/texthip/thip/ui/group/room/viewmodel/GroupRoomViewModel.kt (2)
37-39: SharedFlow 버퍼 미설정으로 emit가 불필요하게 suspend될 수 있음수집자가 잠시 없으면
emit이 suspend되어 UI 흐름이 끊길 수 있습니다. 버퍼를 주고tryEmit을 쓰는 쪽이 토스트 이벤트에 적합합니다.- private val _eventFlow = MutableSharedFlow<GroupRoomEvent>() + private val _eventFlow = MutableSharedFlow<GroupRoomEvent>( + replay = 0, + extraBufferCapacity = 1 + ) val eventFlow = _eventFlow.asSharedFlow()- .onSuccess { - _eventFlow.emit(GroupRoomEvent.ShowToast(GroupRoomToastType.LEAVE_ROOM_SUCCESS)) - } + .onSuccess { + _eventFlow.tryEmit(GroupRoomEvent.ShowToast(GroupRoomToastType.LEAVE_ROOM_SUCCESS)) + } .onFailure { throwable -> - _eventFlow.emit(GroupRoomEvent.ShowToast(GroupRoomToastType.ACTION_FAILURE)) + _eventFlow.tryEmit(GroupRoomEvent.ShowToast(GroupRoomToastType.ACTION_FAILURE)) }Also applies to: 59-69
21-24: ACTION_FAILURE가 지나치게 범용적 — 이벤트를 액션별로 세분화하면 매핑이 명확해집니다토스트 문자열/네비게이션을 액션별로 안정적으로 매핑하려면 이벤트를 구체 타입으로 분리하는 편이 깔끔합니다.
-enum class GroupRoomToastType { - LEAVE_ROOM_SUCCESS, - ACTION_FAILURE // 방 나가기/삭제 등 실패 시 공통으로 사용 -} - -sealed interface GroupRoomEvent { - data class ShowToast(val type: GroupRoomToastType) : GroupRoomEvent -} +sealed interface GroupRoomEvent { + data object LeaveRoomSuccess : GroupRoomEvent + data object LeaveRoomFailure : GroupRoomEvent +}- .onSuccess { - _eventFlow.tryEmit(GroupRoomEvent.ShowToast(GroupRoomToastType.LEAVE_ROOM_SUCCESS)) - } + .onSuccess { + _eventFlow.tryEmit(GroupRoomEvent.LeaveRoomSuccess) + } .onFailure { throwable -> - _eventFlow.tryEmit(GroupRoomEvent.ShowToast(GroupRoomToastType.ACTION_FAILURE)) + _eventFlow.tryEmit(GroupRoomEvent.LeaveRoomFailure) }화면단에서는
LeaveRoomSuccess/Failure를 각각leave_room_toast/leave_room_fail_toast로 매핑하면 됩니다.Also applies to: 26-28, 59-69
app/src/main/java/com/texthip/thip/data/service/RoomsService.kt (1)
194-197: 모임 나가기 응답 타입(Unit) 직렬화/언랩 동작 확인
BaseResponse<Unit>에 대해 서버가data필드를 비우거나(또는 204) 반환 시,handleBaseResponse().getOrThrow()가 성공으로 처리되며 null/빈 객체를 허용하는지 확인 필요합니다. 불일치 시, 빈 응답 전용 DTO(예:EmptyResponse) 도입을 고려해 주세요.app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt (2)
292-301: 반환 타입 명시로 계약을 선명하게가독성과 사용처의 기대치를 명확히 하기 위해 반환 타입을 명시하는 것을 권장합니다.
- suspend fun deleteRoomsDailyGreeting( - roomId: Int, - attendanceCheckId: Int - ) = runCatching { + suspend fun deleteRoomsDailyGreeting( + roomId: Int, + attendanceCheckId: Int + ): Result<com.texthip.thip.data.model.rooms.response.RoomsDeleteDailyGreetingResponse?> = runCatching { roomsService.deleteRoomsDailyGreeting( roomId = roomId, attendanceCheckId = attendanceCheckId ).handleBaseResponse().getOrThrow() }(필요 시 파일 상단에
RoomsDeleteDailyGreetingResponseimport 추가)
312-318: 트레일링 콤마 제거 + 반환 타입 명시 제안파라미터/호출부의 트레일링 콤마 제거로 일관성을 맞추고, 반환 타입을 명시해 주세요.
- suspend fun leaveRoom( - roomId: Int, - ) = runCatching { - roomsService.leaveRoom( - roomId = roomId, - ).handleBaseResponse().getOrThrow() - } + suspend fun leaveRoom( + roomId: Int + ): Result<Unit?> = runCatching { + roomsService.leaveRoom( + roomId = roomId + ).handleBaseResponse().getOrThrow() + }app/src/main/java/com/texthip/thip/ui/group/room/viewmodel/GroupRoomChatViewModel.kt (1)
65-77: 에러 문자열 하드코딩 제거 권장VM에서 하드코딩 대신 UI 레이어에서 문자열 리소스로 폴백 처리하는 편이 지역화/일관성에 유리합니다.
- }.onFailure { throwable -> - _eventFlow.emit(GroupRoomChatEvent.ShowErrorToast(throwable.message ?: "삭제에 실패했습니다.")) - } + }.onFailure { throwable -> + _eventFlow.emit(GroupRoomChatEvent.ShowErrorToast(throwable.message.orEmpty())) + }추가로, 화면에서 빈 문자열일 때 리소스 기반 기본문구로 대체하도록 처리 제안(아래 화면 코멘트 참고).
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomChatScreen.kt (4)
97-99: 에러 토스트 기본 문구 폴백VM에서 빈 메시지가 올 수 있으므로, 화면에서 리소스 기반 기본값으로 폴백하세요.
- Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show() + Toast.makeText( + context, + event.message.ifBlank { context.getString(R.string.group_room_chat_delete_fail) }, + Toast.LENGTH_SHORT + ).show()추가 리소스:
<!-- res/values/strings.xml --> <string name="group_room_chat_delete_fail">오늘의 한마디 삭제에 실패했어요.</string>
105-111: 토스트 표시시간/애니메이션 타이밍 정렬 제안입장/퇴장 애니메이션(각 2000ms) 대비 표시시간(3000ms)이 길어 UX가 다소 늘어짐. 1500/1500 + 2000ms 표시 등으로 정렬을 고려해 보세요.
279-288: 주석 정리(가독성)“visible 조건을 showToast로 변경” 주석은 변경 의도가 충분히 코드에 드러납니다. 불필요 주석 제거 권장.
- AnimatedVisibility( - // visible 조건을 showToast로 변경 - visible = showToast, + AnimatedVisibility( + visible = showToast,
308-311: 삭제 전 확인 UX 고려바텀시트에서 곧바로 삭제 실행 중입니다. 실수 방지를 위해 간단한 확인 다이얼로그(예/아니오) 추가를 고려해 주세요.
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt (3)
77-85: eventFlow 수집: LaunchedEffect 키를 Unit으로 고정 권장Flow 객체 참조 변화에 영향받지 않도록 키를 고정하는 편이 안전합니다.
-LaunchedEffect(key1 = viewModel.eventFlow) { +LaunchedEffect(Unit) { viewModel.eventFlow.collectLatest { event -> when (event) { is GroupRoomEvent.ShowToast -> { activeToast = event.type } } } }참고로 생명주기 인식 수집 패턴도 고려해 주세요:
// 필요 import: // import androidx.lifecycle.Lifecycle // import androidx.lifecycle.repeatOnLifecycle // import androidx.compose.ui.platform.LocalLifecycleOwner val lifecycleOwner = LocalLifecycleOwner.current LaunchedEffect(Unit) { lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.eventFlow.collectLatest { /* ... */ } } }
235-274: 토스트 애니메이션과 내비게이션 타이밍 다듬기 제안
- 2초 애니메이션은 과도하게 느립니다. 200~300ms 정도가 일반적입니다.
- 내비게이션(onBackClick) 전에 토스트를 먼저 닫고(exit 애니메이션 시간만큼 대기 후) 이동하면, dispose된 컴포지션에 setState가 걸릴 여지를 줄일 수 있습니다.
- enter = slideInVertically( - initialOffsetY = { -it }, - animationSpec = tween(durationMillis = 2000) - ), + enter = slideInVertically( + initialOffsetY = { -it }, + animationSpec = tween(durationMillis = 250) + ), exit = slideOutVertically( - targetOffsetY = { -it }, - animationSpec = tween(durationMillis = 2000) + targetOffsetY = { -it }, + animationSpec = tween(durationMillis = 250) ), @@ - LaunchedEffect(activeToast) { + LaunchedEffect(activeToast) { if (activeToast != null) { delay(3000L) - if (activeToast == GroupRoomToastType.LEAVE_ROOM_SUCCESS) { - onBackClick() - } - onDismissToast() + // 1) 먼저 토스트 닫기 + onDismissToast() + // 2) 성공 시 exit 애니메이션(250ms) 뒤에 내비게이션 + if (activeToast == GroupRoomToastType.LEAVE_ROOM_SUCCESS) { + delay(250L) + onBackClick() + } } }추가로, 콜백 최신 참조를 캡처하려면 rememberUpdatedState(onBackClick/onDismissToast) 사용을 고려해 주세요.
319-321: 확인 클릭 후 중복 요청 방지 UX 제안확인 버튼 다중 탭을 막기 위해 API 진행 중에는 버튼 비활성/로딩 표시를 넣는 것을 권장합니다(예: DialogPopup에 loading/disabled 상태 지원).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (12)
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsDeleteDailyGreetingResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt(2 hunks)app/src/main/java/com/texthip/thip/data/service/RoomsService.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt(0 hunks)app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMySectionHeader.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/room/mock/GroupRoomChatData.kt(0 hunks)app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomChatScreen.kt(8 hunks)app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt(9 hunks)app/src/main/java/com/texthip/thip/ui/group/room/viewmodel/GroupRoomChatViewModel.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/group/room/viewmodel/GroupRoomViewModel.kt(3 hunks)app/src/main/res/values/strings.xml(2 hunks)
💤 Files with no reviewable changes (2)
- app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt
- app/src/main/java/com/texthip/thip/ui/group/room/mock/GroupRoomChatData.kt
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomChatScreen.kt (2)
app/src/main/java/com/texthip/thip/ui/common/modal/ToastWithDate.kt (1)
ToastWithDate(24-61)app/src/main/java/com/texthip/thip/ui/group/room/viewmodel/GroupRoomChatViewModel.kt (1)
onEvent(57-63)
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt (1)
app/src/main/java/com/texthip/thip/ui/common/modal/ToastWithDate.kt (1)
ToastWithDate(24-61)
🔇 Additional comments (11)
app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMySectionHeader.kt (1)
31-31: 문구 리소스 교체 LGTM
my_group→my_group_room교체 적절합니다.app/src/main/java/com/texthip/thip/data/service/RoomsService.kt (2)
24-24: 필요한 응답 모델 import 추가, OK
RoomsDeleteDailyGreetingResponse사용 준비가 되어 있습니다. 별도 이견 없습니다.
182-187: DELETE 오늘의 한마디 API: 경로/파라미터 이름 BE 스펙 재확인 권장
attendanceCheckId가 서버 스펙의 경로 파라미터 키와 1:1 일치하는지 확인해 주세요. (케이스/스펠링 상이 시 404 가능성)app/src/main/java/com/texthip/thip/ui/group/room/viewmodel/GroupRoomChatViewModel.kt (3)
27-29: ToastType 확장, OK신규 토스트 타입 정의 적절합니다.
35-36: 이벤트 추가, OK
DeleteGreeting(attendanceCheckId)이벤트 정의 적절합니다.
57-63: onEvent 분기 추가, OK삭제 이벤트를 VM 내부 처리로 위임하는 구조 적절합니다.
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt (5)
3-6: 필요 import 추가/정리 LGTM애니메이션/토스트/Flow 수집에 필요한 의존성들이 정확히 추가되었습니다.
Also applies to: 35-35, 44-44, 49-50, 56-57
70-70: 토스트 상태를 상위(Screen)에서 관리한 설계 좋습니다단발성 이벤트 처리 후 UI에 전달하는 흐름이 명확합니다.
103-107: leaveRoom 연동 배선 OK. 이벤트 발행/중복 처리 확인 부탁드립니다
- 성공 시 LEAVE_ROOM_SUCCESS, 실패 시 ACTION_FAILURE가 단발성으로 정확히 1회 emit되는지 확인해 주세요.
- 재시도/중복 요청 케이스에서 이벤트가 중복 발생하지 않도록 보장되는지도 점검 부탁드립니다.
127-130: GroupRoomContent API 확장 합리적입니다토스트/퇴장 확인 핸들러를 명시적으로 주입해 테스트 용이성과 응집도가 좋아졌습니다.
375-377: 프리뷰 파라미터 업데이트 LGTM새로운 필수 파라미터 반영이 정확합니다.
| LaunchedEffect(key1 = Unit) { | ||
| viewModel.refreshData() | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
초기 로딩 중복 여부 확인
LaunchedEffect(Unit)에서 refreshData()를 호출하면 ViewModel init에서도 초기 로딩을 한다면 이중 호출이 됩니다. 한쪽으로 일원화하는지 확인해 주세요.
다음 스크립트로 중복 가능성을 점검할 수 있습니다:
🏁 Script executed:
#!/bin/bash
# GroupMyViewModel에서 init 블록이나 초기 데이터 로딩 여부 확인
rg -nP 'class\s+GroupMyViewModel\b' -A120 | rg -n 'init\s*\{|refreshData\s*\(' -n -C2Length of output: 1093
초기 데이터 로딩 중복 제거
GroupMyViewModel의 init { loadMyRooms(reset = true) }와 GroupMyScreen의 LaunchedEffect(Unit)에서 호출하는 viewModel.refreshData()(loadMyRooms)가 중복됩니다. 한쪽 호출을 제거해 단일 진입점으로 통일해 주세요.
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt
around lines 51-53, the initial data load is duplicated: GroupMyViewModel
already calls loadMyRooms(reset = true) in its init block and GroupMyScreen
calls viewModel.refreshData() inside a LaunchedEffect(Unit); remove the
LaunchedEffect call in GroupMyScreen so the ViewModel's init remains the single
entry point for initial data loading, keeping UI code free of duplicate startup
logic and relying on the ViewModel to manage its own lifecycle-triggered load.
| val dailyGreetingLimitMessage = stringResource(R.string.group_room_chat_max) | ||
| val deleteSuccessMessage = stringResource(R.string.group_room_chat_delete_success) | ||
|
|
There was a problem hiding this comment.
FIRST_WRITE 토스트 메시지 매핑 오류
FIRST_WRITE가 현재 “하루 최대 작성 수 초과” 메시지로 표시됩니다. 첫 작성 축하/완료성 메시지로 분리해 주세요.
- val dailyGreetingLimitMessage = stringResource(R.string.group_room_chat_max)
- val deleteSuccessMessage = stringResource(R.string.group_room_chat_delete_success)
+ val dailyGreetingLimitMessage = stringResource(R.string.group_room_chat_max)
+ val firstWriteMessage = stringResource(R.string.group_room_chat_first_write)
+ val deleteSuccessMessage = stringResource(R.string.group_room_chat_delete_success)
…
- ToastType.FIRST_WRITE -> {
- toastMessage = dailyGreetingLimitMessage
- toastColor = colorWhite
- }
+ ToastType.FIRST_WRITE -> {
+ toastMessage = firstWriteMessage
+ toastColor = colorWhite
+ }화면 외부 변경(리소스 추가)도 필요합니다:
<!-- res/values/strings.xml -->
<string name="group_room_chat_first_write">첫 오늘의 한마디를 남겼어요!</string>Also applies to: 81-96
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomChatScreen.kt
around lines 74-76 (and similarly lines 81-96), the FIRST_WRITE toast is
incorrectly mapped to the daily limit message; update the mapping so FIRST_WRITE
uses a new string resource R.string.group_room_chat_first_write (a celebratory
"first write" message) instead of group_room_chat_max, and add the corresponding
string entry to res/values/strings.xml with the provided text so the toast
displays the correct first-write message.
| <string name="leave_room_toast">모임 나가기를 완료했어요.</string> | ||
| <string name="leave_room_fail_toast">모임 나가기를 실패했어요.</string> |
There was a problem hiding this comment.
문구 교정: 조사 사용 오류(‘…를 실패’ → ‘…에 실패’)
자연스러운 표현으로 수정 권장합니다.
- <string name="leave_room_fail_toast">모임 나가기를 실패했어요.</string>
+ <string name="leave_room_fail_toast">모임 나가기에 실패했어요.</string>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <string name="leave_room_toast">모임 나가기를 완료했어요.</string> | |
| <string name="leave_room_fail_toast">모임 나가기를 실패했어요.</string> | |
| <string name="leave_room_toast">모임 나가기를 완료했어요.</string> | |
| <string name="leave_room_fail_toast">모임 나가기에 실패했어요.</string> |
🤖 Prompt for AI Agents
In app/src/main/res/values/strings.xml around lines 172 to 173, the second
string uses the wrong Korean particle ("모임 나가기를 실패했어요."); change it to the
natural phrasing "모임 나가기에 실패했어요." by updating the value of the
leave_room_fail_toast string accordingly.
➕ 이슈 링크
🔎 작업 내용
📸 스크린샷
😢 해결하지 못한 과제
기록, 투표 수정하기는 2차때 반영하겠습니다
📢 리뷰어들에게
Summary by CodeRabbit