Conversation
Walkthrough모델에 isPublic/isSaved/isLiked 필드가 추가되고, FeedStateUpdateResult에 commentCount가 도입되었습니다. 피드 좋아요/저장 갱신이 양 리스트(all/my)에 동기화되며, SavedStateHandle을 통한 업데이트 전파가 추가되었습니다. CardItemRoom에 isSecret 파라미터가 생겨 비공개 뱃지를 오버레이합니다. CardBookList는 onClick 콜백을 받도록 변경되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant C as CommentsViewModel
participant S as Server
participant D as FeedDetailViewModel
participant FCS as FeedCommentScreen
participant SSH as SavedStateHandle
U->>C: 댓글 작성 요청
C->>S: createComment(payload)
S-->>C: 성공 응답(댓글)
C->>C: state.comments 업데이트<br/>isCommentCreated = true
C-->>FCS: state 변화
FCS->>D: (LaunchedEffect) loadFeedDetail(feedId)
D-->>FCS: detail(commentCount 등)
FCS->>SSH: updated_feed_commentCount = detail.commentCount
FCS->>C: resetCommentCreatedState()
sequenceDiagram
autonumber
actor U as User
participant VM as FeedViewModel
participant Repo as FeedRepository
participant S as Server
U->>VM: changeFeedLike(feedId)
VM->>VM: all/my 리스트 낙관적 토글<br/>likeCount 조정
VM->>Repo: ChangeFeedLikeUseCase(feedId, current 상태)
Repo->>S: API 호출
alt 성공
S-->>Repo: OK
Repo-->>VM: FeedStateUpdateResult(isLiked, likeCount, isSaved, commentCount)
VM->>VM: all/my 리스트에 결과 반영
else 실패
S-->>Repo: Error
Repo-->>VM: Error
VM->>VM: 이전 상태로 롤백
end
sequenceDiagram
autonumber
participant FS as FeedScreen
participant SSH as SavedStateHandle
participant VM as FeedViewModel
FS->>SSH: updated_* 값 읽기<br/>(feedId, isLiked, likeCount, isSaved, commentCount)
alt 값 존재
FS->>VM: updateFeedStateFromResult(FeedStateUpdateResult)
FS->>SSH: updated_* 키 삭제
else 없음
FS->>VM: 필요 시 데이터 로드/리프레시
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (2)
124-132: observeForever 사용은 메모리 누수 및 중복 수신 위험 — getStateFlow로 교체하세요.Compose 범위에서 LiveData.observeForever는 해제되지 않아 누수/중복 콜백 위험이 큽니다. SavedStateHandle.getStateFlow를 사용해 수명에 안전하게 수집하도록 바꾸세요.
- LaunchedEffect(Unit) { - navController.currentBackStackEntry?.savedStateHandle?.let { handle -> - handle.getLiveData<Long>("deleted_feed_id").observeForever { deletedId -> - if (deletedId != null) { - feedViewModel.removeDeletedFeed(deletedId) - handle.remove<Long>("deleted_feed_id") - } - } - } - } + LaunchedEffect(navController) { + navController.currentBackStackEntry?.savedStateHandle?.let { handle -> + handle.getStateFlow<Long?>("deleted_feed_id", null).collect { deletedId -> + deletedId?.let { + feedViewModel.removeDeletedFeed(it) + handle["deleted_feed_id"] = null + } + } + } + }
191-218: updated_ 이벤트에도 observeForever 사용 — 동일하게 StateFlow로 마이그레이션 필요.*위와 동일한 이유로 누수/중복 위험이 있습니다. getStateFlow를 사용해 수집하고, 처리 후 null로 되돌려 재발행을 제어하세요.
- LaunchedEffect(Unit) { //커스텀객체 타입 인식오류 -> 직렬화가 아닌 잘게 쪼개어 전달 - navController.currentBackStackEntry?.savedStateHandle?.let { handle -> - handle.getLiveData<Long>("updated_feed_id").observeForever { feedId -> - if (feedId != null) { - val isLiked = handle.get<Boolean>("updated_feed_isLiked") ?: false - val likeCount = handle.get<Int>("updated_feed_likeCount") ?: 0 - val isSaved = handle.get<Boolean>("updated_feed_isSaved") ?: false - val commentCount = handle.get<Int>("updated_feed_commentCount") ?: 0 - - val result = FeedStateUpdateResult( - feedId = feedId, - isLiked = isLiked, - likeCount = likeCount, - isSaved = isSaved, - commentCount = commentCount - ) - - feedViewModel.updateFeedStateFromResult(result) - - handle.remove<Long>("updated_feed_id") - handle.remove<Boolean>("updated_feed_isLiked") - handle.remove<Int>("updated_feed_likeCount") - handle.remove<Boolean>("updated_feed_isSaved") - handle.remove<Int>("updated_feed_commentCount") - } - } - } - } + LaunchedEffect(navController) { // 커스텀 객체 직렬화 이슈 회피: 키 분해 전달 유지 + navController.currentBackStackEntry?.savedStateHandle?.let { handle -> + handle.getStateFlow<Long?>("updated_feed_id", null).collect { feedId -> + feedId?.let { + val result = FeedStateUpdateResult( + feedId = it, + isLiked = handle.get<Boolean>("updated_feed_isLiked") ?: false, + likeCount = handle.get<Int>("updated_feed_likeCount") ?: 0, + isSaved = handle.get<Boolean>("updated_feed_isSaved") ?: false, + commentCount = handle.get<Int>("updated_feed_commentCount") ?: 0 + ) + feedViewModel.updateFeedStateFromResult(result) + handle["updated_feed_id"] = null + handle["updated_feed_isLiked"] = null + handle["updated_feed_likeCount"] = null + handle["updated_feed_isSaved"] = null + handle["updated_feed_commentCount"] = null + } + } + } + }
🧹 Nitpick comments (21)
app/src/main/java/com/texthip/thip/ui/search/component/SearchRecentBook.kt (1)
133-137: 마지막 아이템 여백은 LazyColumn의 contentPadding으로 일원화 권장마지막 아이템에서만
Spacer(padding = 20.dp)를 두는 분기는 동작상 문제는 없으나, 아이템 분기 로직이 복잡해지고 불필요한 측정이 추가됩니다.LazyColumn에contentPadding을 주면 동일 효과를 더 간결하게 얻을 수 있습니다. 또한 리스트 길이가 변해도 바닥 여백이 일관되게 유지되어 스크롤 유지 측면에서도 안전합니다.아래처럼 수정을 제안드립니다.
- LazyColumn( - modifier = Modifier.fillMaxSize() - ) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(bottom = 20.dp) + ) { items(popularBooks.size) { index -> val book = popularBooks[index] CardBookSearch( number = index + 1, title = book.title, imageUrl = book.imageUrl, onClick = { onBookClick(book) } ) if (index < popularBooks.size - 1) { Spacer( modifier = Modifier .padding(top = 12.dp, bottom = 12.dp) .fillMaxWidth() .height(1.dp) .background(colors.DarkGrey02) ) - } else { - Spacer( - modifier = Modifier - .padding(bottom = 20.dp) - ) } } }추가로 필요한 import:
+import androidx.compose.foundation.layout.PaddingValuesapp/src/main/java/com/texthip/thip/ui/feed/mock/FeedStateUpdateResult.kt (1)
10-11: 기본값 추가하여 옵셔널 필드로 전환 권장검증 결과
FeedStateUpdateResult생성자 호출부는 모두 명시적으로isSaved와commentCount값을 전달하고 있어, 아래와 같이 기본값을 추가해도 기존 동작에 전혀 영향이 없습니다.
또한,@Serializable클래스에 기본값을 두면 SavedStateHandle/JSON 역직렬화 시 해당 키가 누락되더라도 안전하게 디폴트 값이 적용되어 직렬화 호환성을 높일 수 있습니다.수정 대상 위치:
- app/src/main/java/com/texthip/thip/ui/feed/mock/FeedStateUpdateResult.kt:6
권장하는 변경(diff):
data class FeedStateUpdateResult( val feedId: Long, - val isSaved: Boolean, - val commentCount: Int + val isSaved: Boolean = false, + val commentCount: Int = 0 )app/src/main/java/com/texthip/thip/ui/search/component/SearchActiveField.kt (1)
40-48: 무한 스크롤 트리거의 안정성 개선 제안
remember(hasMore, isLoading)만 키로 쓰면listState교체 시 파생 상태 인스턴스가 재생성되지 않아 미묘한 타이밍 이슈가 날 수 있습니다. 키에listState를 포함하는 것을 권장합니다. 또한 중복 호출 방지에snapshotFlow { shouldLoadMore }.distinctUntilChanged()패턴도 고려 가능.- val shouldLoadMore by remember(hasMore, isLoading) { + val shouldLoadMore by remember(listState, hasMore, isLoading) { derivedStateOf { val layoutInfo = listState.layoutInfo val totalItemsCount = layoutInfo.totalItemsCount val lastVisibleItemIndex = (layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1 hasMore && !isLoading && totalItemsCount > 0 && lastVisibleItemIndex >= totalItemsCount - 3 } }추가로, 현재 파일에서는
import androidx.compose.foundation.clickable가 사용되지 않는 것으로 보입니다. 린트 경고가 있다면 제거해 주세요.app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt (3)
47-49: isSecret 파라미터는 nullable 대신 기본값 false가 단순합니다모든 호출부가 isPublic 기반으로 명확히 결정되고 있어 Boolean? 보다는 Boolean = false가 사용성과 가독성에 유리합니다.
-fun CardItemRoom( +fun CardItemRoom( ... - hasBorder: Boolean = false, - isSecret: Boolean? = null, + hasBorder: Boolean = false, + isSecret: Boolean = false, onClick: () -> Unit = {} )
78-87: 커버 이미지 모서리 클리핑 및 Placeholders 추가 제안현재 Box는 둥근 모서리 없이 이미지를 꽉 채워 렌더링합니다. 카드의 라운드와 시각적으로 일치하도록 이미지와 오버레이에 동일한 클립을 적용하고, 빈 문자열 대비도 처리하면 UX가 좋아집니다.
적용 diff:
- Box( - modifier = Modifier.size(width = 80.dp, height = 107.dp) - ) { - AsyncImage( - model = imageUrl ?: R.drawable.img_book_cover_sample, - contentDescription = "책 이미지", - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop - ) + Box( + modifier = Modifier.size(width = 80.dp, height = 107.dp) + ) { + AsyncImage( + model = (imageUrl?.takeIf { it.isNotBlank() } ?: R.drawable.img_book_cover_sample), + contentDescription = stringResource(R.string.cd_book_cover), // 문자열 리소스 필요 + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(8.dp)), + contentScale = ContentScale.Crop + )추가 필요(파일 상단 import):
import androidx.compose.ui.draw.clip
88-93: 비밀 배지 접근성/현지화 정리오버레이 이미지는 장식 목적이라면 contentDescription을 null로 두고, 대체 텍스트(예: "비밀방")는 상위 컨테이너에 semantics로 부여하는 편이 스크린리더 경험에 적합합니다. 하드코딩 문자열도 stringResource로 교체해주세요.
적용 diff:
- if (isSecret == true) { - Image( - painter = painterResource(id = R.drawable.ic_secret_cover), - contentDescription = "비밀방", - modifier = Modifier.fillMaxSize() - ) - } + if (isSecret == true) { + Image( + painter = painterResource(id = R.drawable.ic_secret_cover), + contentDescription = null, + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(8.dp)) + ) + }추가로 Box 또는 Card에 semantics 부여(범위 외 보조 코드):
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics // Box modifier에 예시로 추가 Box( modifier = Modifier .size(width = 80.dp, height = 107.dp) .semantics { if (isSecret == true) contentDescription = stringResource(R.string.cd_secret_room) } ) { ... }app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt (1)
210-212: Preview 데이터에 isPublic 포함 OK시나리오(공개/비공개)별 렌더링 점검에 도움이 됩니다. 필요시 비밀 배지 노출 케이스도 추가 스냅샷 해두면 회귀에 유용합니다.
Also applies to: 220-222, 230-232, 240-242, 250-252, 260-262
app/src/main/java/com/texthip/thip/ui/search/component/SearchBookFilteredResult.kt (1)
3-5: 사용되지 않는 import 정리clickable을 더 이상 사용하지 않으므로 제거해주세요. Lint 경고 예방용입니다.
-import androidx.compose.foundation.clickableapp/src/main/java/com/texthip/thip/data/model/book/response/RecruitingRoomsResponse.kt (1)
21-23: isPublic 기본값(true) 결정 근거 및 모델 간 일관성 확인 요청
- RecruitingRoomsResponse.kt (21–23행)
현재@SerialName("isPublic") val isPublic: Boolean = true로 정의되어 있습니다. 서버에서 이 필드를 누락할 경우 “공개”로 간주되어 의도치 않은 노출이 발생할 수 있습니다.- 다른 Response 모델들과 비교해 보면
- 일부 모델(e.g. CompletedRoomsResponse.kt)에서는
val isPublic: Boolean? = null처럼 nullable로 선언하거나- 기본값 없이 non-nullable
val isPublic: Boolean만 두어 서버 값을 그대로 사용하고 있습니다.
(검색 결과: 여러 Response 클래스에서isPublic정의 방식을 다양하게 사용 중임 확인)따라서 아래 사항을 확인 부탁드립니다:
- 백엔드 API 계약 상
isPublic필드가 항상 명시적으로 내려오는지 검증- 만약 누락 가능 이라면
- DTO에서는
Boolean? = null처럼 nullable로 받고,- 도메인 계층에서
true/false기본값을 결정하는 방식 고려- 또는 kotlinx.serialization의
@EncodeDefault등 기본값 직렬화 전략 활용 검토- 모델 간
isPublic처리 방식을 통일하여 혼선을 방지하도록 리팩터링 검토app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)
118-124: 댓글 생성 시 재로딩 로직에 finally로 플래그 리셋을 보강하세요네트워크 예외가 발생하더라도
isCommentCreated플래그가 반드시 원복되도록 보장하는 편이 안전합니다. 또한 다중 탭으로 인한 중복 호출을 줄이는 효과도 있습니다.- LaunchedEffect(commentsUiState.isCommentCreated) { - if (commentsUiState.isCommentCreated) { - feedDetailViewModel.loadFeedDetail(feedId) - commentsViewModel.resetCommentCreatedState() - } - } + LaunchedEffect(commentsUiState.isCommentCreated) { + if (commentsUiState.isCommentCreated) { + try { + feedDetailViewModel.loadFeedDetail(feedId) + } finally { + // 실패/성공과 무관하게 플래그를 원복해 재호출 루프를 방지 + commentsViewModel.resetCommentCreatedState() + } + } + }참고: 생성 직후 댓글 수만 동기화가 필요하다면 전체 상세 재호출 대신, 로컬로
commentCount + 1을 저장 상태로 전파(예: SavedStateHandle → FeedViewModel)하는 경량화도 고려 가능합니다.app/src/main/java/com/texthip/thip/ui/group/done/screen/GroupDoneScreen.kt (1)
146-148: MyRoomResponse에 isPublic 필드 반영 확인 완료
app/src/main/java/com/texthip/thip/data/model/rooms/response/MyRoomListResponse.kt에서MyRoomResponse데이터 클래스에 다음과 같이isPublic: Boolean이 정의되어 있습니다. 더 이상의 검증은 필요 없습니다.
- 22행:
@SerialName("isPublic") val isPublic: Boolean💡 니트픽 제안
완료 리스트에서는isRecruiting=false가 고정이라면
RoomUtils.isRecruitingByType(room.type)대신
상수false를 넘겨 구성요소 간 결합을 줄일 수 있습니다.
(적용 대상: 146–148, 156–158, 166–168, 176–178, 186–188)app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt (1)
261-265: 좋아요 수 계산 음수 방지동시성/상태 불일치로
currentLikeCount가 0일 때 언라이크하면 음수가 될 수 있습니다. 최소 0으로 보정하세요.- val newLikeCount = if (it.isLiked) currentLikeCount + 1 else currentLikeCount - 1 + val newLikeCount = (if (it.isLiked) currentLikeCount + 1 else currentLikeCount - 1) + .coerceAtLeast(0)app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookGroupScreen.kt (1)
216-217: 모집 카드에 isSecret 전달 적용 👍
!item.isPublic로 비공개 상태를 명확히 반영했습니다. 동일 패턴을 사용하는 다른 화면들과도 일관됩니다.가독성을 위해 루프 내에서
val isSecret = !item.isPublic로 한 번 계산해 넘기는 방식도 고려 가능합니다.app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (1)
22-24: UiState에 이벤트성 플래그를 두는 패턴은 재입장/프로세스 복원 시 미세 이슈가 있을 수 있습니다
isCommentCreated는 일회성 이벤트에 가깝습니다. 장기적으로는SharedFlow(또는Channel기반)로 “댓글 생성됨” 이벤트를 별도로 발행하고, 화면에서 collect하는 패턴이 더 견고합니다. 현재 구현은 동작상 문제 없으나 전환을 권장드립니다.app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (5)
363-364: StateFlow 업데이트 방식 일관화 권장 (updateState로 통일).동일 파일 내에서 updateState와 _uiState.update가 혼용되고 있습니다. 동일한 헬퍼를 사용해 가독성과 유지보수성을 높이는 것이 좋습니다.
- _uiState.update { it.copy(allFeeds = newAllFeeds, myFeeds = newMyFeeds) } + updateState { it.copy(allFeeds = newAllFeeds, myFeeds = newMyFeeds) }- .onFailure { - _uiState.update { it.copy(allFeeds = currentAllFeeds, myFeeds = currentMyFeeds) } - } + .onFailure { + updateState { it.copy(allFeeds = currentAllFeeds, myFeeds = currentMyFeeds) } + }- ).onFailure { - _uiState.update { it.copy(allFeeds = currentAllFeeds, myFeeds = currentMyFeeds) } - } + ).onFailure { + updateState { it.copy(allFeeds = currentAllFeeds, myFeeds = currentMyFeeds) } + }Also applies to: 379-380, 429-430
332-339: feedId 변환 중복 호출 정리로 미세 최적화 및 가독성 개선.동일 블록에서 toLong 변환이 반복됩니다. 한 번만 변환해 지역 변수로 사용하면 안전하고 읽기 쉽습니다.
- val allFeedToUpdate = currentAllFeeds.find { it.feedId.toLong() == feedId } - val myFeedToUpdate = currentMyFeeds.find { it.feedId.toLong() == feedId } + val targetId = feedId + val allFeedToUpdate = currentAllFeeds.find { it.feedId.toLong() == targetId } + val myFeedToUpdate = currentMyFeeds.find { it.feedId.toLong() == targetId }
283-287: refreshData는 현재 직렬 실행입니다 — 네트워크 대기 시간을 줄이려면 병렬화/일관화 고려.refreshAllFeeds()와 refreshMyFeeds()를 순차 호출하여 총 대기 시간이 길어질 수 있습니다. 두 호출을 병렬화하거나(예: coroutineScope + async) 혹은 ViewModel init/화면 최초 진입 로직 중 하나만 단일 진입점으로 두는 방식으로 단순화하는 것을 권장합니다. 현 PR의 범위를 넘는 변경이므로 선택 사항으로 남깁니다.
해당 변경을 적용할 경우, 도메인 레이어의 API 제약(동시 호출 허용 여부)을 확인해 주세요.
434-462: 양 리스트(all/my) 동기화 및 commentCount 전파 로직은 명확하고 일관적입니다.서버/상세화면에서 넘어온 FeedStateUpdateResult를 기반으로 두 리스트를 동시에 갱신하는 접근이 합리적입니다. 불필요한 재조합을 줄이려면 실제 변경이 없는 경우 early-return을 추가하는 것도 고려해볼 수 있습니다.
290-311: isLoading을 ‘최근 작성자’ 로딩에도 재사용 — 초기 스피너 제어 상의 미묘한 결합.최근 작성자 로딩에도 isLoading을 사용하고 있어(라인 292/306), 초기 진입/탭 전환 시 스피너 표시에 간접 영향이 생길 수 있습니다. UI가 명확하다면 그대로도 가능하나, 필요 시 recentWritersLoading 같은 전용 플래그로 분리하면 의도가 더 분명해집니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (2)
106-122: 무한 스크롤 트리거 계산은 동작하나, snapshotFlow로 스크롤 신호만 관찰하면 불필요한 recomposition을 더 줄일 수 있습니다.현재 derivedStateOf로 계산하고 LaunchedEffect(shouldLoadMore)로 로딩을 트리거합니다. 성능상 문제는 크지 않지만, 리스트 스크롤 신호만 추출해 snapshotFlow { currentListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index }로 수집하면 키 변경에 따른 재계산을 더 구체적으로 제어할 수 있습니다.
143-153: Feed 초기 로딩 중복 가능성 확인 및 단일 진입점으로 정리 제안현재 FeedViewModel init 블록에서 loadAllFeeds() 를 호출하는 한편, FeedScreen 에서도 진입 시 데이터가 비어 있으면 refreshData() 를 다시 호출하고 있어 초기 진입 시 API가 중복 요청될 수 있습니다.
- FeedViewModel.kt init 블록 (67–70줄): loadAllFeeds(), fetchRecentWriters() 호출
- FeedViewModel.kt refreshData 메서드 (282–286줄): refreshAllFeeds(), refreshMyFeeds(), fetchRecentWriters() 순으로 재호출
- FeedScreen.kt 초기 진입 로직 (143–147줄): allFeeds와 myFeeds가 비었으면 refreshData() 실행
제안:
- UI(Screen) 진입부에서만 초기 로드를 담당하거나,
- ViewModel init에서만 초기 로드를 수행하도록 통일
- 또는 ViewModel에서 “초기 로드 완료” 상태를 노출하여 UI가 중복 호출을 방지하도록 조건을 명확히 분리
📜 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 (18)
app/src/main/java/com/texthip/thip/data/model/book/response/RecruitingRoomsResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/feed/response/MyFeedResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/rooms/response/MyRoomListResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/feed/mock/FeedStateUpdateResult.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedMyScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt(15 hunks)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/done/screen/GroupDoneScreen.kt(6 hunks)app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt(7 hunks)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/search/component/SearchActiveField.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/search/component/SearchBookFilteredResult.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/search/component/SearchRecentBook.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookGroupScreen.kt(2 hunks)app/src/main/res/values/strings.xml(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (3)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (1)
updateState(40-42)app/src/main/java/com/texthip/thip/ui/group/viewmodel/GroupViewModel.kt (1)
updateState(33-35)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (1)
updateState(35-37)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (3)
app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt (1)
hexToColor(6-13)app/src/main/java/com/texthip/thip/ui/feed/component/FeedSubscribelistBar.kt (1)
FeedSubscribeBarlist(35-101)app/src/main/java/com/texthip/thip/ui/feed/component/MyFeedCard.kt (1)
MyFeedCard(38-187)
🔇 Additional comments (19)
app/src/main/res/values/strings.xml (1)
319-319: 닉네임 검증 로직 위치 및 일관성 확인 필요코드베이스 내에서 닉네임 입력을 실제로 필터링하거나 정규식으로 검증하는 로직(Pattern/Regex/InputFilter)을 찾을 수 없습니다. 서버 측 검증 규칙(한글·영문 소문자·숫자 허용 여부, 길이 제한 등)과 클라이언트 UI의 안내 문구 및 구현이 일치하는지 직접 점검해 주세요.
– app/src/main/res/values/strings.xml:319 (
nickname_condition= “한글/영문소문자/숫자로 구성”)
– app/src/main/java/.../SignupNicknameScreen.kt:WarningTextField에maxLength미설정(길이 제한 없음)
– app/src/main/java/.../MypageEditScreen.kt:WarningTextField에maxLength=10적용요청사항
- 서버 API(
UserService.checkNickname)의 허용 패턴(대문자·특수문자 포함 여부, 최소/최대 길이)을 확인- 위 규칙을 클라이언트 쪽 InputFilter/Regex 또는 TextField
maxLength등에 반영- strings.xml 안내 문구에도 “2~12자” 등 구체적인 길이 범위를 추가하여 UX 개선
app/src/main/java/com/texthip/thip/ui/search/component/SearchActiveField.kt (1)
66-68: CardBookList onClick API 전환 적절카드 자체의 클릭 처리를 컴포저블 파라미터로 넘기는 방향이 명확합니다. 이벤트 추적/테스트에도 유리합니다.
app/src/main/java/com/texthip/thip/data/model/rooms/response/MyRoomListResponse.kt (1)
21-22: 비-옵셔널isPublic필드로 인한 역직렬화 실패 위험
현재MyRoomResponse내에서
- 파일:
app/src/main/java/com/texthip/thip/data/model/rooms/response/MyRoomListResponse.kt- 위치:
MyRoomResponse클래스,@SerialName("isPublic") val isPublic: Boolean처럼 non-nullable Boolean으로 선언되어 있습니다. 서버가 해당 필드를 누락하거나 배포가 지연된 경우,
kotlinx.serialization역직렬화 단계에서 예외가 발생합니다. 안전한 기본값을 지정하거나 nullable 처리하는 것을 권장합니다.제안하는 리팩터링 예시:
data class MyRoomResponse( @SerialName("type") val type: String, - @SerialName("isPublic") val isPublic: Boolean + @SerialName("isPublic") val isPublic: Boolean = true )
- 기본값으로
true를 지정하면, 기존에isSecret = !room.isPublic로 UI를 구성할 때 변경 없이 동일한 UX를 보장합니다.- 서버 스키마나 샘플 JSON 응답에서
isPublic필드가 항상 포함되는지 반드시 확인해 주세요.app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt (1)
165-167: 검증 완료: Room 응답 모델의 isPublic은 non-nullable로 선언되어 있습니다
- MyRoomListResponse.kt 에서
@SerialName("isPublic") val isPublic: Boolean으로 선언되어 있어 null 또는 누락 가능성이 없습니다.- RoomsSearchResponse.kt 에서는
@SerialName("isPublic") val isPublic: Boolean = true로 기본값을 지정하고 있어 누락 시에도true로 처리됩니다.- RoomsPlayingResponse.kt, RoomRecruitingResponse.kt 등 모든 룸 관련 응답 모델에서
isPublic: Boolean으로 정의되어 있습니다.따라서
isSecret = !room.isPublic역치환 로직은 안전하며, 별도 추가 검증 없이 코드 변경을 승인합니다.app/src/main/java/com/texthip/thip/ui/search/component/SearchBookFilteredResult.kt (1)
93-95: CardBookList.onClick API로의 이관 깔끔합니다클릭 영역을 컴포넌트 내부로 수렴시켜 책임이 명확해졌습니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedMyScreen.kt (2)
86-89: isSaved/isLiked/isWriter를 모델에서 직접 반영 — 상태 일관성 개선하드코딩 제거로 다른 화면과의 상태 동기화가 수월해집니다. LGTM.
206-208: 프리뷰 데이터도 최신 필드 반영 OKisLiked 패턴과 isSaved=false로 다양한 케이스 확인 가능. 유지하세요.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)
94-101: 키 소비 및 제거 정상 확인됨
FeedScreen.kt에서handle.getXXX("updated_feed_*")로 값을 읽은 후
handle.remove<…>("updated_feed_id")부터handle.remove<Int>("updated_feed_commentCount")까지 모든 키가 명시적으로 제거되고 있습니다.updated_feed_commentCount역시 소비 후 정상 제거되는 것을 확인했습니다.이상 없으므로 변경 승인합니다.
app/src/main/java/com/texthip/thip/ui/group/done/screen/GroupDoneScreen.kt (1)
113-122: isSecret 파생값 전달 방향성 좋습니다
isSecret = !room.isPublic으로 의미가 명확합니다. CardItemRoom의 잠금 뱃지 렌더링과도 자연스럽게 매핑됩니다.app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookGroupScreen.kt (1)
273-275: 프리뷰 Mock에 isPublic 추가 반영 적절디자이너/QA 확인용으로 공개/비공개 케이스를 모두 포함한 점 좋습니다.
Also applies to: 282-284, 291-293
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (3)
144-148: 상위 댓글 추가 시 상태 갱신과 이벤트 플래그 설정 타이밍 적절성공 시 선반영(UI 즉시성) +
isCommentCreated=true로 후속 동기화 트리거를 주는 흐름이 명확합니다.
296-298: 리셋 메서드 분리 좋습니다화면에서 명시적으로 호출해 재호출 루프를 방지하는 구조가 명확합니다.
163-167: replyList 병합 로직: API 응답 계약 확인 필요현재
CommentsCreateResponse.replyList를 기존originalParentComment.replyList에 추가(+ res.replyList)하는 방식은
서버가"신규 답글만"반환한다는 전제 하에서만 안전합니다.
만약 서버가 “부모 댓글의 전체 최신 답글 목록”을 반환한다면 기존 리스트와 중복이 발생할 수 있습니다.– 점검 대상
• app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsCreateResponse.kt
• app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (163–167행)서버 응답이 “신규 답글만”인지, “전체 최신 목록”인지 API 명세 또는 백엔드 담당자에게 확인 후,
필요 시 merge 대신 replace 로직으로 변경해 주세요.app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (6)
93-98: 스크롤 상태 rememberSaveable + LazyListState.Saver 적용 좋습니다.프로세스 킬/재생성 후에도 탭별 스크롤 위치를 안정적으로 복원할 수 있어 UX가 향상됩니다.
156-160: 사용자 주도 탭 전환 시에만 상단 스크롤 — UX 측면에서 적절합니다.프로그램적 탭 변경 시 스크롤 이동을 막아 의도치 않은 점프를 방지합니다.
182-190: 새로고침 결과 처리 후 상단 스크롤 처리 적절.refreshFeed 플래그 소비 후 현재 탭 리스트만 스크롤하여 과도한 이동을 방지합니다.
312-314: UI 안전성 보완(색상/리스트 fallback) 좋습니다.
- aliasColor 파싱 실패 시 기본색으로 폴백
- 팔로워 이미지 URL 리스트 null 시 빈 리스트 처리
- 수량 표기 stringResource 포맷팅 적용
모두 런타임 안정성에 기여합니다.
Also applies to: 321-323, 331-335
365-367: 내 피드 카드 매핑 및 액션 바인딩 적절.isLiked/isSaved를 MyFeedItem에서 직접 반영하고, 좋아요 액션을 ViewModel로 연결해 상/하위 리스트 동기화를 기대할 수 있습니다.
Also applies to: 381-383, 390-391
417-420: 전체 피드 리스트 키 및 액션 연동 적절.
- key를 feedId로 지정해 항목 재정렬/갱신 시 안정성 확보
- 저장/좋아요 클릭 액션이 ViewModel에 위임되어 상태 동기화 경로가 명확
Also applies to: 444-449
| @SerialName("isSaved") val isSaved: Boolean, | ||
| @SerialName("isLiked") val isLiked: Boolean, |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
MyFeedItem에 isSaved/isLiked 추가: 서버 누락 시 크래시 가능
신규 불린 필드를 비-옵셔널로 추가하면, 구버전 서버/캐시된 응답/실험 플래그 미적용 환경에서 역직렬화가 실패할 수 있습니다. 기본값을 지정해 하위 호환성을 확보하세요.
- @SerialName("isSaved") val isSaved: Boolean,
- @SerialName("isLiked") val isLiked: Boolean,
+ @SerialName("isSaved") val isSaved: Boolean = false,
+ @SerialName("isLiked") val isLiked: Boolean = false,또한, 새 필드가 누락되더라도 UI/도메인 매핑이 자연스럽게 동작하는지(예: 기본 false로 표시) 확인 바랍니다.
디코딩 회귀 테스트 추가 제안:
- 케이스1: 두 필드가 없는 기존 JSON → 정상 디코딩(기본값 적용)
- 케이스2: 두 필드가 포함된 최신 JSON → 정상 디코딩
기본값 누락 여부 확인을 위해 아래 스크립트를 실행합니다.
🏁 Script executed:
#!/bin/bash
# MyFeedResponse.kt 파일 경로 검색
file=$(fd --strip-cwd-prefix 'MyFeedResponse.kt')
echo "File: $file"
# isSaved 선언부 확인 (기본값 없는지)
rg -n '@SerialName\("isSaved"\)' "$file" -B2 -A2
# isLiked 선언부 확인 (기본값 없는지)
rg -n '@SerialName\("isLiked"\)' "$file" -B2 -A2Length of output: 1031
MyFeedResponse.kt의 isSaved/isLiked에 기본값(false) 지정 필수
검증 결과, app/src/main/java/com/texthip/thip/data/model/feed/response/MyFeedResponse.kt 파일의 26–27행에 선언된 두 Boolean 필드에 기본값이 없어, 서버에서 해당 필드가 누락될 경우 역직렬화 실패로 앱이 크래시될 수 있습니다.
수정 제안:
- 파일:
app/src/main/java/com/texthip/thip/data/model/feed/response/MyFeedResponse.kt - 위치: 26–27행
- @SerialName("isSaved") val isSaved: Boolean,
- @SerialName("isLiked") val isLiked: Boolean,
+ @SerialName("isSaved") val isSaved: Boolean = false,
+ @SerialName("isLiked") val isLiked: Boolean = false,추가 확인 및 작업:
- 기본값 지정 후, UI/도메인 매핑 시 두 필드가 누락돼도
false로 자연스럽게 처리되는지 확인 - 디코딩 회귀 테스트 추가
- 케이스1:
isSaved/isLiked누락 기존 JSON → 기본값 적용 후 정상 디코딩 - 케이스2: 두 필드 포함 최신 JSON → 정상 디코딩
- 케이스1:
📝 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.
| @SerialName("isSaved") val isSaved: Boolean, | |
| @SerialName("isLiked") val isLiked: Boolean, | |
| @SerialName("isSaved") val isSaved: Boolean = false, | |
| @SerialName("isLiked") val isLiked: Boolean = false, |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/model/feed/response/MyFeedResponse.kt
around lines 26–27, the Boolean properties isSaved and isLiked lack default
values which causes deserialization to fail when the server omits them; add
default values (false) to both declarations, update any UI/domain mapping to
treat absent fields as false, and add decoding regression tests: one JSON
missing isSaved/isLiked should decode with false defaults, and one JSON
including both should still decode correctly.
| isSaved = currentIsSaved, // isSaved 상태는 그대로 유지 | ||
| commentCount = 0 // 좋아요 함수에서는 댓글 수 정보 없음 | ||
| ) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
commentCount=0로 브로드캐스트 시 댓글 수가 0으로 초기화되는 치명적 가능성
좋아요/저장 처리에서 FeedStateUpdateResult(commentCount = 0)를 내보내면, 뷰모델 머지 로직에 따라 목록의 commentCount가 0으로 덮어써질 수 있습니다(실제 값 보존 실패). 이는 데이터 손실/UX 오류로 직결됩니다.
권장 수정안 A(타입 안전):
FeedStateUpdateResult.commentCount를Int?로 변경하고,null일 때는 기존 값을 유지하도록 머지 로직을 수정하세요.- 본 파일에서는 다음처럼 설정합니다.
- commentCount = 0 // 좋아요 함수에서는 댓글 수 정보 없음
+ commentCount = null // 댓글 수는 변경하지 않음- commentCount = 0 // 저장 함수에서는 댓글 수 정보 없음
+ commentCount = null // 댓글 수는 변경하지 않음추가로 필요한 외부 변경(예시):
// FeedStateUpdateResult.kt (예시)
data class FeedStateUpdateResult(
val feedId: Long,
val isLiked: Boolean,
val likeCount: Int,
val isSaved: Boolean,
val commentCount: Int? = null // null이면 기존 값 유지
)권장 수정안 B(임시 우회):
- 타입 변경이 어렵다면, ViewModel의 머지 단계에서 “좋아요/저장에 의해 발생한 업데이트”에는
commentCount를 무시하도록 분기하세요. 단, 실제 0으로 갱신해야 하는 시나리오(댓글 0건)도 존재하므로, 이 접근은 장기적으로 안전하지 않습니다.
대안 C(호출부 전달):
- 본 함수 시그니처에
currentCommentCount: Int를 추가해 그대로 전달/유지. 호출부 전파가 필요합니다.
다음 스크립트로 머지 로직이 commentCount를 어떻게 취급하는지 확인해 주세요.
Also applies to: 291-293
🏁 Script executed:
#!/bin/bash
# FeedStateUpdateResult 병합 경로 및 commentCount 사용 방식 확인
rg -nP --type=kt -C3 'updateFeedStateFromResult|apply.*FeedStateUpdateResult|commentCount'Length of output: 119
기존 스크립트가 .kt 파일 타입을 인식하지 못해 결과를 얻지 못했습니다. 아래 스크립트로 Kotlin 소스 전체에서 머지 로직과 commentCount 사용 위치를 다시 확인해주세요.
🏁 Script executed:
#!/bin/bash
# Kotlin 파일 전체에서 FeedStateUpdateResult와 commentCount 사용 위치 검색
# 1. updateFeedStateFromResult 호출 위치 검색
rg -n 'updateFeedStateFromResult' -C3 -g '*.kt'
# 2. FeedStateUpdateResult 생성/사용 위치 검색
rg -n 'FeedStateUpdateResult' -C3 -g '*.kt'
# 3. apply { FeedStateUpdateResult(...) } 패턴 검색
rg -nP 'apply.*FeedStateUpdateResult' -C3 -g '*.kt'
# 4. commentCount 필드 접근 또는 할당 위치 검색
rg -n 'commentCount' -C3 -g '*.kt'Length of output: 43136
commentCount 초기화 버그 발견 — 긴급 수정 필요
FeedRepository.kt에서 좋아요/저장 처리 시 commentCount = 0으로 설정하고,
FeedViewModel.updateFeedStateFromResult에서 이 값을 무조건 덮어써 실제 댓글 개수가 손실됩니다.
다음 위치를 확인해주세요:
- app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt (266–268, 291–293)
- app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (434–442, 451–454)
권장 수정안 A (타입 안전)
// app/src/main/java/com/texthip/thip/ui/feed/mock/FeedStateUpdateResult.kt
- data class FeedStateUpdateResult(
+ data class FeedStateUpdateResult(
val feedId: Long,
val isLiked: Boolean,
val likeCount: Int,
val isSaved: Boolean,
- val commentCount: Int
+ val commentCount: Int? = null // null이면 기존 값 유지
)// app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt
- commentCount = 0 // 좋아요/저장 함수에서는 댓글 수 정보 없음
+ commentCount = null // 댓글 수는 변경하지 않음// app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt
- commentCount = result.commentCount
+ commentCount = result.commentCount ?: feed.commentCount※ 임시 우회(B) 및 호출부 전달(C) 대안은 앞선 코멘트를 참고하세요.
📝 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.
| isSaved = currentIsSaved, // isSaved 상태는 그대로 유지 | |
| commentCount = 0 // 좋아요 함수에서는 댓글 수 정보 없음 | |
| ) | |
| isSaved = currentIsSaved, // isSaved 상태는 그대로 유지 | |
| commentCount = null // 댓글 수는 변경하지 않음 | |
| ) |
| val newAllFeeds = currentAllFeeds.map { | ||
| if (it.feedId.toLong() == feedId) { | ||
| it.copy( | ||
| isLiked = !it.isLiked, | ||
| likeCount = if (it.isLiked) it.likeCount - 1 else it.likeCount + 1 | ||
| ) | ||
| } else { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
좋아요 수 음수로 내려갈 수 있습니다 — 최소 0으로 클램프하세요.
현재 낙관적 업데이트에서 isLiked가 true인 상태에서 해제 시 likeCount를 단순히 -1 합니다. 0에서 해제하면 -1이 되어 UI 일관성과 서버 정합성이 깨질 수 있습니다. 최소값을 0으로 제한하는 것이 안전합니다.
- likeCount = if (it.isLiked) it.likeCount - 1 else it.likeCount + 1
+ likeCount = if (it.isLiked) (it.likeCount - 1).coerceAtLeast(0) else it.likeCount + 1- likeCount = if (it.isLiked) it.likeCount - 1 else it.likeCount + 1
+ likeCount = if (it.isLiked) (it.likeCount - 1).coerceAtLeast(0) else it.likeCount + 1Also applies to: 353-357
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt around
lines 341-347 and 353-357, the optimistic update can decrement likeCount below
zero; change the decrement logic to clamp the resulting likeCount to a minimum
of 0 (e.g., compute the new count and apply max/coerceAtLeast(0)) so unliking
from 0 never produces -1; apply this fix in both places where likeCount is
adjusted.
| .onFailure { | ||
| _uiState.update { it.copy(allFeeds = currentFeeds) } | ||
| _uiState.update { it.copy(allFeeds = currentAllFeeds, myFeeds = currentMyFeeds) } | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
낙관적 업데이트 실패 시 “전체 스냅샷 롤백”은 최신 데이터 덮어쓸 위험이 큽니다. 대상 아이템만 롤백하세요.
현재 실패 처리에서 변경 전 전체 리스트 스냅샷으로 되돌립니다. 같은 시점에 백그라운드 새로고침/페이지네이션이 반영되었다면 그 변경까지 소실될 수 있습니다. 실패 시 해당 feedId 아이템만 원래 값으로 복구하세요.
- ).onFailure {
- updateState { it.copy(allFeeds = currentAllFeeds, myFeeds = currentMyFeeds) }
- }
+ ).onFailure {
+ updateState {
+ it.copy(
+ allFeeds = it.allFeeds.map { f ->
+ if (f.feedId.toLong() == feedId) {
+ f.copy(
+ isLiked = (allFeedToUpdate?.isLiked ?: myFeedToUpdate!!.isLiked),
+ likeCount = (allFeedToUpdate?.likeCount ?: myFeedToUpdate!!.likeCount)
+ )
+ } else f
+ },
+ myFeeds = it.myFeeds.map { f ->
+ if (f.feedId.toLong() == feedId) {
+ f.copy(
+ isLiked = (myFeedToUpdate?.isLiked ?: allFeedToUpdate!!.isLiked),
+ likeCount = (myFeedToUpdate?.likeCount ?: allFeedToUpdate!!.likeCount)
+ )
+ } else f
+ }
+ )
+ }
+ }- ).onFailure {
- updateState { it.copy(allFeeds = currentAllFeeds, myFeeds = currentMyFeeds) }
- }
+ ).onFailure {
+ updateState {
+ it.copy(
+ allFeeds = it.allFeeds.map { f ->
+ if (f.feedId.toLong() == feedId) {
+ f.copy(isSaved = (allFeedToUpdate?.isSaved ?: myFeedToUpdate!!.isSaved))
+ } else f
+ },
+ myFeeds = it.myFeeds.map { f ->
+ if (f.feedId.toLong() == feedId) {
+ f.copy(isSaved = (myFeedToUpdate?.isSaved ?: allFeedToUpdate!!.isSaved))
+ } else f
+ }
+ )
+ }
+ }Also applies to: 429-430
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt around
lines 378-381 (and similarly at 429-430), the optimistic-update failure handler
currently replaces the entire feed lists with a prior full snapshot, which can
overwrite concurrent updates; change the rollback to restore only the single
feed item that was modified: capture the original Feed item (by feedId) before
applying the optimistic change, and onFailure update the UI state by mapping
over current lists to replace the item with that original value (leaving other
items and any background refresh/pagination intact); ensure you handle both
allFeeds and myFeeds lists and null/absent cases gracefully.
| Box( | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .weight(1f) | ||
| ) { |
There was a problem hiding this comment.
Modifier.weight import 누락으로 컴파일 실패 가능
Modifier.weight(1f)는 androidx.compose.foundation.layout.weight 확장 함수입니다. 현재 import 목록에 없어서 컴파일 에러가 발생할 수 있습니다. 아래 import를 추가해 주세요.
+import androidx.compose.foundation.layout.weight추가 제안: 이 Box가 실질적으로 가중치 적용 용도라면, 빈 상태 UI(Column)와 LazyColumn에 각각 Modifier.weight(1f)를 직접 부여하도록 구조를 단순화할 수 있습니다. 가독성이 좋아지고 중첩이 줄어듭니다.
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/search/component/SearchRecentBook.kt
around lines 89 to 93, the call to Modifier.weight(1f) uses the extension
androidx.compose.foundation.layout.weight which is not imported and will cause a
compile error; add the import androidx.compose.foundation.layout.weight to the
file, and optionally simplify the layout by removing this Box and applying
Modifier.weight(1f) directly to the empty-state Column and the LazyColumn
respectively so each branch handles its own weight without an extra wrapping
Box.
|
➕ 이슈 링크
🔎 작업 내용
📸 스크린샷
😢 해결하지 못한 과제
[] TASK
📢 리뷰어들에게
Summary by CodeRabbit
신기능
개선
스타일