Conversation
Walkthrough방 기록/투표 수정 기능을 추가. PATCH 요청/응답 모델 생성, Retrofit Service/Repository 경로 추가. UI는 작성 화면에 편집 모드(prefill, 포커스, 비활성화 제어) 도입. 입력 컴포넌트(API) 다수 시그니처 변경(TextFieldValue·isEnabled·readOnly 등). 리스트 화면에서 “수정” 진입 및 라우팅/라우트 데이터 확장. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant L as GroupNoteScreen
participant N as GroupNoteCreateScreen
participant VM as GroupNoteCreateViewModel
participant R as RoomsRepository
participant S as RoomsService
participant API as Server
U->>L: 게시물에서 "수정" 선택
L->>N: 네비게이트(postId, page, content, isOverview, ...)
N->>VM: initialize(..., postId, page, content, isOverview)
VM->>VM: isEditMode = true, 필드 prefill
U->>N: 내용 편집 후 저장 클릭
N->>VM: CreateRecordClicked
alt isEditMode
VM->>R: patchRoomsRecord(roomId, recordId, content)
R->>S: PATCH /rooms/{roomId}/records/{recordId}
S->>API: Request
API-->>S: BaseResponse(roomId)
S-->>R: Response
R-->>VM: Result
VM-->>N: 성공 상태 업데이트
else create
VM->>R: createRoomsRecord(...)
end
sequenceDiagram
autonumber
actor U as User
participant L as GroupNoteScreen
participant V as GroupVoteCreateScreen
participant VM as GroupVoteCreateViewModel
participant R as RoomsRepository
participant S as RoomsService
participant API as Server
U->>L: 투표에서 "수정" 선택
L->>V: 네비게이트(postId, page, isOverview, title, options)
V->>VM: initialize(..., postId, page, isOverview, title, options)
VM->>VM: isEditMode = true, 필드 prefill
U->>V: 제목/옵션 확인 후 저장
V->>VM: CreateVoteClicked
alt isEditMode
VM->>R: patchRoomsVote(roomId, voteId, content=titleValue.text)
R->>S: PATCH /rooms/{roomId}/votes/{voteId}
S->>API: Request
API-->>S: BaseResponse(roomId)
S-->>R: Response
R-->>VM: Result
VM-->>V: 성공 상태 업데이트
else create
VM->>R: createRoomsVote(...)
end
sequenceDiagram
autonumber
participant Feed as GroupNoteScreen
participant Nav as NavHostController
participant Routes as GroupRoutes
Feed->>Nav: navigateToGroupNoteCreate(..., postId,page,content,isOverview)
Nav->>Routes: GroupRoutes.NoteCreate(..., postId,...)
Routes-->>Nav: Dest args 확장
Nav-->>Feed: 화면 전환
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewersPre-merge checks (2 passed, 3 warnings)❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
Poem
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupVoteCreateViewModel.kt (1)
115-119: 옵션 인덱스 범위 체크 누락으로 인한 크래시 가능성UI 상 비정상 인덱스가 전달되면 IndexOutOfBoundsException이 발생할 수 있습니다. 방어 로직을 추가해 주세요.
- is GroupVoteCreateEvent.OptionChanged -> _uiState.update { - val newOptions = it.options.toMutableList() - newOptions[event.index] = event.text - it.copy(options = newOptions) - } + is GroupVoteCreateEvent.OptionChanged -> _uiState.update { + val newOptions = it.options.toMutableList() + if (event.index in newOptions.indices) { + newOptions[event.index] = event.text + } + it.copy(options = newOptions) + } - is GroupVoteCreateEvent.RemoveOptionClicked -> if (_uiState.value.options.size > 2) { - _uiState.update { - val newOptions = it.options.toMutableList() - newOptions.removeAt(event.index) - it.copy(options = newOptions) - } - } + is GroupVoteCreateEvent.RemoveOptionClicked -> _uiState.update { + val newOptions = it.options.toMutableList() + if (event.index in newOptions.indices && newOptions.size > 2) { + newOptions.removeAt(event.index) + } + it.copy(options = newOptions) + }Also applies to: 133-139
🧹 Nitpick comments (19)
app/src/main/java/com/texthip/thip/ui/common/buttons/GroupVoteButton.kt (2)
50-59: 정밀도 손실로 애니메이션 계단현상 가능 — Float 비율로 직접 애니메이션 권장정수 퍼센트(
toInt())로 먼저 잘라내면 진행 바가 1% 단위로만 움직여 덜 매끄럽습니다. 비율(Float)을 그대로 타겟으로 쓰면 부드럽습니다.- val votePercent = if (totalVotes > 0) { - (item.count.toFloat() / totalVotes * 100).toInt() - } else { - 0 - } - - val animatedPercent by animateFloatAsState( - targetValue = votePercent / 100f, + val progressFraction = if (totalVotes > 0) { + item.count.toFloat() / totalVotes + } else { + 0f + } + + val animatedPercent by animateFloatAsState( + targetValue = progressFraction, animationSpec = tween(durationMillis = 500) )설명: 변수명은 유지(
animatedPercent)하여 하단fillMaxWidth(animatedPercent)는 수정 없이 동작합니다.
112-116: 표시 문자열 하드코딩 지양 — 문자열 리소스로 국제화(i18n) 대비
"${item.count}표"는 한글 고정 문자열입니다. 리소스로 분리해 포맷을 관리하면 지역화와 테스트가 수월합니다. 필요하면 퍼센트 병행 표기도 선택 옵션으로 둬도 좋습니다.- Text( - text = "${item.count}표", + Text( + text = stringResource(R.string.vote_count_format, item.count), color = textColor, style = fontStyle )추가 사항:
- 임포트:
import androidx.compose.ui.res.stringResourceres/values/strings.xml:<string name="vote_count_format">%1$d표</string>(영문화 시 예: "%1$d votes")
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPatchRecordRequest.kt (1)
5-8: 빈 문자열 방지 가드 고려서버가 공백·빈 문자열을 거부한다면 상위 레이어(Repository/UseCase)에서
isBlank()체크로 빠르게 실패 처리하는 편이 안전합니다.app/src/main/java/com/texthip/thip/ui/common/forms/BorderedTextField.kt (1)
50-59: 비활성화 시 클릭 가드 보강 및 접근성 텍스트 현지화 제안
- 방어적으로 클릭 가능 여부도
isEnabled에 종속시키면 안전합니다.contentDescription에 하드코딩된 영어 문자열 대신 string 리소스 사용 권장.적용 예:
- modifier = Modifier - .clickable(enabled = iconEnabled) { + modifier = Modifier + .clickable(enabled = iconEnabled && isEnabled) {Also applies to: 107-119
app/src/main/java/com/texthip/thip/ui/common/forms/BookPageTextField.kt (1)
76-90: readOnly에서 텍스트 자동 초기화 방지 가드현재 포커스 획득 시 초기화 로직이
enabled만 보고 동작합니다. 방어적으로!readOnly도 함께 확인하는 것이 안전합니다.- .then( - if (enabled) { + .then( + if (enabled && !readOnly) { Modifier.onFocusChanged { focusState ->app/src/main/java/com/texthip/thip/ui/group/note/component/OpinionInputSection.kt (1)
36-37: FocusRequester 기본값 처리 방식 점검기본값에서
remember { FocusRequester() }사용은 가능하나, 외부에서 포커스를 강제하지 않는 케이스가 더 많다면 내부에서 생성(hoist 불필요)하거나, 호출자가 항상 주입하도록 일관성 정리 권장.app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPatchVoteResponse.kt (1)
6-8: 후행 쉼표 제거 제안(스타일 일관성/빌드 호환성)Kotlin 1.4+에선 허용되지만, 옆 파일(RecordResponse)과 스타일을 맞추고 구버전 호환성 이슈를 예방합시다.
data class RoomsPatchVoteResponse( - val roomId: Int, + val roomId: Int )app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPatchVoteRequest.kt (1)
7-8: 트레일링 콤마 스타일 확인 필요프로젝트의 Kotlin/ktlint 설정에 따라 트레일링 콤마 허용 여부가 다릅니다. 팀 컨벤션이 미허용이면 제거해 주세요.
다음과 같이 정리 가능:
data class RoomsPatchVoteRequest( - val content: String, + val content: String )app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt (1)
322-334: 콘텐츠 전송 전 최소 전처리 권장(트림/빈값 방지)사소하지만 API 일관성에 도움 됩니다. 공백만 있는 경우 서버에서 400을 주지 않는다면 클라이언트에서 방지하는 편이 안전합니다.
다음과 같이 트림 적용 및 빈값 가드 추가를 고려해 주세요:
- request = RoomsPatchRecordRequest( - content = content - ) + request = RoomsPatchRecordRequest( + content = content.trim() + )필요 시 호출부(뷰모델)에서
require(content.isNotBlank())체크도 함께 권장합니다.app/src/main/java/com/texthip/thip/ui/group/note/component/PageInputSection.kt (1)
75-80: readOnly 처리 일관성 보완 제안isGeneralReview일 때도 읽기 전용으로 표시되면 UX가 더 명확합니다. 현재는 enabled만 꺼지고 readOnly는 isEnabled에만 연동됩니다. 둘을 결합해 상태를 명확히 해주세요.
- enabled = !isGeneralReview && isEnabled, - readOnly = !isEnabled, + enabled = !isGeneralReview && isEnabled, + readOnly = isGeneralReview || !isEnabled,app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt (1)
102-107: NoteCreate 네비게이션 파라미터 확장 OK편집 흐름(postId, page, content, isOverview)을 선택적으로 전달하는 구조가 자연스럽습니다. 다만 선택 파라미터가 늘어난 만큼 “편집 페이로드” 데이터 클래스로 묶는 것도 가독성/확장성에 유리합니다.
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteCreateViewModel.kt (1)
46-46: 변수 명명 일관성 개선 필요
postIdToEdit는 기록과 투표 모두에서 사용되므로, 더 일반적인 이름인editingPostId로 변경하는 것이 좋겠습니다.- private var postIdToEdit: Int? = null // 수정할 포스트 ID 저장 + private var editingPostId: Int? = null // 수정 중인 포스트 IDapp/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt (1)
94-99: 포커스 요청 타이밍 개선 필요고정된 100ms 지연 대신 컴포지션이 완료된 후 포커스를 요청하는 것이 더 안정적입니다.
LaunchedEffect(uiState.isEditMode) { if (uiState.isEditMode) { - delay(100) - focusRequester.requestFocus() + // 컴포지션이 완료된 후 포커스 요청 + focusRequester.requestFocus() } }app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt (2)
51-55: 투표 옵션 타입 안전성 개선 필요
options파라미터가List<String>?타입인데, 빈 리스트와 null을 구분해서 처리해야 할 수 있습니다.수정 모드에서 options가 null이거나 빈 리스트인 경우를 명시적으로 처리하는 것이 좋겠습니다.
94-99: 포커스 요청 로직 중복
GroupNoteCreateScreen과 동일한 포커스 로직이 중복되어 있습니다. 공통 컴포저블로 추출을 고려해보세요.// 공통 유틸리티 파일에 추가 @Composable fun RequestFocusOnEditMode( isEditMode: Boolean, focusRequester: FocusRequester ) { LaunchedEffect(isEditMode) { if (isEditMode) { focusRequester.requestFocus() } } }app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupVoteCreateViewModel.kt (3)
81-96: 수정 모드에서 총평(isOverview)일 때 pageText 처리 일관화 제안총평 선택 시 토글 로직(라인 121~127)에서는 pageText를 비우는데, 초기화에서는 항상 page를 문자열로 넣고 있습니다. 초기·토글 동작을 맞추면 UX 혼선을 줄일 수 있습니다.
- pageText = page.toString(), - isGeneralReview = isOverview, + pageText = if (isOverview) "" else page.toString(), + isGeneralReview = isOverview, + savedPageText = if (isOverview) page.toString() else it.savedPageText,
178-179: 하드코딩된 에러 문자열의 리소스화/분리 제안ViewModel 내 사용자 노출 문자열은 다국어/테스트 용이성을 위해 리소스(or StringProvider)로 분리하는 것이 좋습니다.
67-77: initialize 파라미터 수 과다초기화 인자가 많아 가독성이 떨어집니다. 편집 페이로드를 데이터 클래스로 묶는 것을 권장합니다(예: VoteEditPayload).
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (1)
427-428: 매직 넘버(98.dp) 상수화헤더/오버레이 패딩 값이 분산되면 유지보수 비용이 증가합니다. 파일 상단에 상수로 추출해 재사용을 권장합니다.
- .padding(top = 98.dp) + .padding(top = HEADER_OVERLAY_TOP_PADDING)추가 코드(파일 상단 임의 위치):
private val HEADER_OVERLAY_TOP_PADDING = 98.dp
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (23)
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPatchRecordRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPatchVoteRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPatchRecordResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPatchVoteResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPostsResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt(2 hunks)app/src/main/java/com/texthip/thip/data/service/RoomsService.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/common/buttons/GroupVoteButton.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/common/forms/BookPageTextField.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/common/forms/BorderedTextField.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/OpinionInputSection.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/PageInputSection.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/VoteInputSection.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt(6 hunks)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt(6 hunks)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteCreateViewModel.kt(9 hunks)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteViewModel.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupVoteCreateViewModel.kt(7 hunks)app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt(1 hunks)app/src/main/res/values/strings.xml(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt (2)
app/src/main/java/com/texthip/thip/ui/common/topappbar/InputTopAppBar.kt (1)
InputTopAppBar(25-69)app/src/main/java/com/texthip/thip/ui/group/note/component/VoteInputSection.kt (1)
VoteInputSection(34-118)
app/src/main/java/com/texthip/thip/ui/group/note/component/PageInputSection.kt (1)
app/src/main/java/com/texthip/thip/ui/common/buttons/ToggleSwitchButton.kt (1)
ToggleSwitchButton(23-54)
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt (3)
app/src/main/java/com/texthip/thip/ui/common/topappbar/InputTopAppBar.kt (1)
InputTopAppBar(25-69)app/src/main/java/com/texthip/thip/ui/group/note/component/OpinionInputSection.kt (1)
OpinionInputSection(29-90)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteCreateViewModel.kt (1)
onEvent(84-111)
🔇 Additional comments (31)
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPostsResponse.kt (1)
35-40: "percentage" 키 사용 흔적 미발견 – 호환성 매핑은 필요 시 선택적 적용
프로젝트 전체에percentage응답 키 사용이 검색되지 않아 현 시점에선@SerialName/@JsonNames매핑이 필수는 아닙니다. 백엔드가 과거percentage키를 더 이상 사용하지 않는지 확인한 뒤 적용 여부를 결정하세요.app/src/main/java/com/texthip/thip/ui/common/buttons/GroupVoteButton.kt (1)
42-42: 총 투표수 계산 도입 LGTM
sumOf { it.count }로 단순·명확합니다. 데이터 크기가 매우 크지 않다면 성능/오버플로 이슈 우려도 낮습니다.app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPatchRecordResponse.kt (1)
5-8: 응답 모델 초안 LGTM단순/명확하고 직렬화 요건 충족합니다.
app/src/main/java/com/texthip/thip/ui/common/forms/BorderedTextField.kt (1)
41-45: isEnabled 도입 LGTM컴포넌트 인터랙션을 상위에서 제어할 수 있게 되어 일관성 좋아졌습니다.
app/src/main/java/com/texthip/thip/ui/common/forms/BookPageTextField.kt (1)
49-55: API 확장 LGTM
readOnly/showTotalPage추가로 다양한 편집 모드 대응이 쉬워졌습니다.app/src/main/java/com/texthip/thip/ui/group/note/component/OpinionInputSection.kt (2)
32-37: TextFieldValue 전환 및 길이 제한 처리 LGTM입력 상태/커서 정보 보존되고, 최대 길이 제약도 간결합니다.
Also applies to: 51-56
32-37: 이전 시그니처 호출부 없음 확인 전체.kt파일 검사 결과OpinionInputSection(text: String, …)또는onTextChange: (String) -> Unit형태의 구버전 호출부가 검색되지 않았습니다.app/src/main/res/values/strings.xml (1)
44-46: 문구 추가 LGTM편집 플로우의 라벨링이 명확합니다. 다국어 지원 시 해당 키로 확장만 하면 됩니다.
app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPatchVoteRequest.kt (1)
5-8: 간단 PATCH 요청 모델 정의 적절서버 스키마와 일치한다면 그대로 사용해도 무방합니다.
app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt (1)
12-13: 신규 요청 모델 import 추가 OKapp/src/main/java/com/texthip/thip/data/service/RoomsService.kt (5)
9-10: 신규 PATCH 요청 모델 import 적절
29-30: 신규 PATCH 응답 모델 import 적절
42-42: @patch 도입 정상
204-209: 기록 PATCH 엔드포인트 정의 적절기존
records네이밍과 일치해 보입니다.
211-216: 투표 PATCH 경로 네이밍(복수/단수) 불일치 가능성기존 생성/삭제가
vote(단수)인 반면 본 메서드는votes(복수)입니다. 백엔드 스펙과 불일치 시 404 발생합니다.스펙이 단수라면 아래처럼 수정해 주세요:
- @PATCH("rooms/{roomId}/votes/{voteId}") + @PATCH("rooms/{roomId}/vote/{voteId}") suspend fun patchRoomsVote(위 스크립트로 레포 전체의 경로 일관성도 함께 확인해 주세요(Repository 코멘트 참조).
app/src/main/java/com/texthip/thip/ui/navigator/routes/GroupRoutes.kt (2)
51-56: 노트 편집을 위한 파라미터 확장 설계 적절기존 호출부 유지(기본값 null)와 편집 모드 프리필을 동시에 만족합니다.
63-69: 네비게이션 페이로드 크기/직렬화 확인 권장(List 전달)
options: List<String>?를 라우트 인자로 직렬화해 전달하면 항목 수가 많을 때 바인더 한도를 건드릴 수 있습니다. 라이브러리(toRoute)에서 안전하게 Bundle/JSON 변환되는지와 예상 최대 크기를 확인해 주세요. 필요 시 옵션은 ViewModel 측에서postId로 재조회하는 방식이 안전합니다.app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (4)
354-366: 편집 네비게이션 파라미터 주입 OK — 단, 상태 읽기 타이밍 점검클릭 시점에
viewModel.uiState.value를 직접 읽는 패턴은 안전하나, 비동기 로딩 중이면 최신 값이 아닐 수 있습니다. 초기값 요구사항(최근/전체 페이지)이 엄격하면 수집된 상태(collectAsState())에서 파생해 사용하는지 확인해 주세요.
415-424: 노트 편집 라우팅 파라미터 전달 OK선택 탭 복귀 로직(savedStateHandle)과 함께 정상 동작할 것으로 보입니다.
445-450: 투표 편집 라우팅 파라미터 전달 OK
title/options전달로 프리필 요구사항 충족.
367-381: 어떤 서비스의 Rooms PATCH API 스펙을 참고해야 할지 알려주세요.
사내 API 문서 URL 또는 관련 스펙(요청/응답 예시 포함)을 공유해 주시면 투표 옵션(option) 수정 지원 여부와 스키마 키를 확인해 드리겠습니다.app/src/main/java/com/texthip/thip/ui/group/note/component/PageInputSection.kt (1)
113-120: ToggleSwitchButton onToggleChange에서 ‘전체 구간’ 문자열 주입 제거
- 토글 ON 시
onPageTextChange(if (checked) allRangeText else "")호출을 제거하고, 오직 토글 해제 시에만pageText초기화 로직만 수행- 전송 로직에서
isGeneralReview == true일 때page필드를null또는 미포함 처리하는지 확인 필요app/src/main/java/com/texthip/thip/ui/navigator/extensions/GroupNavigationExtensions.kt (3)
139-145: LGTM: 전달 인자 매핑 적정roomId/recentPage/totalPage/isOverviewPossible 및 편집 관련 파라미터가 올바르게 매핑되었습니다.
113-118: GroupRoutes.NoteCreate 시그니처 불일치
NoteCreate 데이터 클래스는roomId,recentBookPage,totalBookPage등을 받으며postId/page/content매개변수는 존재하지 않습니다. 해당 리뷰 코멘트는 적용 대상이 아닙니다.Likely an incorrect or invalid review comment.
127-133: VoteCreate의 options 전달 방식 검증
VoteCreate는 @serializable 데이터 클래스이지만, 네비게이션이 문자열 경로 기반이라면 List? 인자를 JSON 인코딩 등으로 직렬화/역직렬화해 전달해야 합니다. 타입 세이프 네비게이션을 쓰는지, 혹은 NavType.StringArray 등 리스트 전용 NavType을 등록해두었는지 확인하세요.app/src/main/java/com/texthip/thip/ui/group/note/component/VoteInputSection.kt (1)
53-58: 상호작용 제어 흐름은 일관되고 명확합니다외곽 클릭 포커스 해제, 옵션 필드의 isEnabled 연동, 항목 추가 버튼의 노출 조건 모두 의도에 맞게 동작합니다.
Also applies to: 91-93, 96-116
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt (1)
50-53: nullable 파라미터 추가 검증 불필요
GroupRoutes.NoteCreate데이터 클래스에서postId,page,content,isOverview에 모두= null기본값이 정의되어 있고, type-safe navigation 호출부(GroupNavigation.kt)에서 항상 명시적으로 전달되므로 부분적 null 상태가 발생하지 않습니다.Likely an incorrect or invalid review comment.
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt (1)
108-109: 문자열 리소스 정의 확인 완료
R.string.create_vote와R.string.edit_vote가app/src/main/res/values/strings.xml에 정의되어 있어 별도 수정이 필요 없습니다.app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (3)
519-529: 작성자 메뉴에 “수정” 항목 추가 — 동작 적합postType 분기 처리 및 시트 닫기 처리(selectedPostForMenu = null)까지 되어 있어 UX 일관성이 좋습니다.
672-675: 프리뷰 시그니처 반영 완료새 콜백을 프리뷰에서도 전달해 컴파일 안정성을 확보했습니다.
81-83: GroupNoteScreen/Content 호출부 확인 완료
GroupNavigation.kt 및 GroupNoteScreen.kt 내 모든 호출부가 신규 콜백(onEditNoteClick, onEditVoteClick)에 대해 기본값(empty lambda)을 활용하므로 추가 수정 불필요합니다.
| suspend fun patchRoomsVote( | ||
| roomId: Int, | ||
| voteId: Int, | ||
| content: String, | ||
| ) = runCatching { | ||
| roomsService.patchRoomsVote( | ||
| roomId = roomId, | ||
| voteId = voteId, | ||
| request = RoomsPatchVoteRequest( | ||
| content = content, | ||
| ) | ||
| ).handleBaseResponse().getOrThrow() | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
vote PATCH 경로/ID 매핑 재확인 필요
서비스는 "rooms/{roomId}/votes/{voteId}"(복수형) 경로를 사용합니다. 기존 POST/DELETE는 단수형 vote를 사용 중이라 404 위험이 있습니다. 또한 UI에서 전달하는 postId가 실제 voteId와 동일한지 확인이 필요합니다.
아래 스크립트로 현재 경로 일관성을 점검해 주세요:
🏁 Script executed:
#!/bin/bash
# vote vs votes 경로 사용 현황 확인
rg -nP 'rooms/\{roomId\}/votes?/\{voteId\}' -g '!**/build/**'
# voteId로 사용되는 식별자 확인(예: postId를 그대로 쓰는지)
rg -nP '\b(voteId|recordId|postId)\b' app/src/main/java -C2Length of output: 55050
RoomsService POST/DELETE 경로 plural 통일 및 voteId 매핑 검증
- app/src/main/java/com/texthip/thip/data/service/RoomsService.kt (라인 150, 163):
@POST("rooms/{roomId}/vote/{voteId}")/
@DELETE("rooms/{roomId}/vote/{voteId}")→
@POST("rooms/{roomId}/votes/{voteId}")/
@DELETE("rooms/{roomId}/votes/{voteId}")로 변경 - UI에서 사용하는
postId가 백엔드의voteId와 일치하는 값인지 재확인 필요
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt around
lines 336 to 348, ensure the vote endpoint usage and ID mapping align with the
service changes: update any RoomsService POST/DELETE path annotations from
"rooms/{roomId}/vote/{voteId}" to "rooms/{roomId}/votes/{voteId}", confirm that
the repository continues to call the updated service methods, and verify the UI
is passing the backend's voteId (not postId) into patchRoomsVote so the voteId
parameter maps correctly to the backend resource.
| onInfoPositionCaptured: (LayoutCoordinates) -> Unit, | ||
| isEnabled: Boolean = true | ||
| ) { |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
API 시그니처 확장: isEnabled 추가는 합리적입니다.
기본값을 제공해 소스 호환성 유지됩니다. 해당 파라미터 전달 여부를 최근 호출부(특히 편집 모드 진입 시)에서 일관되게 적용됐는지 한 번만 점검하면 됩니다.
호출부 점검 스크립트:
🏁 Script executed:
#!/bin/bash
rg -nP --type=kotlin -C2 '\bPageInputSection\(' app/src | sed -E 's/^/HIT: /'Length of output: 2596
API 시그니처 확장: isEnabled 파라미터 전달 여부 확인
현재 GroupVoteCreateScreen.kt (라인 122)와 GroupNoteCreateScreen.kt (라인 125)에서 PageInputSection이 isEnabled 인자 없이 호출되어 디폴트(true)를 사용 중입니다. 에디팅 모드 진입 시 비활성화가 필요하다면 isEnabled = false를 명시적으로 넘겨주세요.
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/component/PageInputSection.kt
around lines 43-45 the function adds an isEnabled: Boolean = true parameter but
callers GroupVoteCreateScreen.kt (line 122) and GroupNoteCreateScreen.kt (line
125) currently rely on the default true; update those call sites to explicitly
pass isEnabled = false when entering editing/disabled modes (or pass the
appropriate boolean state variable) so the component is correctly disabled, and
ensure any other callers that should reflect non-editable state are updated
likewise.
| focusRequester: FocusRequester = remember { FocusRequester() }, | ||
| ) { |
There was a problem hiding this comment.
기본 파라미터에서 remember 사용은 컴파일 에러
기본값에 remember를 사용할 수 없습니다(Composable 호출 불가). Nullable 파라미터로 받고 내부에서 remember로 대체하세요. 또한 .focusRequester(...) 사용부를 대체 변수로 교체해야 합니다.
- maxOptions: Int = 5,
- focusRequester: FocusRequester = remember { FocusRequester() },
+ maxOptions: Int = 5,
+ focusRequester: FocusRequester? = null,함수 본문 내 보완(추가 코드):
val resolvedFocusRequester = focusRequester ?: remember { FocusRequester() }사용부 교체:
- .focusRequester(focusRequester),
+ .focusRequester(resolvedFocusRequester),Also applies to: 67-67
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/component/VoteInputSection.kt
around lines 46-47 (and also line 67), the parameter currently uses remember as
a default which causes a compile error because remember cannot be called in
parameter defaults; change the parameter type to nullable (FocusRequester?) and
remove the remember default, then inside the function create a
resolvedFocusRequester with val resolvedFocusRequester = focusRequester ?:
remember { FocusRequester() } and replace all usages of .focusRequester(...) or
references to the original parameter with resolvedFocusRequester so the
composable uses the remembered instance when none is provided.
| value = titleValue, | ||
| onValueChange = { if (it.text.length <= maxOptionLength) onTitleChange(it) }, | ||
| textStyle = typography.smalltitle_m500_s18_h24.copy(color = colors.White), | ||
| modifier = Modifier | ||
| .fillMaxWidth(), | ||
| .fillMaxWidth() | ||
| .focusRequester(focusRequester), | ||
| cursorBrush = SolidColor(colors.NeonGreen), |
There was a problem hiding this comment.
isEnabled=false에서도 제목 입력이 가능함
BasicTextField에 enabled/readOnly가 전달되지 않아 비활성화 상태에서도 편집이 가능합니다. 아래처럼 막아주세요.
- BasicTextField(
+ BasicTextField(
value = titleValue,
onValueChange = { if (it.text.length <= maxOptionLength) onTitleChange(it) },
+ enabled = isEnabled,
+ readOnly = !isEnabled,
textStyle = typography.smalltitle_m500_s18_h24.copy(color = colors.White),
modifier = Modifier
.fillMaxWidth()
- .focusRequester(focusRequester),
+ .focusRequester(resolvedFocusRequester),Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/component/VoteInputSection.kt
around lines 62–68, the BasicTextField currently allows editing even when
isEnabled is false; pass the proper flags to disable input by adding enabled =
isEnabled and readOnly = !isEnabled to the text field call (or if BasicTextField
variant used here doesn't support enabled, switch to the TextField API or wrap
the field with a read-only/consuming pointer modifier) so that when isEnabled is
false the user cannot edit the title.
| if (postId != null && page != null && content != null && isOverview != null) { | ||
| this.postIdToEdit = postId | ||
| _uiState.update { | ||
| it.copy( | ||
| isEditMode = true, | ||
| pageText = page.toString(), | ||
| isGeneralReview = isOverview, | ||
| opinionTextFieldValue = TextFieldValue( | ||
| text = content, | ||
| selection = TextRange(content.length) | ||
| ) | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
수정 모드 진입 조건 개선 필요
현재 모든 파라미터가 null이 아닌 경우에만 수정 모드로 진입하는데, isOverview는 false일 수 있으므로 null 체크가 불필요합니다. 또한 content가 빈 문자열인 경우도 유효한 케이스일 수 있습니다.
- // postId가 null이 아니면 수정 모드로 진입
- if (postId != null && page != null && content != null && isOverview != null) {
- this.postIdToEdit = postId
+ // postId가 null이 아니면 수정 모드로 진입
+ if (postId != null && page != null && content != null) {
+ this.editingPostId = postId
_uiState.update {
it.copy(
isEditMode = true,
pageText = page.toString(),
- isGeneralReview = isOverview,
+ isGeneralReview = isOverview ?: false,
opinionTextFieldValue = TextFieldValue(
text = content,
selection = TextRange(content.length)
)
)
}
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteCreateViewModel.kt
around lines 68–81, the current guard requires postId, page, content and
isOverview all non-null which wrongly rejects valid cases where isOverview is
false or content is an empty string; change the entry condition to only require
postId and page non-null, then inside the block treat isOverview and content
defensively (e.g., use isOverview ?: false for the boolean and content ?: ""
when populating TextFieldValue, and set selection based on the resolved content
length) while still setting isEditMode and postIdToEdit as before.
| private fun updateRecord() { | ||
| viewModelScope.launch { | ||
| _uiState.update { it.copy(isLoading = true) } | ||
| val content = _uiState.value.opinionTextFieldValue.text | ||
| val postId = postIdToEdit ?: return@launch | ||
|
|
||
| roomsRepository.patchRoomsRecord( | ||
| roomId = roomId, | ||
| recordId = postId, | ||
| content = content | ||
| ).onSuccess { | ||
| _uiState.update { it.copy(isLoading = false, isSuccess = true) } | ||
| }.onFailure { throwable -> | ||
| _uiState.update { it.copy(isLoading = false, error = throwable.message) } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
에러 처리 로직 개선 필요
postIdToEdit가 null인 경우 return@launch로 조용히 종료하는 대신, 에러 상태를 업데이트해야 합니다.
private fun updateRecord() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
val content = _uiState.value.opinionTextFieldValue.text
- val postId = postIdToEdit ?: return@launch
+ val postId = editingPostId
+ if (postId == null) {
+ _uiState.update {
+ it.copy(isLoading = false, error = "수정할 기록을 찾을 수 없습니다.")
+ }
+ return@launch
+ }
roomsRepository.patchRoomsRecord(
roomId = roomId,
recordId = postId,
content = content
).onSuccess {
_uiState.update { it.copy(isLoading = false, isSuccess = true) }
}.onFailure { throwable ->
_uiState.update { it.copy(isLoading = false, error = throwable.message) }
}
}
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupNoteCreateViewModel.kt
around lines 144-160, the code silently returns when postIdToEdit is null which
leaves isLoading=true and no error state; change the logic to check postIdToEdit
before or immediately after setting isLoading and, if null, update _uiState to
set isLoading=false and set an appropriate error (e.g., "Missing postIdToEdit")
and return; ensure isSuccess is false in that branch so the UI reflects the
failure.
| refreshAllData() | ||
| } |
There was a problem hiding this comment.
async/await가 효과 없음 — 내부에서 다시 launch되어 대기되지 않습니다
loadPosts()와 loadBookPageInfo() 둘 다 내부에서 viewModelScope.launch를 사용하므로, 여기서 async { ... }로 감싸도 즉시 반환되어 awaitAll이 실질적으로 아무 것도 대기하지 않습니다. 의도와 달리 동시 실행/대기가 이뤄지지 않으므로 정정이 필요합니다.
권장 수정안 1(가장 간단): 두 함수를 직접 호출(이미 내부에서 launch).
- private fun refreshAllData() {
- viewModelScope.launch {
- val postsJob = async { loadPosts(isRefresh = true) }
- val bookPageJob = async { loadBookPageInfo() }
- awaitAll(postsJob, bookPageJob)
- }
- }
+ private fun refreshAllData() {
+ // 두 함수가 자체적으로 viewModelScope.launch를 사용하므로 호출만으로 충분
+ loadPosts(isRefresh = true)
+ loadBookPageInfo()
+ }권장 수정안 2(진짜 동시 실행/대기): 두 함수를 suspend로 바꾸고 내부 launch 제거 후 coroutineScope에서 launch/join 혹은 async/awaitAll로 병렬화.
추가 제안(선택): 새로고침 시 nextCursor를 명시적으로 초기화.
private fun loadPosts(isRefresh: Boolean = false) {
val currentState = _uiState.value
if (currentState.isLoading || currentState.isLoadingMore || (currentState.isLastPage && !isRefresh)) return
viewModelScope.launch {
+ if (isRefresh) nextCursor = nullAlso applies to: 123-129
| private fun updateVote() { | ||
| val currentState = _uiState.value | ||
| val postId = currentState.postId ?: return | ||
|
|
||
| viewModelScope.launch { | ||
| _uiState.update { it.copy(isLoading = true) } | ||
| roomsRepository.patchRoomsVote( | ||
| roomId = roomId, | ||
| voteId = postId, | ||
| content = currentState.titleValue.text | ||
| ).onSuccess { | ||
| _uiState.update { it.copy(isLoading = false, isSuccess = true) } | ||
| }.onFailure { throwable -> | ||
| _uiState.update { it.copy(isLoading = false, error = throwable.message) } | ||
| } | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
투표 수정 시 옵션 변경사항이 서버에 반영되지 않음
현재 patchRoomsVote 호출에 content만 전달합니다. 사용자가 옵션을 추가/삭제/수정해도 서버로 전송되지 않아 수정 내용이 유실됩니다. 또한 입력 검증과 상태 초기화도 필요합니다.
private fun updateVote() {
val currentState = _uiState.value
val postId = currentState.postId ?: return
-
- viewModelScope.launch {
- _uiState.update { it.copy(isLoading = true) }
- roomsRepository.patchRoomsVote(
- roomId = roomId,
- voteId = postId,
- content = currentState.titleValue.text
- ).onSuccess {
- _uiState.update { it.copy(isLoading = false, isSuccess = true) }
- }.onFailure { throwable ->
- _uiState.update { it.copy(isLoading = false, error = throwable.message) }
- }
- }
+ // 간단 입력 검증: 제목 공백 및 옵션 2개 미만 방지
+ val filledOptionsCount = currentState.options.count { it.isNotBlank() }
+ if (currentState.titleValue.text.isBlank() || filledOptionsCount < 2) {
+ _uiState.update { it.copy(error = "제목과 최소 2개의 옵션을 입력해주세요.") }
+ return
+ }
+
+ val voteItems = currentState.options
+ .filter { it.isNotBlank() }
+ .map { VoteItem(itemName = it) }
+
+ viewModelScope.launch {
+ _uiState.update { it.copy(isLoading = true, isSuccess = false, error = null) }
+ roomsRepository.patchRoomsVote(
+ roomId = roomId,
+ voteId = postId,
+ content = currentState.titleValue.text,
+ voteItemList = voteItems
+ ).onSuccess {
+ _uiState.update { it.copy(isLoading = false, isSuccess = true) }
+ }.onFailure { throwable ->
+ _uiState.update { it.copy(isLoading = false, isSuccess = false, error = throwable.message) }
+ }
+ }
}다음 스크립트로 Repository/Service/Request 시그니처를 확인해 주세요. patchRoomsVote가 voteItemList(또는 유사 필드)를 지원하는지 점검이 필요합니다.
🏁 Script executed:
#!/bin/bash
# 1) patchRoomsVote 시그니처와 요청 모델 확인
rg -nP -C3 --type=kt -g '!**/build/**' '\bpatchRoomsVote\s*\('
fd -a -t f -e kt 'RoomsPatchVoteRequest|RoomsPatchVoteResponse|RoomsRepository|RoomsService|VoteItem' 2>/dev/null | sed 's|^|--- |'
rg -nP -C3 --type=kt -g '!**/build/**' '\bdata\s+class\s+RoomsPatchVoteRequest\b|\binterface\s+RoomsService\b|\bclass\s+RoomsRepository\b'Length of output: 451
🏁 Script executed:
#!/bin/bash
# Inspect RoomsRepository for patchRoomsVote signature
echo "---- RoomsRepository.kt ----"
sed -n '1,200p' app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt
grep -nR "patchRoomsVote" -n app/src/main/java/com/texthip/thip/data/repository/RoomsRepository.kt || true
# Inspect RoomsService for patchRoomsVote
echo "---- RoomsService.kt ----"
sed -n '1,200p' app/src/main/java/com/texthip/thip/data/service/RoomsService.kt
grep -nR "patchRoomsVote" -n app/src/main/java/com/texthip/thip/data/service/RoomsService.kt || true
# Inspect RoomsPatchVoteRequest to see available fields
echo "---- RoomsPatchVoteRequest.kt ----"
sed -n '1,200p' app/src/main/java/com/texthip/thip/data/model/rooms/request/RoomsPatchVoteRequest.ktLength of output: 15045
patchRoomsVote에 옵션 목록 전송 로직 추가 필요
RoomsPatchVoteRequest에 변경된 옵션을 담을 voteItems(List) 필드를 추가하고, RoomsService.patchRoomsVote @patch 메서드와 RoomsRepository.patchRoomsVote 시그니처에 voteItems 파라미터를 반영한 뒤 ViewModel의 updateVote에서 전달하도록 수정하세요. 입력 검증(제목 빈값, 옵션 2개 미만) 로직은 그대로 유지해야 합니다.
➕ 이슈 링크
🔎 작업 내용
📸 스크린샷
투표 수정
1000032984.mp4
기록 수정
1000032985.mp4
Summary by CodeRabbit