Conversation
Walkthrough여러 UI/네비게이션/모델 변경이 포함됨. 주요 내용은: 퍼센티지 타입(Double→Int) 변경 전파, 팔로워 모델에 isMyself 추가, 댓글 액션 모드(enum) 및 관련 파라미터 제거, 피드 마이 화면 추가와 네비게이션 분기(내 프로필/타인) 도입, 클릭 핸들러(onClick) 확장, 레이아웃/토스트/오버레이 조정, 테마 색상/문자열 추가. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant FeedNav as FeedNavigation
participant VM as FeedViewModel
participant Routes as FeedRoutes
participant Screen as FeedScreen/FeedMyScreen
User->>FeedNav: 사용자 프로필 클릭(userId, isMyself?)
FeedNav->>VM: uiState 수집(myFeedInfo.creatorId = myUserId)
alt isMyself == true or userId == myUserId
FeedNav->>Routes: navigate(FeedRoutes.My)
Routes-->>Screen: FeedMyScreen(viewModel)
else
FeedNav->>Routes: navigate(FeedRoutes.Others(userId))
Routes-->>Screen: OthersProfile/Feed
end
sequenceDiagram
autonumber
actor User
participant FCS as FeedCommentScreen
participant VM as FeedDetailViewModel
Note over FCS: 상태: isPostMenuVisible, isCommentMenuVisible,<br/>showDeleteDialog, showToast, toastMessage
User->>FCS: 앱바 액션(메뉴)
FCS->>FCS: isPostMenuVisible = true
User->>FCS: 댓글 롱프레스(commentId, isWriter)
FCS->>FCS: selectedCommentForMenu 설정<br/>isCommentMenuVisible = true
alt 댓글 작성자
User->>FCS: 삭제 선택
FCS->>VM: DeleteComment(commentId)
FCS->>FCS: isCommentMenuVisible = false
else 타인
User->>FCS: 신고 선택
FCS->>FCS: toastMessage 설정, showToast = true
Note over FCS: LaunchedEffect로 3초 후 showToast=false
end
User->>FCS: 게시글 삭제(메뉴)
FCS->>FCS: showDeleteDialog = true
User->>FCS: 확인
FCS->>VM: deleteFeed(feedId)
FCS->>FCS: showDeleteDialog = false
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
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
🪧 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: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (1)
88-93: 토스트 애니메이션 타이밍 문제: '정지 시간'이 거의 없어 UX가 저하됩니다현재 진입/종료 애니메이션이 각각 2000ms이고, 표시 타이머도 2000ms라서, 토스트가 완전히 자리 잡자마자 곧바로 사라지는 흐름입니다. 사용자는 정지된 상태의 토스트를 거의 보지 못합니다.
아래 중 하나로 정렬해 주세요.
- 최소 변경(기존 2초 애니메이션 유지): 표시 지연을 4000ms로 늘려 정지 시간을 2초 확보
- 권장(애니메이션 단축 + 가독성 향상): 진입/퇴장 애니메이션을 200~300ms로 줄이고, 표시 시간을 별도로 2000ms 확보
최소 변경안(라인 88-93만 수정):
LaunchedEffect(uiState.showToast) { if (uiState.showToast) { - delay(2000) + // enter(2s) + hold(2s) = 4s 후 hide + delay(4000) onHideToast() } }권장 변경안(애니메이션 단축 + 표시 시간 분리):
AnimatedVisibility( visible = uiState.showToast, enter = slideInVertically( initialOffsetY = { -it }, - animationSpec = tween(durationMillis = 2000) + animationSpec = tween(durationMillis = 300) ), exit = slideOutVertically( targetOffsetY = { -it }, - animationSpec = tween(durationMillis = 2000) + animationSpec = tween(durationMillis = 200) ),그리고 표시 타이머는 진입 시간(300ms)을 고려해 총 2300ms 정도로:
LaunchedEffect(uiState.showToast) { if (uiState.showToast) { - delay(2000) + // enter(300ms) + hold(2000ms) + delay(2300) onHideToast() } }Also applies to: 189-198
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt (1)
52-52: 불필요한 단독 표현식 제거 필요(data)Line 52의 단독
data표현식은 의미가 없고 정적 분석(ktlint/detekt)에서 경고/실패를 유발할 수 있습니다. 제거하세요.- data ProfileBarFeed(app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (1)
240-247: SearchPeople 경로에서 ‘내 프로필’ 분기 미적용 — 요구사항 누락으로 보입니다현재 navigateToUserProfile(userId) 확장을 그대로 사용하여 항상 Others로 갈 가능성이 큽니다. 본 PR의 목표(내/타인 분기)에 맞춰 로직을 맞춰주세요.
아래처럼 Feed 탭의 ViewModel에서 myUserId를 가져와 분기하는 것으로 통일할 수 있습니다:
composable<FeedRoutes.SearchPeople> { - SearchPeopleScreen( - onNavigateBack = navigateBack, - onUserClick = { userId -> - navController.navigateToUserProfile(userId) - } - ) + val feedViewModel: FeedViewModel = hiltViewModel(navController.getBackStackEntry(MainTabRoutes.Feed)) + val uiState by feedViewModel.uiState.collectAsState() + val myUserId = uiState.myFeedInfo?.creatorId + + SearchPeopleScreen( + onNavigateBack = navigateBack, + onUserClick = { userId -> + if (myUserId != null && myUserId == userId) { + navController.navigate(FeedRoutes.My) + } else { + navController.navigate(FeedRoutes.Others(userId)) + } + } + ) }
🧹 Nitpick comments (35)
app/src/main/java/com/texthip/thip/ui/navigator/BottomNavigationBar.kt (2)
101-101: 패딩 32→20dp 변경: 디자인·터치 영역 확인 및 dimen 추출 권장좌우 패딩 축소로 가장자리 아이템의 시각적/터치 밀집도가 높아질 수 있습니다. 디자인 스펙과 최소 터치 영역(권장 48dp) 충족 여부를 한번 확인해 주세요. 또한 매직 넘버 대신 dimen 리소스로 추출해 두면 향후 조정이 용이합니다.
적용 제안(diff):
- .padding(horizontal = 20.dp), + .padding(horizontal = dimensionResource(id = R.dimen.bottom_nav_horizontal_padding)),추가 필요사항:
- import 추가
import androidx.compose.ui.res.dimensionResource- 리소스 정의(res/values/dimens.xml 예시)
<resources> <dimen name="bottom_nav_horizontal_padding">20dp</dimen> </resources>
139-146: Preview 추가 굿. 가독성 향상을 위한 소폭 개선 제안Preview 캔버스에서 컴포넌트 경계 인지가 쉬우도록 showBackground와 name을 지정해 두면 편합니다. 선택 사항이지만 실사용 테마가 있다면(예: ThipTheme 컴포저블) 프리뷰를 테마로 감싸면 색/타이포 일관성이 더 잘 보입니다.
적용 제안(diff):
-@Preview +@Preview(name = "BottomNavigationBar", showBackground = true) @Composable private fun BottomNavigationBarPreview() { val navController = rememberNavController() - BottomNavigationBar( - navController = navController - ) + BottomNavigationBar(navController = navController) }참고: 테마 컴포저블이 존재한다면 아래처럼 감싸는 것도 고려해 주세요(예시).
// ThipTheme { BottomNavigationBar(navController) }app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt (1)
96-100: padding과 verticalScroll 적용 순서 조정 제안현재 순서(…verticalScroll().padding())에서는 padding이 스크롤 영역 밖에 적용됩니다. 폼 끝단까지 자연스럽게 스크롤되도록 패딩이 스크롤에 포함되게 하는 것이 일반적으로 더 자연스러워, 아래처럼 순서를 바꾸는 것을 권장합니다.
- modifier = Modifier - .weight(1f) - .verticalScroll(rememberScrollState()) - .padding(vertical = 32.dp, horizontal = 20.dp), + modifier = Modifier + .weight(1f) + .padding(vertical = 32.dp, horizontal = 20.dp) + .verticalScroll(rememberScrollState()),app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt (1)
96-100: 스크롤/패딩 순서 통일 제안 (사용자 경험 개선)Note 화면과 동일하게 padding을 스크롤에 포함시키는 순서가 더 자연스럽습니다. 아래와 같이 적용을 권장합니다.
- modifier = Modifier - .weight(1f) - .verticalScroll(rememberScrollState()) - .padding(vertical = 32.dp, horizontal = 20.dp), + modifier = Modifier + .weight(1f) + .padding(vertical = 32.dp, horizontal = 20.dp) + .verticalScroll(rememberScrollState()),app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt (1)
31-31: BottomSheet의 높이 제한(0.8f) + advancedImePadding 조합 검증 권장0.8f 높이 제한에 IME 패딩이 더해지면, 소형 단말/대형 IME 환경에서 실제 가용 공간이 과도하게 줄 수 있습니다. 특히 검색 결과가 길어 스크롤이 필요한 경우와 로딩 스피너가 표시되는 경우 레이아웃 안정성을 점검해 주세요.
권장 수동 테스트 시나리오:
- 단말 A(작은 해상도)와 단말 B(큰 해상도)에서 키보드 열고/닫기 시 리스트 영역이 충분히 스크롤 가능하고 TextField가 가려지지 않는지 확인
- 제스처 내비/3버튼 내비 모두에서 하단 네비게이션 바와 겹침 없는지 확인
- 검색어 입력 중 로딩 스피너(라인 101-110)가 중앙 정렬로 정상 노출되는지 확인
대안(선택):
- IME 패딩을 전체 Column이 아닌 리스트 영역(예: GroupBookListWithScrollbar를 감싼 컨테이너)에만 적용하여 헤더/탭 영역 레이아웃 이동 최소화
- 0.8f를 Dimen로 추출해 화면군(폰/태블릿)에 따라 튜닝 가능하게 구성
Also applies to: 69-71
app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomBody.kt (1)
45-49: percentage 값 범위(0..100) 보장 클램핑 권장서버/계산 오류로 0~100 범위를 벗어나는 값이 들어올 경우 CardNote 내부 진행률 표시가 깨질 수 있습니다. 방어적으로 클램핑하면 안전합니다.
다음 변경을 제안합니다:
CardNote( currentPage = currentPage, - percentage = userPercentage, + percentage = userPercentage.coerceIn(0, 100), ) { onNavigateToNote() }app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookListWithScrollbar.kt (4)
30-39: 스크롤 뷰포트 제약이 없어 실제로 스크롤되지 않을 수 있습니다Box의 고정 높이(또는 상위 제약)가 사라지면 Column의 verticalScroll이 부모 제약 내에서만 의미가 있어, 상위가 wrapContent일 경우 리스트 전체가 확장되어 사실상 스크롤이 동작하지 않을 수 있습니다. 기존 320.dp 고정 높이를 제거한 의도(유연한 높이)는 좋지만, 부모 쪽에서 명시적으로 높이/weight를 주입할 수 있는 여지를 열어두는 편이 안전합니다.
권장: modifier 파라미터를 추가해 부모에서 높이/weight 제약을 전달할 수 있게 해주세요.
@Composable fun GroupBookListWithScrollbar( books: List<BookData>, onBookClick: (BookData) -> Unit, modifier: Modifier = Modifier, // 추가 ) { val scrollState = rememberScrollState() Box( modifier .fillMaxWidth() ) { Column( Modifier .fillMaxWidth() .verticalScroll(scrollState) .drawVerticalScrollbar(scrollState) ) { /* ... */ } } }만약 컴포넌트 내부에서 기본 상한을 주고 싶다면 heightIn(max = …) 사용을 고려해 주세요(예: .heightIn(max = 320.dp)). 단, 가능한 한 상위에서 제약을 전달하는 쪽이 재사용성과 일관성 면에서 더 낫습니다.
QA 체크 포인트: 실제 화면에서 리스트가 “스크롤되어야 하는” 영역에서 정상적으로 스크롤/스크롤바가 동작하는지 확인 바랍니다.
46-46: 마지막 아이템 하단에 불필요한 12.dp 여백이 생깁니다 + 항목 간 간격이 12+1+12=25.dp로 증가했습니다현재 구조상 모든 항목 뒤에 12.dp가 무조건 들어가고, 마지막이 아닌 항목에는 Divider 전후로 총 24.dp(+1.dp 라인) 간격이 생깁니다. 의도된 디자인 변경인지 확인이 필요합니다. 일반적인 리스트 간격(예: 8~12.dp) 대비 과도하게 벌어질 수 있습니다.
최소 수정(항목 간 간격을 줄이고 마지막 하단 공백 제거):
- Spacer(modifier = Modifier.height(12.dp)) + // 항목 간 과도한 여백 및 마지막 하단 공백 방지: 제거대안 1(시각적 여백을 Divider 위쪽에만 두고 12.dp 유지):
- Spacer(modifier = Modifier.height(12.dp)) if (index < books.size - 1) { + Spacer(modifier = Modifier.height(12.dp)) // Divider 위 여백 // Divider(또는 1.dp 라인) - Spacer(modifier = Modifier.height(12.dp)) }대안 2(컴포즈의 Arrangement.spacedBy로 간격을 일관 관리하고, Divider만 사이에 두기): Column에 verticalArrangement = Arrangement.spacedBy(12.dp)를 주고, Divider는 항목 사이에서만 렌더링하도록 루프를 조정하는 방법도 있습니다.
48-53: Separator는 Spacer+background 대신 Divider 사용 권장, 6.dp 매직 넘버 상수화 제안Material 컴포넌트인 Divider를 사용하면 두께/컬러/투명도 등 접근성과 일관성이 좋아집니다. 또한 end = 6.dp는 스크롤바와의 간섭을 피하려는 의도로 보이는데, 디자인 토큰(예: ThipTheme.dimensions.xxx)으로 상수화하면 유지보수에 유리합니다.
적용 예시(해당 라인 범위 내 변경):
- Spacer( - modifier = Modifier - .fillMaxWidth() - .padding(end = 6.dp) - .height(1.dp) - .background(color = colors.Grey02) - ) + Divider( + modifier = Modifier + .fillMaxWidth() + .padding(end = 6.dp), // TODO: 디자인 토큰으로 대체 권장 + thickness = 1.dp, + color = colors.Grey02, + )추가: Divider import 필요
import androidx.compose.material3.Divider참고: 스크롤바와 겹침 방지 여백(6.dp)이 실제 디바이스/다크모드에서도 시각적으로 자연스러운지 확인 부탁드립니다.
40-57: 데이터가 많아질 가능성이 있으면 LazyColumn 전환 고려Column + verticalScroll은 모든 항목을 한 번에 컴포지션/측정하므로, 리스트가 길어질 경우 성능/메모리 비용이 커집니다. LazyColumn(itemsIndexed)로 전환하면 가시 영역만 렌더링되어 효율적입니다. 다만 drawVerticalScrollbar가 LazyListState를 지원하도록 확장되어야 합니다.
스켈레톤 예시:
val listState = rememberLazyListState() LazyColumn( modifier = Modifier.fillMaxWidth(), state = listState, // contentPadding = PaddingValues(bottom = 12.dp) 등 ) { itemsIndexed(books) { index, book -> CardBookSearch( title = book.title, imageUrl = book.imageUrl, onClick = { onBookClick(book) } ) if (index < books.lastIndex) { // 위 코멘트의 Divider 방식을 적용 } } } // drawVerticalScrollbar(listState)를 지원하도록 확장 필요app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt (2)
45-49: 비활성(Disabled) 텍스트 컬러 변경은 의도 명확. 다만 isFilled=true 배경과의 대비(contrast) 점검 필요현재 !enabled && isFilled일 때 배경이 DarkGrey02, 텍스트가 DarkGrey로 설정됩니다. 두 값의 명도 차가 작으면 가독성/접근성(WCAG 대비 4.5:1 권장) 이슈가 생길 수 있습니다. 디자인 토큰 기준으로 대비를 한 번 확인해 주세요. 필요 시 비활성 텍스트 컬러를 한 단계 더 밝히거나(alpha 적용 포함) 배경 대비가 충분한 색상으로 조정하는 것을 권장합니다.
접근성 보강을 위해 시각적 비활성화뿐 아니라 스크린 리더에도 “비활성”으로 노출되도록 semantics를 추가하는 것도 권장드립니다(아래는 변경 라인 외 지원 코드 예시).
// import 추가 import androidx.compose.ui.semantics.disabled import androidx.compose.ui.semantics.semantics // Box(modifier = ...) 체인에 추가 .semantics { if (!enabled) disabled() }
57-61: 비활성 보더 컬러 추가는 👍. 상호작용(리플/접근성) 일관성 개선 제안현재 clickable은 항상 활성화되어 있어 비활성일 때도 리플이 표시되고(onDisabledClick 호출), 접근성 트리에서는 “클릭 가능”으로 노출될 수 있습니다. 의도가 “선택 제한 초과 시 안내 토스트 등”이라면 onDisabledClick 유지도 합리적이지만, 리플은 꺼주고 접근성에는 disabled semantics를 부여하는 게 일관성 있습니다.
아래와 같이 개선을 검토해 보세요(변경 라인 외 지원 코드 예시).
// import import androidx.compose.foundation.interaction.MutableInteractionSource Box( modifier = modifier // ... .semantics { if (!enabled) disabled() } // 시각/접근성 동기화 .clickable( // 비활성 시 리플 제거 indication = if (enabled) null else null, interactionSource = remember { MutableInteractionSource() } ) { if (enabled) { if (isSelected == null) isClicked = !isClicked onClick() } else { onDisabledClick() } } // ... )참고: 비활성 시 완전히 상호작용을 막고 싶다면 clickable(enabled = enabled)로 바꾸고 else 분기를 제거하면 됩니다(그 경우 onDisabledClick은 더 이상 호출되지 않습니다).
app/src/main/java/com/texthip/thip/ui/common/buttons/SubGenreChipRow.kt (2)
27-32: 선택 제한 로직 명확. 매직 넘버(5) 상수/파라미터화 제안현재 제한값 5가 하드코딩되어 있어 향후 기획 변경 시 파급 범위가 커질 수 있습니다. 지역 상수 혹은 함수 파라미터로 노출하는 것을 권장합니다.
적용 예(해당 범위 내 최소 수정):
- val isLimitReached = selectedGenres.size >= 5 + val maxSelection = 5 + val isLimitReached = selectedGenres.size >= maxSelection또는 함수 시그니처에 기본값 파라미터로 받는 방법:
@Composable fun SubGenreChipGrid( subGenres: List<String>, selectedGenres: List<String>, onGenreToggle: (String) -> Unit, horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, maxSelection: Int = 5, // new ) { val isLimitReached = selectedGenres.size >= maxSelection // ... }
33-39: OptionChipButton 파라미터 연결 좋습니다. 비활성 클릭 시 사용자 피드백 훅 제공 고려isSelected/ enabled 전달로 UI 상태가 잘 반영됩니다. 선택 제한에 걸렸을 때 사용자에게 즉시 안내(예: 토스트)하고 싶다면 onDisabledClick을 활용할 수 있도록 훅을 노출하는 것을 권장합니다.
예시(지원 코드):
@Composable fun SubGenreChipGrid( subGenres: List<String>, selectedGenres: List<String>, onGenreToggle: (String) -> Unit, horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, maxSelection: Int = 5, onMaxSelectionAttempt: (() -> Unit)? = null, // new ) { val isLimitReached = selectedGenres.size >= maxSelection subGenres.forEach { genre -> val isSelected = selectedGenres.contains(genre) val isEnabled = isSelected || !isLimitReached OptionChipButton( text = genre, isSelected = isSelected, isFilled = false, enabled = isEnabled, onClick = { onGenreToggle(genre) }, onDisabledClick = { onMaxSelectionAttempt?.invoke() }, // new ) } }원하시면 위 변경을 반영한 커밋 패치를 만들어 드리겠습니다.
app/src/main/res/values/strings.xml (1)
374-374: 신규 토스트 메시지 추가 좋습니다. 문장부호 톤만 기존과 맞추면 더 일관적입니다.같은 섹션의
posting_complete_feed는!를 사용합니다. 동일 톤 유지 차원에서 감탄부호 사용을 제안드립니다.- <string name="report_complete_feed">게시글 신고를 완료했어요.</string> + <string name="report_complete_feed">게시글 신고를 완료했어요!</string>app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)
320-320: 빈 상태 상단 여백 하드코딩 대신 dimens로 추출 권장고정 110.dp는 기기 해상도/비율에 따라 어색할 수 있습니다. 리소스로 추출하면 화면별 조정이 쉬워집니다.
- .padding(top = 110.dp), + .padding(top = dimensionResource(id = R.dimen.feed_empty_top_padding)),추가로 필요한 리소스 정의 예시(별도 파일):
<!-- res/values/dimens.xml --> <resources> <dimen name="feed_empty_top_padding">110dp</dimen> </resources>app/src/main/java/com/texthip/thip/ui/group/note/component/FilterHeaderSection.kt (2)
55-57: 불필요한 조건 분기 제거로 간결화이미 보이는지 여부와 상관없이 true 대입은 멱등적입니다. 단순 대입으로 정리하면 읽기 쉬워집니다.
- if (!isPageInputVisible) { - isPageInputVisible = true - } + isPageInputVisible = true
71-80: 상태 분기를 enabled 속성으로 위임해 의도를 명확히 표현
isPageInputVisible일 때 클릭 무시 로직을 onClick/onDisabledClick에서 분기하기보다, 버튼 자체를 비활성 처리하는 편이 접근성/시맨틱 측면에서 더 자연스럽습니다.enabled = totalEnabled && !isPageInputVisible로 위임하면 스크린리더에도 명확히 전달됩니다.- enabled = totalEnabled, + enabled = totalEnabled && !isPageInputVisible, textStyle = typography.menu_r400_s14_h24, - onClick = { - if (!isPageInputVisible) { - onTotalToggle() - } - }, - onDisabledClick = { - if (!isPageInputVisible) { - onDisabledClick() - } - }, + onClick = onTotalToggle, + onDisabledClick = onDisabledClick,추가 고려:
isPageInputVisible상태에서 해당 옵션을 비활성화했음을 사용자에게 알릴 필요가 있다면,onDisabledClick에서 안내 토스트를 노출하는 패턴도 유효합니다.app/src/main/java/com/texthip/thip/ui/common/cards/CardBookList.kt (1)
50-52: Row 전역 클릭 처리 시 두 가지 자잘한 개선 제안
- Transparent 배경 지정은 기본값과 동일하여 불필요합니다.
- 접근성 향상을 위해 role 지정 또는 contentDescription/semantics 보강을 고려하세요.
- .background(Color.Transparent) - .clickable { onClick() }, + .clickable(role = null) { onClick() },추가로, UI 테스트/접근성에 도움이 되도록 필요 시
semantics { }또는testTag부여를 검토해 주세요.app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (1)
122-127: 불필요한 TODO 주석 정리 권장해당 버튼에 이미 onToggleFollow가 연결되어 있어 “TODO: 띱하기/취소하기 로직 연결” 주석은 현재 상태와 불일치합니다. 혼선을 줄이기 위해 제거 또는 최신 설명으로 갱신해 주세요.
- // TODO: 띱하기/취소하기 로직 연결 onButtonClick = onToggleFollow,app/src/main/java/com/texthip/thip/data/model/users/response/OthersFollowersResponse.kt (1)
22-23: isMyself 필드에 기본값을 부여해 하위호환성을 확보하세요서버가 구버전이거나 필드가 누락될 경우, Kotlinx Serialization/Gson 모두에서 역직렬화 실패가 발생할 수 있습니다. 기본값을 추가해 방어적으로 처리하는 것을 권장합니다.
- @SerializedName("isMyself") val isMyself: Boolean + @SerializedName("isMyself") val isMyself: Boolean = false또한 본 파일은
@Serializable(kotlinx)와@SerializedName(Gson) 어노테이션을 혼용하고 있습니다. 프로젝트 전반의 직렬화 전략(하나로 통일)을 재점검하는 것이 유지보수에 유리합니다.app/src/main/java/com/texthip/thip/ui/feed/component/ImageViewerModal.kt (1)
46-47: 배경 오버레이 추가는 UX 향상에 유효합니다. 접근성(Back 버튼/스크린리더) 보완을 권장합니다.
- Back 버튼으로도 닫히도록 BackHandler를 추가하면 접근성이 좋아집니다.
- "닫기" 아이콘의 contentDescription을 stringResource로 통일(i18n)하면 번역 누락을 줄일 수 있습니다.
아래 코드를 컴포저블 상단(rememberPagerState 아래 등)에 추가하는 것을 제안드립니다:
import androidx.activity.compose.BackHandler BackHandler(enabled = true) { onDismiss() }app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt (1)
46-53: feedId 타입 통일 확인 완료
모든 레이어에서feedId가Long으로 일치합니다. 별도 수정은 필요 없으며, 원하실 경우 람다 파라미터에 타입을 명시하시면(예:{ feedId: Long -> … }) 컴파일 타임 검사가 더 빠르게 적용됩니다.
- MypageSaveScreen(onFeedClick: Long)
- FeedContent(onFeedClick: Long)
- NavHostController.navigateToFeedComment(feedId: Long)
- FeedRoutes.Comment(feedId: Long)
app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt (1)
28-31: onBookClick 추가는 타당합니다. 재사용성/역호환을 위해 기본값 제공을 제안합니다.기본값을 두면 호출부 수정 누락으로 인한 컴파일 오류를 줄일 수 있습니다.
- onBookClick: (isbn: String) -> Unit + onBookClick: (isbn: String) -> Unit = {}app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt (1)
48-50: 컨텐츠 클릭 → 댓글 진입 트리거 연결 좋습니다. 댓글 버튼도 동일 동작으로 통일을 고려해 보세요.UX 일관성을 위해 액션바의 댓글 버튼(onCommentClick)도 동일한 핸들러로 연결을 제안합니다.
SavedFeedCard( feedItem = feed, onBookmarkClick = { viewModel.toggleBookmark(feed.id) }, onLikeClick = { viewModel.toggleLike(feed.id) }, - onContentClick = { onFeedClick(feed.id) } + onContentClick = { onFeedClick(feed.id) }, + onCommentClick = { onFeedClick(feed.id) } )app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (2)
285-293: FeedViewModel을 Group 백스택에 스코핑하고 탭 전환(onTabSelected)로 내 정보 로딩하는 방식은 결합도가 높습니다Group 흐름에서 FeedViewModel을 생성(navController.getBackStackEntry(MainTabRoutes.Group))해 myFeedInfo를 로딩하는 것은 Feed 탭과 서로 다른 인스턴스/상태를 가지게 되어 중복 로딩·상태 불일치가 발생할 수 있습니다. 또한 Group 흐름 진입 시 Feed 탭의 선택 인덱스를 강제로 바꾸는 부작용도 생깁니다.
- 추천: 전역 사용자 세션/프로필(예: Auth/UserSession Repository)에서 myUserId를 직접 제공받아 의존성을 낮추거나, UI와 무관한 가벼운 ViewModel/UseCase로 현재 사용자 ID만 획득하세요.
- 최소한으로는, 해당 인스턴스가 실제로 myFeedInfo만 안전하게 로딩하는지와 Feed 탭의 상태에 영향이 없는지 확인이 필요합니다.
로컬에서 Group → RoomMates/Note 진입 시 Feed 탭 상태(선택 인덱스, 로딩 플래그)가 의도치 않게 변하지 않는지 한 번 더 확인 부탁드립니다.
301-306: 내/타인 분기 로직이 중복됩니다 — 공용 헬퍼로 추출해 DRY 유지 권장두 곳 모두 동일한 분기 if (myUserId != null && myUserId == userId) … 로직을 사용하고 있습니다. 공용 함수(예: NavHostController 확장)로 추출하면 유지보수가 쉬워지고, 추후 분기 요건 변경 시 한 곳에서 수정 가능합니다.
적용 예시(선택):
- if (myUserId != null && myUserId == userId) { - navController.navigate(FeedRoutes.My) - } else { - navController.navigate(FeedRoutes.Others(userId)) - } + navController.navigateToProfile(myUserId, userId)추가: 파일 내부(또는 extensions)에 다음 유틸을 정의하세요.
// 예: NavigationExtensions.kt fun NavHostController.navigateToProfile(myUserId: Long?, targetUserId: Long) { if (myUserId != null && myUserId == targetUserId) { navigate(FeedRoutes.My) } else { navigate(FeedRoutes.Others(targetUserId)) } }Also applies to: 371-376
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (2)
35-47: 초기 진입 시 myUserId가 null이면 자기 프로필 클릭에도 Others로 갈 수 있습니다현재 myUserId는 uiState.myFeedInfo?.creatorId에서 파생되는데, Feed 탭이 ‘모두’ 탭(0)일 때는 myFeedInfo 로딩이 보장되지 않아 null일 수 있습니다. 이 경우 실제로 본인 프로필을 눌러도 Others로 분기될 수 있습니다.
- 단기: myUserId가 null이고 클릭된 userId가 서버에서 ‘isMyself’로 함께 내려오는 경우(있다면) 이를 활용해 보정.
- 장기: myUserId는 탭 상태와 무관한 전역 소스(세션/프로필)에서 제공받아 분기에 사용.
88-102: MySubscription에서도 동일한 my/others 분기 신뢰성 이슈가 있습니다myUserId 미로딩 시 자기 자신을 Others로 라우팅할 수 있습니다. 위 코멘트와 동일한 개선을 권장합니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)
162-169: 하드코딩된 토스트 문자열 + 불사용 변수(context)
- context를 정의했지만 사용하지 않습니다.
- 토스트 문구는 하드코딩 대신 stringResource/리소스로 관리하세요(현지화·일관성).
다음과 같이 정리할 수 있습니다(문자열 리소스가 있다면):
toastMessage = stringResource(R.string.report_feed_done) // 또는 toastMessage = context.getString(R.string.report_feed_done)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedMyScreen.kt (2)
164-170: 내 피드에서 OthersFeedCard 사용은 UX 미스매치 가능성기존 FeedScreen에서는 내 피드에 대해 MyFeedCard를 사용하며 “좋아요/저장 개념 없음”이라고 주석이 있습니다. 여기서는 OthersFeedCard를 사용해 액션이 노출되고, toFeedList에서 isLiked/isSaved를 항상 false로 넣어 상태 일치성도 떨어집니다.
- 선택 1(권장): MyFeedCard 사용으로 컴포넌트 정책 일관화
- 선택 2: OthersFeedCard가 액션 노출 여부를 제어할 수 있도록 파라미터(예: isSaveVisible, isLikeVisible)를 추가하고 내 피드에서는 비활성화
74-90: toFeedList()를 Composable 내부에 정의하면 재구성마다 람다 생성 비용이 발생합니다매핑 함수는 UI와 독립적이므로 top-level 확장/Mapper로 분리하는 것을 권장합니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt (3)
79-79: OthersSubscriptionContent 시그니처 갱신 OK. 동일한 typealias 적용으로 일관성 확보를 제안합니다.아래와 같이 공용 typealias를 적용하면 상위와 하위 컴포저블 간 시그니처가 한 곳에서 관리됩니다.
- onProfileClick: (userId: Long, isMyself: Boolean) -> Unit + onProfileClick: OnProfileClick위 변경에는 다음 import가 필요합니다:
import com.texthip.thip.ui.common.types.OnProfileClick
167-169: Preview: 가짜 데이터에서 isMyself 값을 다양화하여 분기 시각화를 돕는 것을 제안합니다.모든 항목이 true면 분기 검증에 한계가 있습니다. 짝/홀로 번갈아가며 값을 주면 UI 점검에 더 유리합니다.
- isMyself = true + isMyself = it % 2 == 0
46-46: 콜백 타입 재사용을 위한 typealias 도입 제안현재
OthersSubscriptionListScreen에만 쓰이고 있는 2-parameter 콜백 시그니처를 공용으로 관리하면, 추후 유사 패턴이 생길 때 중복 정의를 줄이고 오탈자 위험을 낮출 수 있습니다.제안 사항:
공용 typealias 정의
- 파일:
app/src/main/java/com/texthip/thip/ui/common/types/Handlers.ktpackage com.texthip.thip.ui.common.types /** 사용자 프로필 클릭 콜백(userId, isMyself) */ typealias OnProfileClick = (userId: Long, isMyself: Boolean) -> Unit
OthersSubscriptionListScreen에 적용import com.texthip.thip.utils.color.hexToColor + import com.texthip.thip.ui.common.types.OnProfileClick @Composable fun OthersSubscriptionListScreen( onNavigateBack: () -> Unit, - onProfileClick: (userId: Long, isMyself: Boolean) -> Unit = { _, _ -> }, + onProfileClick: OnProfileClick = { _, _ -> }, viewModel: OthersSubscriptionViewModel = hiltViewModel() ) { … }현재 코드베이스 내 콜백 현황
• 2-parameter (Long, Boolean) →OthersSubscriptionListScreen.kt,FeedNavigation.kt(229행)
• 1-parameter (Long) →CommentSection.kt,CommentBottomSheet.kt등에 여전히 사용 중
— 필요 시 별도 typealias((Long) -> Unit) 도입 검토기본값(
= { _, _ -> })은 실수로 콜백을 빼먹었을 때 문제 발견이 늦어질 수 있으니, 팀 합의가 된다면 제거 고려(선택 사항).
📜 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 (33)
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPlayingResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/users/response/OthersFollowersResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/common/CommentActionMode.kt(0 hunks)app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/common/buttons/SubGenreChipRow.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/common/cards/CardBookList.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/component/ImageViewerModal.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt(7 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedMyScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookListWithScrollbar.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt(0 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/FilterHeaderSection.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomBody.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/navigator/BottomNavigationBar.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt(7 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/theme/Color.kt(3 hunks)app/src/main/res/values/strings.xml(1 hunks)
💤 Files with no reviewable changes (2)
- app/src/main/java/com/texthip/thip/ui/common/CommentActionMode.kt
- app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt
🧰 Additional context used
🧬 Code Graph Analysis (8)
app/src/main/java/com/texthip/thip/ui/common/buttons/SubGenreChipRow.kt (1)
app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt (1)
OptionChipButton(30-93)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt (1)
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomMatesScreen.kt (1)
GroupRoomMatesScreen(29-66)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (1)
app/src/main/java/com/texthip/thip/ui/common/modal/ToastWithDate.kt (1)
ToastWithDate(24-61)
app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt (1)
app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (1)
SavedFeedCard(38-141)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedMyScreen.kt (6)
app/src/main/java/com/texthip/thip/ui/common/topappbar/DefaultTopAppBar.kt (1)
DefaultTopAppBar(24-75)app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt (1)
AuthorHeader(34-121)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/OthersFeedCard.kt (1)
OthersFeedCard(35-123)app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
ThipTheme(41-69)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (3)
app/src/main/java/com/texthip/thip/ui/common/modal/ToastWithDate.kt (1)
ToastWithDate(24-61)app/src/main/java/com/texthip/thip/ui/common/bottomsheet/MenuBottomSheet.kt (1)
MenuBottomSheet(22-59)app/src/main/java/com/texthip/thip/ui/common/modal/DialogPopup.kt (1)
DialogPopup(22-71)
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt (2)
app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt (1)
FeedContent(25-63)app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt (1)
BookContent(26-68)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (3)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)
FeedScreen(66-471)app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt (1)
MySubscriptionScreen(52-91)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedMyScreen.kt (1)
FeedMyScreen(40-61)
🔇 Additional comments (33)
app/src/main/java/com/texthip/thip/ui/navigator/BottomNavigationBar.kt (1)
27-31: 프리뷰용 import 추가 적절Preview/rememberNavController 도입으로 Studio 내 빠른 UI 확인이 가능해졌습니다. 별다른 부작용 없어 보입니다.
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt (2)
9-10: 스크롤 상태 도입 적절합니다긴 폼 입력 시 키보드/작은 화면 대응에 유효한 변경입니다. 불필요한 재구성을 유발하지 않는 패턴으로 사용되고 있어 좋습니다.
84-87: 루트 컨테이너 IME 대응 추가 👍Box에 advancedImePadding()을 적용하여 키보드에 가려지지 않도록 한 방향이 적절합니다. 상위 계층에서 중복된 ime/systemBars 패딩을 또 적용하지 않는지만 확인해 주세요(이중 인셋 적용 시 여백이 과해질 수 있습니다).
중복 인셋 확인 체크:
- 이 화면을 감싸는 상위 Scaffold/Container에 imePadding 또는 systemBarsPadding이 추가로 있는지 점검
- 키보드 열고/닫을 때 하단 요소가 이격만 생기고 레이아웃 점프(jump)가 없는지 육안 확인
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt (1)
81-85: IME 대응 패딩 적용 적합루트 Box에 advancedImePadding() 적용은 키보드 표시 시 입력 영역 가림을 방지하는데 효과적입니다. GroupNoteCreateScreen과 동일한 패턴으로 일관성이 있는 점도 좋습니다.
app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupBookSearchBottomSheet.kt (1)
6-6: fillMaxHeight로의 전환 적절BottomSheet 내부에서 높이를 퍼센트로 제한하는 방식은 다양한 단말 높이에 대응하기 용이합니다. 아래 컨테이너 변경과도 일관됩니다.
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPlayingResponse.kt (1)
23-23: userPercentage Int 변경: 코드베이스 검증 완료 & 백엔드 스키마 확인 요청– 모든 .kt 파일에서
userPercentage: Double선언 및roundToInt(…)호출 미발견
– 모든 .json 모킹 샘플에 소수점 퍼센티지 미발견모델을
Int로 유지해도 코드상 추가 변경은 불필요하나, 반드시 백엔드가 0..100 정수만 반환하도록 스키마/응답을 확인해 주세요.
만약 아직 소수점 값이 남아있다면, 아래와 같이 관대한 역직렬화를 적용하세요.
RoomsPlayingResponse.kt에 커스텀 직렬화기 지정- val userPercentage: Int, + @kotlinx.serialization.Serializable(with = PercentIntSerializer::class) + val userPercentage: Int,
- 새 파일
app/src/main/java/com/texthip/thip/data/serialization/PercentIntSerializer.kt추가package com.texthip.thip.data.serialization import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.JsonDecoder import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.doubleOrNull import kotlinx.serialization.json.intOrNull import kotlin.math.roundToInt object PercentIntSerializer : KSerializer<Int> { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("PercentInt", PrimitiveKind.INT) override fun deserialize(decoder: Decoder): Int { val jsonDecoder = decoder as? JsonDecoder ?: return decoder.decodeInt() val element = jsonDecoder.decodeJsonElement() as? JsonPrimitive ?: throw SerializationException("Cannot decode percent from $element") jsonDecoder.decodeJsonElement().let { it.intOrNull?.let { return it } it.doubleOrNull?.let { return it.roundToInt() } } throw SerializationException("Percent must be number, but was $element") } override fun serialize(encoder: Encoder, value: Int) { encoder.encodeInt(value) } }app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomBody.kt (1)
27-27: 타입 전파 및 사용처 업데이트 깔끔합니다
- Composable 파라미터/호출/프리뷰 모두 Int로 일관성 있게 변경되어 UI 층의 형변환 오버헤드가 제거되었습니다.
Also applies to: 47-47, 74-74
app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt (1)
304-304: 프리뷰 Double→Int 반영 정상입니다샘플 데이터 타입이 모델 변경과 일치합니다.
app/src/main/java/com/texthip/thip/ui/theme/Color.kt (2)
43-43: 새 반투명 블랙(Black80) 추가 LGTM모달/오버레이 배경에 적합한 알파값이며 팔레트 구성과 네이밍도 일관적입니다.
86-87: ThipColors 생성자 변경 검토 완료 – 영향 범위 없음rg 검색 결과
ThipColors(...)직접 호출은defaultThipColors에서만 이루어지고,ThipColors.copy()호출도 없어 추가 수정이 필요하지 않습니다.
안정적으로 빌드에 영향이 없음을 확인했습니다.app/src/main/java/com/texthip/thip/ui/common/cards/CardBookList.kt (2)
4-4: clickable import 추가 적절Row에 클릭 핸들러를 부여하기 위한 필수 import입니다.
44-46: onClick 파라미터 도입 LGTM — 기본값으로 역호환성 유지됨기존 호출부에 영향 없이 아이템 전체 탭 동작을 쉽게 주입할 수 있습니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (1)
64-69: 팔로우/언팔로우 토스트 메시지 구성 적절합니다닉네임 null-safety와 리소스 사용이 일관적입니다. 여기서는 LGTM입니다.
app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt (1)
38-39: 프로필 클릭 콜백 분리 좋습니다actionMode 제거 후 onProfileClick만 남겨 상위 네비게이션 분기에 맞게 단순화된 API가 명확합니다. 하위 ProfileBarFeed로 안전하게 전달됩니다.
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt (1)
36-60: 프로필 클릭 콜백 전달 LGTMonProfileClick이 ProfileBarFeed의 onClick으로 일관되게 전달되어 네비게이션/소유자 분기에 활용하기 좋습니다.
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt (2)
35-51: actionMode 제거에 따른 섹션 단순화 LGTMCommentItem/ReplyItem 호출부가 간결해졌고, 이벤트(onEvent) 라우팅도 명확합니다.
Also applies to: 58-73
53-56: creatorId 타입 일치 확인 완료CommentList.creatorId는 Long?이며 let 블록 내 id는 non-null Long이고, ReplyList.creatorId는 Long으로 onProfileClick(id)에 타입 불일치가 없습니다. 별도 toLong() 변환이 필요하지 않습니다.
app/src/main/java/com/texthip/thip/ui/feed/component/ImageViewerModal.kt (2)
70-71: 스타일(개행) 변경만 존재 — 동작 영향 없음별도 조치 불필요합니다.
108-112: 문자열 리소스 다중 인자 호출로 가독성 개선 — 좋습니다.표현식 분리로 리뷰/수정 용이성이 올라갑니다.
app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt (2)
12-12: 책 상세 내비게이션 import 추가 적절합니다.
15-15: 피드 댓글 내비게이션 import 추가 적절합니다.app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt (1)
53-55: 아이템 클릭/북마크 토글 연결 방식 LGTM
- onBookmarkClick에서 VM 액션 분리, onClick으로 ISBN 전달 모두 적절합니다.
app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt (2)
35-41: 리스트 구성 및 key 지정 방식 적절합니다.
- contentPadding/first item spacing/Stable key(feed.id) 모두 적절합니다.
27-30: feedId 타입 일관성 확인 완료MypageSaveScreen(onFeedClick: (feedId: Long)), FeedContent(onFeedClick: (feedId: Long)) 및 navigateToFeedComment(feedId: Long) 모두 Long으로 일치하므로 추가 변경이 필요 없습니다.
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt (2)
46-51: 신규 콜백(onBookClick, onFeedClick) 추가 및 기본값 처리 LGTM기본 no-op 제공으로 기존 호출부 영향 없이 확장되었습니다. 하위 컴포저블로의 전달도 자연스럽습니다.
120-136: 하위 콘텐츠로 콜백 전파가 일관적이고 명확합니다
- FeedContent: onFeedClick(feed.id)
- BookContent: onClick → onBookClick(book.isbn)
탭 전환과 데이터 로딩(LaunchedEffect(selectedTabIndex))도 적절합니다.
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (3)
173-185: FeedRoutes.My 화면 진입 로직 LGTMFeed 탭의 백스택에 스코프된 FeedViewModel을 재사용하고, FeedMyScreen 내에서 탭 인덱스 전환을 트리거하는 흐름이 자연스럽습니다.
201-226: Feed 댓글 화면으로의 내/타인 분기 연동이 적절합니다FeedCommentScreen으로부터의 프로필 네비게이션 콜백이 일관되게 FeedRoutes.My/Others로 분기됩니다.
231-237: 구독자 목록(onProfileClick) 분기 로직이 명확합니다isMyself 플래그를 활용해 My/Others를 정확히 분기하고 있습니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedMyScreen.kt (1)
49-51: 탭 전환 트리거 로직은 적절합니다LaunchedEffect(Unit)으로 최초 1회 My 탭 전환을 보장해 화면 진입 경험이 명확합니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt (3)
71-72: 하위 컴포저블로의 콜백 전달 배선이 명확합니다.단순 전달 패턴이어서 재조합에 따른 부작용이 없어 보입니다.
125-125: 아이템 클릭 시 isMyself 플래그를 함께 전달하는 방식이 요구사항에 부합합니다.
- 키(key)에 userId를 사용하고 있어 리스트 안정성도 확보되었습니다.
182-183: Preview 콜백 시그니처 반영이 정확합니다.미리보기에서 no-op으로 두는 선택도 합리적입니다. 필요하면 println/log로 클릭 동작을 가볍게 확인할 수 있습니다.
| modifier = if (isOverlayVisible) { | ||
| Modifier | ||
| .fillMaxSize() | ||
| .blur(5.dp) | ||
| .background(colors.Black800) | ||
| } else { | ||
| Modifier.fillMaxSize() | ||
| } | ||
| // 바깥 터치 시 키보드 숨기기 |
There was a problem hiding this comment.
🛠️ Refactor suggestion
오버레이(메뉴/다이얼로그/이미지뷰어) 표시 시 배경 터치가 차단되지 않습니다
현재는 blur만 적용되어 있고 입력을 소거하지 않아 배경의 터치가 통과할 수 있습니다. 오버레이가 띄워진 동안 포인터 이벤트를 소비하도록 수정하세요.
- Box(
- modifier = if (isOverlayVisible) {
- Modifier
- .fillMaxSize()
- .blur(5.dp)
- } else {
- Modifier.fillMaxSize()
- }
+ Box(
+ modifier = if (isOverlayVisible) {
+ Modifier
+ .fillMaxSize()
+ .blur(5.dp)
+ .pointerInput(Unit) {
+ awaitPointerEventScope {
+ while (true) { awaitPointerEvent() } // 모든 터치 소비
+ }
+ }
+ } else {
+ Modifier.fillMaxSize()
+ }📝 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.
| modifier = if (isOverlayVisible) { | |
| Modifier | |
| .fillMaxSize() | |
| .blur(5.dp) | |
| .background(colors.Black800) | |
| } else { | |
| Modifier.fillMaxSize() | |
| } | |
| // 바깥 터치 시 키보드 숨기기 | |
| Box( | |
| modifier = if (isOverlayVisible) { | |
| Modifier | |
| .fillMaxSize() | |
| .blur(5.dp) | |
| .pointerInput(Unit) { | |
| awaitPointerEventScope { | |
| while (true) { awaitPointerEvent() } // 모든 터치 소비 | |
| } | |
| } | |
| } else { | |
| Modifier.fillMaxSize() | |
| } | |
| ) { | |
| // 바깥 터치 시 키보드 숨기기 | |
| … | |
| } |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt around
lines 201 to 208, the overlay only applies a blur so pointer events still pass
through to the background; update the modifier so that when isOverlayVisible is
true it also consumes pointer events. Add a conditional pointer-consumption
layer (e.g., a pointerInput or clickable layer that continuously awaits and
consumes pointer events while the overlay is visible) on the same composable
that applies the blur, ensuring taps are swallowed and do not reach underlying
views; keep the non-overlay branch as Modifier.fillMaxSize().
| selectedCommentForMenu = CommentActionTarget(comment.commentId!!, comment.isWriter) | ||
| isCommentMenuVisible = true | ||
| }, | ||
| onReplyLongPress = { reply -> | ||
| selectedCommentId = reply.commentId | ||
| }, | ||
| onDismissPopup = { | ||
| selectedCommentId = null | ||
| selectedCommentForMenu = CommentActionTarget(reply.commentId, reply.isWriter) | ||
| isCommentMenuVisible = true | ||
| }, |
There was a problem hiding this comment.
NPE 위험: commentId가 null일 수 있는 타입에 대해 !! 사용
key에서 comment.commentId ?: comment.hashCode()로 널 가능성을 인정하고 있으나, 롱프레스 처리에서는 !!로 단언하고 있어 크래시 위험이 있습니다.
안전하게 처리하는 것으로 교체를 권장합니다:
- onCommentLongPress = { comment ->
- selectedCommentForMenu = CommentActionTarget(comment.commentId!!, comment.isWriter)
- isCommentMenuVisible = true
- },
+ onCommentLongPress = { comment ->
+ val id = comment.commentId
+ if (id != null) {
+ selectedCommentForMenu = CommentActionTarget(id, comment.isWriter)
+ isCommentMenuVisible = true
+ }
+ },
onReplyLongPress = { reply ->
- selectedCommentForMenu = CommentActionTarget(reply.commentId, reply.isWriter)
- isCommentMenuVisible = true
+ val id = reply.commentId
+ if (id != null) {
+ selectedCommentForMenu = CommentActionTarget(id, reply.isWriter)
+ isCommentMenuVisible = 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.
| selectedCommentForMenu = CommentActionTarget(comment.commentId!!, comment.isWriter) | |
| isCommentMenuVisible = true | |
| }, | |
| onReplyLongPress = { reply -> | |
| selectedCommentId = reply.commentId | |
| }, | |
| onDismissPopup = { | |
| selectedCommentId = null | |
| selectedCommentForMenu = CommentActionTarget(reply.commentId, reply.isWriter) | |
| isCommentMenuVisible = true | |
| }, | |
| onCommentLongPress = { comment -> | |
| val id = comment.commentId | |
| if (id != null) { | |
| selectedCommentForMenu = CommentActionTarget(id, comment.isWriter) | |
| isCommentMenuVisible = true | |
| } | |
| }, | |
| onReplyLongPress = { reply -> | |
| val id = reply.commentId | |
| if (id != null) { | |
| selectedCommentForMenu = CommentActionTarget(id, reply.isWriter) | |
| isCommentMenuVisible = true | |
| } | |
| }, |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt around
lines 381-387, remove the unsafe non-null assertion on comment.commentId (the
"!!") and instead compute a safe id consistent with the key logic (e.g., val id
= comment.commentId ?: comment.hashCode() or convert hashCode to Long if the
constructor expects Long), then pass that id into CommentActionTarget along with
comment.isWriter; this avoids NPEs by using the same null-safe fallback used
elsewhere.
| if (showDeleteDialog) { | ||
| Box( | ||
| Modifier | ||
| .fillMaxSize() | ||
| .clickable { showDialog = false }) { | ||
| .clickable(enabled = false, onClick = {}) | ||
| ) { | ||
| Box(Modifier.align(Alignment.Center)) { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
클릭 차단을 위해 clickable(enabled = false)는 효과가 없습니다
enabled=false는 포인터 입력을 추가하지 않아 배경 터치가 통과합니다. 빈 onClick으로 터치를 소비하세요.
- Box(
- Modifier
- .fillMaxSize()
- .clickable(enabled = false, onClick = {})
- ) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .clickable(
+ indication = null,
+ interactionSource = remember { androidx.compose.foundation.interaction.MutableInteractionSource() }
+ ) { /* consume clicks */ }
+ ) {📝 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 (showDeleteDialog) { | |
| Box( | |
| Modifier | |
| .fillMaxSize() | |
| .clickable { showDialog = false }) { | |
| .clickable(enabled = false, onClick = {}) | |
| ) { | |
| Box(Modifier.align(Alignment.Center)) { | |
| if (showDeleteDialog) { | |
| Box( | |
| Modifier | |
| .fillMaxSize() | |
| .clickable( | |
| indication = null, | |
| interactionSource = remember { androidx.compose.foundation.interaction.MutableInteractionSource() } | |
| ) { /* consume clicks */ } | |
| ) { | |
| Box(Modifier.align(Alignment.Center)) { |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt around
lines 517 to 523, the current Modifier.clickable(enabled = false, onClick = {})
does not block pointer events because enabled=false prevents adding pointer
handling; replace it with a clickable modifier that provides an empty onClick
handler (i.e., a consuming no-op onClick) so the background touch is consumed
and clicks do not pass through, ensuring the dialog overlay intercepts touches.
| @Serializable | ||
| data object My : FeedRoutes() |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
새 FeedRoutes.My 추가 자체는 적절합니다. 다만 네이밍/일관성 점검이 있으면 더 좋겠습니다.
- 네이밍: 기존 패턴(MySubscription, OthersSubscription, Others)을 고려하면
My는 다소 모호합니다.MyFeed등 구체적 네이밍을 고려해 보세요. - 타입 일관성(참고): 동일 파일 내 Write.feedId가 Int?이고, Comment.feedId는 Long입니다. 도메인 단위로 feedId 타입(Long 권장)을 일관화하면 파이프라인 전반(네비, VM, API)에서 캐스팅 잡음이 줄어듭니다.
- 내비게이션 등록 확인:
composable<FeedRoutes.My>가 실제 그래프에 등록되었는지, 관련 딥링크/업네비 동작(Back stack)도 함께 확인 부탁드립니다.
다음 스크립트로 사용/등록 여부와 타입 불일치를 빠르게 훑을 수 있습니다:
🏁 Script executed:
#!/bin/bash
# FeedRoutes.My 사용/그래프 등록 확인
rg -nP -C3 '\bcomposable<\s*FeedRoutes\.My\s*>' --type=kt
rg -nP -C2 '\bFeedRoutes\.My\b' --type=kt
# Write.feedId 타입 확인(참고)
rg -nP -n 'data\s+class\s+Write\(' app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.ktLength of output: 288
🏁 Script executed:
#!/bin/bash
# FeedRoutes.My 네비게이션 등록 여부 확인
rg -nP -C3 'composable<\s*FeedRoutes\.My\s*>' -g '*.kt'
rg -nP '\bFeedRoutes\.My\b' -g '*.kt'
# FeedRoutes.kt 내 feedId 타입 확인
rg -n 'feedId' app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.ktLength of output: 2064
FeedRoutes.My 추가 – 네이밍·타입·네비게이션 검토
-
네이밍
• 현재My는MySubscription,OthersSubscription,Others패턴 대비 다소 모호합니다.
•MyFeed등 보다 구체적인 이름을 검토해주세요. -
피드 ID 타입 일관성 (app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt)
•Write.feedId: Int?vsComment.feedId: Long
• 도메인(네비, ViewModel, API) 전반에서Long으로 통일해 불필요한 캐스팅과 잠재적 데이터 손실을 방지하세요. -
네비게이션 등록 확인
•app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt에 이미
composable<FeedRoutes.My>로 등록되어 있으며,
navController.navigate(FeedRoutes.My)호출도 다수 존재합니다.
• 딥링크 및 Back stack 동작이 의도대로 작동하는지 추가 검증을 부탁드립니다.
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt around
lines 34-35, the new route named `My` is ambiguous and the feedId types are
inconsistent; rename `My` to a more descriptive name like `MyFeed` (or
`MySubscriptionFeed`) to match the `MySubscription`/`OthersSubscription`
pattern, and change all feedId usages to Long (e.g., make Write.feedId: Long?
instead of Int?) across routes, ViewModels, navigation arguments, and API models
to ensure type consistency and avoid casts/data loss; after renaming and type
changes, update all navigation registrations
(app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt)
and every navController.navigate(...) call to use the new route, and run manual
tests for deep links and Back stack behavior to confirm navigation works as
intended.
➕ 이슈 링크
🔎 작업 내용
📸 스크린샷
😢 해결하지 못한 과제
[] TASK
📢 리뷰어들에게
Summary by CodeRabbit
New Features
UI/UX
Localization