[REFACTOR] QA 2번째 반영#110
Conversation
…into refactor/THIP-TextHip#101-qa2 # Conflicts: # app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt
WalkthroughFeed 관련 모델의 Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as 사용자
participant CBS as CommentBottomSheet
participant VM as CommentsViewModel
note over CBS,VM: 댓글(또는 답글) 생성 흐름
U->>CBS: 입력 후 Send 클릭
CBS->>VM: CommentsEvent.CreateComment(content, parentId)
VM-->>VM: 상태 업데이트(댓글/답글 추가)
VM-->>CBS: sideEffect.ScrollToItem(parentId)
CBS->>CBS: 입력 초기화 및 포커스 해제
CBS->>CBS: 요청된 항목으로 스크롤
sequenceDiagram
autonumber
actor U as 사용자
participant FS as FeedScreen
participant Nav as NavHostController
participant OSL as OthersSubscriptionListScreen
note over FS,Nav: OthersSubscription 네비게이션
U->>FS: 구독바 클릭
FS->>Nav: navigateToOthersSubscription(userId)
Nav->>OSL: OthersSubscriptionListScreen 표시
sequenceDiagram
autonumber
participant SS as SplashScreen
participant SVM as SplashViewModel
participant App as 앱 내비게이터
note over SS,SVM: 상태 기반 전환
SS->>SVM: navigateToLogin 상태 구독
SVM-->>SVM: 초기화 후 3초 지연 -> true 설정
SVM-->>SS: navigateToLogin = true
SS->>App: onNavigateToLogin() 호출
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 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: 9
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/signin/screen/SplashScreen.kt (1)
73-75: Preview 크래시 방지: 명시적 ViewModel 전달Preview에서
hiltViewModel()기본값을 사용하면 실패할 수 있습니다. 미리보기에서는 직접 인스턴스를 전달해 주세요.- SplashScreen() + SplashScreen(viewModel = com.texthip.thip.ui.signin.viewmodel.SplashViewModel())참고: 이미 상단에
SplashViewModelimport가 있으므로, 패키지 접두사 없이SplashViewModel()를 사용해도 됩니다. 패키지 접두사를 제거하려면 import를 유지하세요:- SplashScreen() + SplashScreen(viewModel = SplashViewModel())app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt (1)
1-146: 레거시 콜백 onThipNumClick 사용 흔적 발견 – 수정 필요컴파일 에러 방지를 위해 AuthorHeader 시그니처 변경에 맞춰 아래 콜백 및 호출부를 업데이트해 주세요:
- app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt (25행): 함수 파라미터
onThipNumClick- app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt (37행):
onClick = { onThipNumClick(user) }필수 리팩토링이 필요합니다.
🧹 Nitpick comments (15)
app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SplashViewModel.kt (2)
17-22: 네비게이션 신호를 "상태" 대신 "이벤트"로 모델링하는 개선 제안현재 불린 상태가 true가 된 뒤 별도 reset이 없으므로, 동일 화면에 잔류한 채 재구성이 발생하더라도 재트리거되진 않지만, 장기적으로는 One-shot 이벤트 모델(예:
SharedFlow<Unit>/Channel)이 의도가 더 명확합니다. 혹은 네비게이션 직후 false로 되돌리는 방법도 있습니다.예시(간단 reset 방식):
viewModelScope.launch { - delay(3_000L) - _navigateToLogin.value = true + delay(3_000L) + _navigateToLogin.value = true + // UI에서 처리 후 동일 화면에 남는 경우의 재트리거 방지 + _navigateToLogin.value = false }또는
MutableSharedFlow<Unit>(replay = 0)으로 바꾸고emit(Unit)을 사용하는 방식을 고려해주세요.
1-23: 지연 시간 상수화로 가독성/테스트 용이성 향상매직 넘버를 상수로 분리하면 의도가 명확해지고 테스트 시 주입이 쉬워집니다.
예시:
package com.texthip.thip.ui.signin.viewmodel +private const val SPLASH_DELAY_MS = 3_000L ... viewModelScope.launch { - delay(3_000L) + delay(SPLASH_DELAY_MS) _navigateToLogin.value = true }app/src/main/java/com/texthip/thip/ui/signin/screen/SplashScreen.kt (1)
45-49: Modifier 체인 순서 미세 개선 제안: size → background 순서현재
background().fillMaxSize()순서도 동작하지만, 일반적으로 사이즈 결정 후 배경을 그리는fillMaxSize().background(...)순서가 더 직관적입니다.- Modifier - .background(colors.Black) - .fillMaxSize(), + Modifier + .fillMaxSize() + .background(colors.Black),app/src/main/java/com/texthip/thip/data/model/feed/response/FeedUsersInfoResponse.kt (1)
5-16: 직렬화 네이밍 일관성 제안.동일 계층의 FeedMineInfoResponse는 @SerialName을 사용하고, 본 타입은 기본 이름 매핑을 사용합니다. 백엔드 필드가 고정되어 있다면, 두 모델 모두 동일한 스타일로 맞추는 편이 유지보수에 유리합니다.
예: 이 파일에 @SerialName을 추가하는 경우
package com.texthip.thip.data.model.feed.response -import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable @Serializable data class FeedUsersInfoResponse( - val creatorId: Long, + @SerialName("creatorId") val creatorId: Long, val profileImageUrl: String, ... )app/src/main/java/com/texthip/thip/data/model/feed/response/FeedMineInfoResponse.kt (1)
6-17: ID 전용 값타입 도입 고려(미래 변경 내성 강화).creatorId 같은 식별자는 @JvmInline value class로 감싸 두면 Int/Long 마이그레이션, 잘못된 형 변환, 파라미터 순서 실수 등을 컴파일 타임에 차단할 수 있습니다.
예:
@JvmInline value class CreatorId(val value: Long)
- 네비/DB/매퍼/도메인에 동일 타입 적용 → 변환 지점이 명확해지고 오용이 줄어듭니다.
app/src/main/res/values/themes.xml (1)
6-8: 기본 테마에서 전역 투명 창 속성 분리 및 전용 Translucent 테마 추가기본 애플리케이션 테마인
Theme.Thip에windowIsTranslucent를 제거하고, 실제 투명 창이 필요한 액티비티에만 적용할 전용 테마를 분리하세요.· 수정 필요 위치
• app/src/main/res/values/themes.xml
• app/src/main/AndroidManifest.xml· 제안된 변경사항
--- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ - <style name="Theme.Thip" parent="Base.Theme.Thip"> - <item name="android:windowIsTranslucent">true</item> - </style> + <style name="Theme.Thip" parent="Base.Theme.Thip" /> + <style name="Theme.Thip.Translucent" parent="Base.Theme.Thip"> + <item name="android:windowIsTranslucent">true</item> + </style>--- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ application - android:theme="@style/Theme.Thip" + android:theme="@style/Theme.Thip" @@ <activity android:name=".MainActivity" - android:theme="@style/Theme.Thip"> + android:theme="@style/Theme.Thip.Translucent">· 적용 방법
- 기본 애플리케이션/대부분 액티비티는
Theme.Thip유지- 투명 창이 필요한 액티비티(예: SplashActivity, DialogActivity 등)만
android:theme="@style/Theme.Thip.Translucent"로 설정위와 같이 분리하면 전역 성능 저하·제스처/배경 누락 문제를 방지할 수 있습니다.
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml (1)
2-5: Android 13 테마 아이콘 지원 여부 확인(모노크롬 제거).monochrome 요소가 제거되어 A13 테마 아이콘이 비활성화될 수 있습니다. 의도라면 OK, 아니라면 monochrome 추가를 고려해 주세요.
예:
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/ic_launcher_background"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/> + <monochrome android:drawable="@mipmap/ic_launcher_monochrome"/> </adaptive-icon>검증 스크립트:
#!/bin/bash set -euo pipefail echo "[1] monochrome 요소 존재 여부" rg -n -C1 '<monochrome' app/src/main/res/mipmap-anydpi-v26 || trueapp/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt (1)
25-38: AuthorHeader API 변경 대응은 적절합니다. 다만 콜백 이름(onThipNumClick)은 의미에 맞게 정리해 주세요.이제 헤더 전체가 클릭 대상이므로 onThipNumClick 네이밍은 오해의 소지가 있습니다. onUserClick 또는 onHeaderClick 등으로 바꾸면 가독성과 의도가 더 명확해집니다.
아래처럼 파라미터명만 정리하면 호출부(프리뷰 포함) 영향이 없습니다.
-fun SearchPeopleResult( - modifier: Modifier = Modifier, - peopleList: List<MySubscriptionData>, - onThipNumClick: (MySubscriptionData) -> Unit = {} -) +fun SearchPeopleResult( + modifier: Modifier = Modifier, + peopleList: List<MySubscriptionData>, + onUserClick: (MySubscriptionData) -> Unit = {} +) ... - AuthorHeader( + AuthorHeader( profileImage = user.profileImageUrl, nickname = user.nickname, badgeText = user.role, profileImageSize = 36.dp, showButton = false, showThipNum = true, thipNum = user.subscriberCount, - onClick = { onThipNumClick(user) } + onClick = { onUserClick(user) } )app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml (1)
3-4: Android 13 테마 아이콘 지원 검토 권장
ic_launcher.xml및ic_launcher_round.xml모두<foreground android:drawable="@mipmap/ic_launcher_foreground"/>로 일관되게 설정되어 있으며,
mipmap-hdpi~mipmap-xxxhdpi에ic_launcher_foreground.webp리소스가 정상 존재함을 확인했습니다.- 다만
monochrome요소가 제거되어 Android 13+ 테마 아이콘 기능이 비활성화됩니다.
제품 정책상 테마 아이콘 지원이 계속 필요하다면, 별도 단색 전용 리소스를 유지하는 리팩터를 권장드립니다.예시:
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/ic_launcher_background"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/> <monochrome android:drawable="@mipmap/ic_launcher_monochrome"/> </adaptive-icon>app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt (1)
48-48: 주석 표현을 더 명확하게"띱 목록으로 이동"은 모호할 수 있습니다. "다른 사용자 띱 목록으로 이동"으로 구체화하면 가독성이 좋아집니다.
-// 띱 목록으로 이동 +// 다른 사용자 띱 목록으로 이동app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt (1)
154-165: 클릭 처리를 AuthorHeader로 이관한 방향은 좋습니다. 다만 클릭 영역 변화(헤더만 클릭 가능) 영향을 확인해 주세요.이전에는 아이템 전체(Column)가 클릭 가능했다면, 지금은 헤더 영역만 클릭됩니다. 기획/UX 요구사항이 "아이템 전체 클릭"이라면 기존 범위를 유지하는 편이 나을 수 있습니다.
옵션 B(아이템 전체 클릭 유지)를 원하시면 아래처럼 되돌리는 것을 고려해 보세요.
-Column(modifier = Modifier.padding(horizontal = 20.dp)) { +Column(modifier = Modifier + .padding(horizontal = 20.dp) + .clickable { onUserClick(user.userId) } +) { AuthorHeader( profileImage = user.profileImageUrl, nickname = user.nickname, badgeText = user.aliasName, badgeTextColor = hexToColor(user.aliasColor), buttonText = stringResource(if (user.isFollowing) R.string.thip_cancel else R.string.thip), buttonWidth = 64.dp, profileImageSize = 36.dp, onButtonClick = { onToggleFollow(user.userId, user.nickname) }, - onClick = { onUserClick(user.userId) } + // 아이템 전체 클릭으로 일원화 시 헤더 onClick은 제거 )추가로 필요한 import:
import androidx.compose.foundation.clickable옵션 A(현재처럼 헤더만 클릭)로 유지 시에는 별도 변경 불필요합니다. 이 경우 헤더가 Row 전역 클릭을 처리하므로 ripple/접근성(semantics)도 AuthorHeader 내부에서 일관되게 관리되는 장점이 있습니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)
244-246: 내 피드 → 띱 목록 네비게이션 연결 정상 동작 예상myFeedInfo?.creatorId 존재 시 OthersSubscription으로 라우팅하는 흐름 일관적입니다.
- 네이밍 측면에서는 “OthersSubscription”이 내 피드에서도 쓰이는 점이 살짝 혼동을 줄 수 있습니다. 장기적으로 route/확장 함수명을 “UserSubscription” 등 중립적으로 리네임 고려해도 좋겠습니다.
app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt (1)
53-55: 접근성(세만틱) 보완: Row.clickable에 role 지정 권장스크린리더/테스트 자동화를 위해 Role 지정이 있으면 좋습니다.
다음 변경을 제안합니다:
- .clickable(onClick = onClick), + .clickable( + role = androidx.compose.ui.semantics.Role.Button, + onClick = onClick + ),추가로 필요한 import(선택):
import androidx.compose.ui.semantics.Roleapp/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)
292-295: 댓글 버튼 클릭 시 입력창 포커싱을 바로 연결하세요현재 주석으로 남아있는 포커스 처리 TODO를 바로 구현하면 UX가 매끄러워집니다.
다음과 같이 최소 구현으로 포커스만 요청해도 체감 개선 효과가 큽니다.
- onCommentClick = { /* 스크롤 이동 or 포커스 처리 */ }, + onCommentClick = { + focusRequester.requestFocus() + },추가로 스크롤 이동까지 원하시면
rememberCoroutineScope()를 도입해listState.animateScrollToItem(...)를 호출하는 확장을 도와드릴 수 있습니다.app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (1)
148-155: 답글 병합 시 중복 방지 또는 응답 스키마 확인 필요
originalParentComment.replyList + res.replyList는 응답이 “신규 답글 1건”이 아닌 “부모의 최신 전체 답글 리스트” 형태일 경우 중복을 야기할 수 있습니다. 응답 스키마를 확인하거나 안전장치를 추가하세요.안전한 병합 예시:
- val updatedParentComment = originalParentComment.copy( - replyList = originalParentComment.replyList + res.replyList - ) + val updatedParentComment = originalParentComment.copy( + replyList = (originalParentComment.replyList + res.replyList) + .distinctBy { it.commentId } + )응답이 “신규 답글 1건”이라면, 명시적으로
res.replyList.singleOrNull()만 추가하는 것도 방법입니다.
📜 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 ignored due to path filters (1)
app/src/main/ic_launcher-playstore.pngis excluded by!**/*.png
📒 Files selected for processing (21)
app/src/main/java/com/texthip/thip/data/model/feed/response/FeedMineInfoResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/feed/response/FeedUsersInfoResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt(6 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(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt(11 hunks)app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/signin/screen/SplashScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SplashViewModel.kt(1 hunks)app/src/main/res/drawable/ic_launcher_background.xml(1 hunks)app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml(1 hunks)app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml(1 hunks)app/src/main/res/values/themes.xml(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (1)
app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt (1)
OthersSubscriptionListScreen(43-73)
🔇 Additional comments (37)
app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SplashViewModel.kt (2)
14-16: StateFlow 캡슐화 방식 좋아요쓰기 가능한
MutableStateFlow를 private으로 두고, 외부에는 읽기 전용StateFlow로 노출한 점 적절합니다.
12-13: 의존성 없는 ViewModel에 @Inject 유지 vs 제거현재 의존성이 없는 ViewModel이지만 Hilt로 관리하려면 생성자에 @Inject가 필요합니다. 위 import만
javax.inject.Inject로 바꾸면 현 구조 유지가 적절합니다.app/src/main/java/com/texthip/thip/ui/signin/screen/SplashScreen.kt (2)
39-43: Lifecycle-aware 상태 기반 네비게이션 전환, 적절합니다
LaunchedEffect(navigateToLogin)로 키를 걸고 true일 때만 네비게이션을 트리거하는 패턴이 깔끔합니다. 중복 네비게이션도 방지됩니다.
32-36:SplashScreenviewModel 기본값 사용 확인 완료현재 코드베이스에
viewModel파라미터를 명시적으로 전달하는SplashScreen호출이 없으므로, 모두 기본값(hiltViewModel())이 안전하게 사용되고 있습니다. 추가 조치는 필요하지 않습니다.app/src/main/java/com/texthip/thip/data/model/feed/response/FeedUsersInfoResponse.kt (1)
7-7: creatorId 타입 Long 전환 자체는 합리적입니다.대규모 ID 스페이스를 고려한 확장으로 보이며, 모델 수준에서는 문제 없어 보입니다.
app/src/main/java/com/texthip/thip/data/model/feed/response/FeedMineInfoResponse.kt (1)
8-8: RelatedBooksResponse.kt/RoomsDailyGreetingResponse.kt도 creatorId:Int → Long으로 통일 필요다음 지점에서 추가 수정이 필요해 보입니다:
- app/src/main/java/com/texthip/thip/data/model/feed/response/RelatedBooksResponse.kt (라인 16)
@SerialName("creatorId") val creatorId: Int→ Long으로 변경- app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsDailyGreetingResponse.kt (라인 15)
val creatorId: Int→ Long으로 변경현재 코드베이스에서
- Compose Navigation(
NavType.IntType→NavType.LongType,getInt→getLong)- Bundle/Intent (
putInt/getInt→putLong/getLong)- Room 엔티티·DAO·쿼리 파라미터
- 매핑 계층(toDomain/toUi/toEntity)에서의
toInt()캐스팅
사용 흔적은 발견되지 않았습니다.
하지만 변경 후 실제 네비게이션, 번들, DB 매퍼 흐름에 영향이 없는지 반드시 교차 확인해 주세요.app/src/main/res/drawable/ic_launcher_background.xml (1)
2-74: 포맷 정리만 이뤄졌고 기능적 변화는 없습니다. LGTM.리소스 속성/값/도형 데이터는 동일합니다.
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml (1)
3-4: 리소스 확인 완료: ic_launcher_foreground가 mipmap에 정상 존재하며 adaptive-icon XML에서 올바르게 참조되고 있습니다.
추가 조치 없이 머지해 주세요.app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt (1)
37-37: 확인 결과 레거시onThipNumClick=명명 인자 사용 흔적 없음전체 Kotlin 파일을 대상으로 검색한 결과, 다음과 같습니다:
onThipNumClick=형태의 명명 인자 사용 사례: 없음- 일반 호출(
onThipNumClick(user))은LiveSearchPeopleResult.kt:37에서만 존재추가로 남아 있는 레거시 호출은 없으므로, 더 이상 조치할 필요가 없습니다.
app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt (1)
29-30: OthersSubscription 라우트 추가 적절합니다
- @serializable 적용과 userId: Long 타입 일관성 모두 적절합니다.
app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt (1)
49-51: 확장 함수 추가 LGTMFeedRoutes.OthersSubscription(userId)로의 네비게이션 헬퍼가 일관된 패턴으로 잘 추가되었습니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)
75-75: 새 네비게이션 콜백 추가로 확장성 확보 (역호환 유지 OK)기본값을 빈 람다로 두어 호출부 수정 없이도 컴파일되는 점 좋습니다. 공개 API 확장 방향 적절합니다.
app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt (2)
48-49: 헤더 단일 onClick 진입점으로 인터랙션 통합이전 onThipNumClick 분산을 정리하고 헤더 전체 클릭으로 통일한 점 좋습니다. 내부 OutlinedButton 등 중첩 클릭과의 충돌은 Compose에서 하위 요소가 우선 소비하므로 문제 없을 것으로 보입니다. 의도가 “버튼 영역은 버튼만, 그 외는 헤더 클릭”이면 현재 구현으로 충분합니다.
버튼 영역까지 헤더 onClick이 동작하지 않아야 한다는 명확한 UI 요구사항이 있는지 한번 확인 부탁드립니다. 현재 구현은 버튼 탭 시 버튼만 동작합니다.
142-142: 프리뷰 데이터 최신화 LGTM새 인터랙션 모델에 맞춰 프리뷰 파라미터(thipNum) 반영된 점 좋습니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (5)
42-42: 새 콜백(onNavigateToSubscriptionList) 도입 적절스크린 외부로 네비게이션을 위임하는 패턴과 잘 맞습니다. 기본값 제공으로 역호환도 보장됩니다.
50-52: 콜백 전달 체인 정리 완료ViewModel의 좋아요 토글과 신규 네비게이션 콜백을 Content로 전달하는 구조 명확합니다.
59-61: Content 시그니처 확장 합리적상위에서 받은 콜백을 콘텐츠로 내려 재사용성을 높였습니다.
193-195: 프리뷰 시그니처 보완 LGTM새 파라미터 추가로 프리뷰도 안전하게 컴파일됩니다.
100-103: 팔로워 바 클릭 → 구독 목록 네비게이션 연결 확인 ✅
FeedUsersInfoResponse.creatorId가 Long으로 정의되어 있어 OthersSubscription(userId: Long) 호출부와 타입이 일치함을 확인했습니다. 코드 변경을 승인합니다.app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (5)
14-14: OthersSubscriptionListScreen 매핑 import 추가 OK
20-20: navigateToOthersSubscription 확장 함수 import OK
48-53: FeedScreen → OthersSubscription 네비게이션 연결 정상FeedScreen의 새 콜백을 NavController 확장으로 라우팅. 일관된 네비게이션 계층입니다.
125-127: 다른 유저 피드 → 구독 목록 네비게이션 연결 OKFeedOthersScreen에서의 신규 콜백도 동일한 확장을 사용해 일관성 유지되었습니다.
151-158: FeedRoutes.OthersSubscription 파라미터 바인딩 정상 확인
- app/src/main/java/com/texthip/thip/ui/feed/viewmodel/OthersSubscriptionViewModel.kt:
private val userId: Long = requireNotNull(savedStateHandle["userId"])
SavedStateHandle을 통해 userId를 올바르게 수신하고 있습니다.추가 조치 불필요합니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt (6)
44-47: 엔트리 포인트 시그니처 정비(Back/프로필 콜백) 좋습니다NavController 의존 제거로 테스트 용이성과 재사용성이 개선됩니다. 오탈자(Subscrip-tion) 정정도 반갑네요.
67-72: Content에 콜백 주입 구조 명확상위에서 받은 네비게이션 콜백을 그대로 위임해 계층 책임이 잘 나뉘었습니다.
75-80: Content 시그니처 확장 합리적onNavigateBack, onProfileClick 모두 UI와 상호작용 흐름에 맞게 배치되었습니다.
116-126: 리스트 아이템 헤더 클릭 → 프로필 이동 연결 OKAuthorHeader의 onClick을 사용해 상단 전역 클릭으로 통일한 점이 다른 화면과도 일관적입니다.
159-159: 프리뷰 함수명/시그니처 정리 LGTM
172-182: 프리뷰 콜백 인자 추가로 컴파일 안전성 확보목 데이터/상태도 적절합니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (2)
155-167: 키보드 가려짐 시 Reply 상태/포커스 정리 로직 적절합니다IME 가 내려가면 replyingTo 상태와 포커스를 정리하는 흐름이 UI 기대와 일치합니다.
WindowInsets.ime.getBottom(LocalDensity.current) > 0접근도 합리적입니다.
356-357: Reply 클릭 시 입력창 포커스 처리 LGTM
FocusRequester를 사용한 포커스 요청과 입력창에의 연결이 올바르게 구성되어 있습니다.Also applies to: 376-399
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (1)
201-246: 답글 좋아요 API 재사용 검증
toggleReplyLike에서commentsRepository.likeComment(replyId, ...)를 호출합니다. 동일 엔드포인트로 댓글/답글 좋아요를 모두 처리하도록 설계되었는지 확인해 주세요. 엔드포인트가 분리되어 있다면 분기 필요합니다.원본 인터페이스에서 reply/parent 식별 방식(파라미터명, path 차이 등)을 확인 부탁드립니다.
app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt (1)
505-513: CommentBottomSheet에 ViewModel 주입으로 API 일관화된 점 좋습니다콜백 기반에서 ViewModel 주입형으로 전환되며 호출부가 간결해졌습니다.
initialize타이밍도LaunchedEffect(selectedPostForComment?.postId)내에서 적절합니다.app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt (3)
68-82: 키보드/포커스 흐름 도입 LGTM
FocusRequester연결 + IME 가려짐 시 reply 상태 초기화가 의도와 잘 맞습니다.
124-136: 이벤트 위임을 ViewModel로 일원화한 점 좋습니다로드모어, 삭제 등 액션이 VM으로 수렴되어 테스트/유지보수성이 향상됩니다. Reply 클릭 시 포커싱도 적절합니다.
225-243: 스크롤 끝 감지 및 로드모어 트리거 로직 적절합니다
LazyListState.layoutInfo기반의 끝 감지,LaunchedEffect로의 로드모어 트리거가 깔끔합니다.isLoadingMore/isLastPage게이트도 적절합니다.Also applies to: 251-286
| @Serializable | ||
| data class FeedUsersInfoResponse( | ||
| val creatorId: Int, | ||
| val creatorId: Long, |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Int 가정 잔존 여부 점검 필요 (NavType/Bundle/매퍼/DB).
모델은 Long으로 바뀌었지만, 다음 지점에서 Int 전제가 남아 있으면 런타임/네비게이션 버그가 납니다:
- Compose Navigation: NavType.IntType, getInt/putInt, route 인코딩 시 toInt()
- Bundle/Intent extras: putInt/getInt
- 매핑 확장함수: toInt() 캐스팅
- Room/DB 엔티티/DAO 시그니처: creatorId: Int
아래 스크립트로 잔존 사용을 한 번에 확인해 주세요.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "[1] creatorId: Int 선언 검색"
rg -n --type=kt -C2 '\bcreatorId\s*:\s*Int\b' || true
echo
echo "[2] NavType/Bundle Int 사용 검색"
rg -n --type=kt -C2 'NavType\.IntType|\bgetInt\(\s*"creatorId"\s*\)|\bputInt\(\s*"creatorId"\s*,' || true
echo
echo "[3] creatorId 관련 toInt() 캐스팅 검색"
rg -n --type=kt -C2 'creatorId[^;\n]*\.toInt\(\)|\.toInt\(\)[^;\n]*creatorId' || true
echo
echo "[4] getLong 사용 현황(전환 여부)"
rg -n --type=kt -C2 '\bgetLong\(\s*"creatorId"' || trueLength of output: 715
NavType/Bundle Int용 검사를 위해 .kt 파일만 지정하여 다시 검색해 보겠습니다.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "[1] `creatorId: Int` 선언 검색 (.kt 파일 대상)"
rg -n -C2 '\bcreatorId\s*:\s*Int\b' -g '*.kt' || true
echo
echo "[2] NavType.IntType / getInt·putInt 사용 검색"
rg -n -C2 'NavType\.IntType|\bgetInt\(\s*"creatorId"\s*\)|\bputInt\(\s*"creatorId"\s*,' -g '*.kt' || true
echo
echo "[3] `creatorId.toInt()` 캐스팅 검색"
rg -n -C2 'creatorId[^;\n]*\.toInt\(\)|\.toInt\(\)[^;\n]*creatorId' -g '*.kt' || true
echo
echo "[4] getLong으로 전환된 호출 검색"
rg -n -C2 '\bgetLong\(\s*"creatorId"\s*\)' -g '*.kt' || trueLength of output: 2136
creatorId 타입 일관성 유지 필요
모델에서 Long으로 변경된 creatorId가 여전히 Int로 선언된 곳이 발견되었습니다. 런타임/네비게이션 버그를 방지하려면 아래 파일들을 모두 Long으로 변경하고, 매퍼·NavType·Bundle·DB 시그니처도 함께 검토해 주세요.
• app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsDailyGreetingResponse.kt (15줄)
• app/src/main/java/com/texthip/thip/data/model/feed/response/RelatedBooksResponse.kt (16줄)
예시 변경:
- val creatorId: Int,
+ val creatorId: Long,Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/feed/response/FeedUsersInfoResponse.kt
around line 7 (creatorId), change the creatorId declaration from Int to Long and
propagate that change to all related code: update the two other occurrences at
app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsDailyGreetingResponse.kt
(line 15) and
app/src/main/java/com/texthip/thip/data/model/feed/response/RelatedBooksResponse.kt
(line 16) to use Long; then update any mappers (DTO↔domain), NavType
implementations/argument parsing, Bundle put/get calls to use putLong/getLong,
and DB schema/DAO signatures and entity fields to Long so types remain
consistent end-to-end.
| val listState = rememberLazyListState() | ||
|
|
||
| val isKeyboardVisible = WindowInsets.ime.getBottom(LocalDensity.current) > 0 | ||
|
|
||
| LaunchedEffect(isKeyboardVisible) { | ||
| if (!isKeyboardVisible) { | ||
| replyingToCommentId = null | ||
| replyingToNickname = null | ||
| focusManager.clearFocus() | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
VM SideEffect(ScrollToItem) 수신 누락: 답글 작성 후 해당 부모 위치로 스크롤 연동 필요
CommentsViewModel이 ScrollToItem 사이드이펙트를 발행하지만, 이 화면에서는 수집하지 않아 스크롤이 동작하지 않습니다. 또, 수집자가 없으면 ViewModel 쪽 emit이 서스펜드되는 리스크가 있어 UI 일관성을 위해 반드시 수집을 권장합니다.
다음 코드를 val listState 선언 인근에 추가해 주세요:
LaunchedEffect(commentsViewModel.sideEffect, commentsUiState.comments) {
commentsViewModel.sideEffect.collect { effect ->
when (effect) {
is CommentSideEffect.ScrollToItem -> {
val targetIndex = effect.parentCommentId?.let { id ->
commentsUiState.comments.indexOfFirst { it.commentId == id }
} ?: 0
if (targetIndex >= 0) {
listState.animateScrollToItem(targetIndex)
}
}
}
}
}또는, 답글 시작(onReplyClick) 시에도 입력창 포커스와 함께 해당 항목까지 스크롤하도록 보강할 수 있습니다.
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt around
lines 157 to 168, the screen currently creates listState but does not collect
CommentsViewModel sideEffect(s) so CommentSideEffect.ScrollToItem is ignored
(and the ViewModel emit may suspend); add a LaunchedEffect that collects
commentsViewModel.sideEffect and when receiving CommentSideEffect.ScrollToItem
compute the target index with commentsUiState.comments.indexOfFirst {
it.commentId == effect.parentCommentId } (use 0 or skip if index < 0), then call
listState.animateScrollToItem(targetIndex); ensure this LaunchedEffect depends
on commentsViewModel.sideEffect and commentsUiState.comments so it runs when
data updates; optionally also trigger the same scroll when handling onReplyClick
alongside focusing the input.
| val focusRequester = remember { FocusRequester() } | ||
| val listState = rememberLazyListState() | ||
|
|
||
| val focusManager = LocalFocusManager.current | ||
|
|
||
| val isKeyboardVisible = WindowInsets.ime.getBottom(LocalDensity.current) > 0 | ||
|
|
||
| LaunchedEffect(isKeyboardVisible) { | ||
| if (!isKeyboardVisible) { | ||
| replyingToCommentId = null | ||
| replyingToNickname = null | ||
| focusManager.clearFocus() | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
VM의 ScrollToItem SideEffect 수신/스크롤 처리 누락
ViewModel에서 발행하는 ScrollToItem을 수신하지 않아 답글 작성 후 해당 부모 댓글로의 이동이 이뤄지지 않습니다. 또, 수신자가 없으면 VM의 emit이 서스펜드될 수 있으므로 여기서 수집을 권장합니다.
아래 블록을 focusRequester, listState 정의 이후에 추가해 주세요:
LaunchedEffect(viewModel.sideEffect, uiState.comments) {
viewModel.sideEffect.collect { effect ->
when (effect) {
is CommentSideEffect.ScrollToItem -> {
val index = effect.parentCommentId?.let { id ->
uiState.comments.indexOfFirst { it.commentId == id }
} ?: 0
if (index >= 0) {
listState.animateScrollToItem(index)
}
}
}
}
}Also applies to: 124-136
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt
around lines 68-82 (and apply same for 124-136), the composable never collects
the ViewModel's ScrollToItem side effect so the list doesn't scroll to the
parent comment after replying and the emitter can suspend; add a LaunchedEffect
tied to viewModel.sideEffect and uiState.comments immediately after the
focusRequester and listState definitions that collects viewModel.sideEffect,
handles CommentSideEffect.ScrollToItem by finding the index of the
parentCommentId in uiState.comments (or 0 if null) and calls
listState.animateScrollToItem(index) when index >= 0 so the UI scrolls
correctly.
| CommentTextField( | ||
| modifier = Modifier.fillMaxWidth(), | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .focusRequester(focusRequester), | ||
| hint = stringResource(R.string.reply_to), | ||
| input = inputText, | ||
| onInputChange = { inputText = it }, | ||
| onSendClick = { | ||
| onSendReply( | ||
| inputText, | ||
| replyingToCommentId, | ||
| replyingToNickname | ||
| viewModel.onEvent( | ||
| CommentsEvent.CreateComment( | ||
| content = inputText, | ||
| parentId = replyingToCommentId | ||
| ) | ||
| ) | ||
| inputText = "" | ||
| replyingToCommentId = null | ||
| replyingToNickname = null | ||
| focusManager.clearFocus() | ||
| }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
빈 입력 전송 시 UX 결함: 입력/Reply 상태가 불필요하게 초기화됩니다
ViewModel의 createComment는 빈 문자열이면 즉시 return하지만, UI는 빈 입력에도 상태를 초기화합니다. 빈 입력이면 early-return 하세요.
다음 패치를 적용해 주세요:
onSendClick = {
- viewModel.onEvent(
+ if (inputText.isBlank()) return@onSendClick
+ viewModel.onEvent(
CommentsEvent.CreateComment(
content = inputText,
parentId = replyingToCommentId
)
)
inputText = ""
replyingToCommentId = null
replyingToNickname = null
focusManager.clearFocus()
},📝 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.
| CommentTextField( | |
| modifier = Modifier.fillMaxWidth(), | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .focusRequester(focusRequester), | |
| hint = stringResource(R.string.reply_to), | |
| input = inputText, | |
| onInputChange = { inputText = it }, | |
| onSendClick = { | |
| onSendReply( | |
| inputText, | |
| replyingToCommentId, | |
| replyingToNickname | |
| viewModel.onEvent( | |
| CommentsEvent.CreateComment( | |
| content = inputText, | |
| parentId = replyingToCommentId | |
| ) | |
| ) | |
| inputText = "" | |
| replyingToCommentId = null | |
| replyingToNickname = null | |
| focusManager.clearFocus() | |
| }, | |
| CommentTextField( | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .focusRequester(focusRequester), | |
| hint = stringResource(R.string.reply_to), | |
| input = inputText, | |
| onInputChange = { inputText = it }, | |
| onSendClick = { | |
| // Prevent resetting state on blank submissions | |
| if (inputText.isBlank()) return@onSendClick | |
| viewModel.onEvent( | |
| CommentsEvent.CreateComment( | |
| content = inputText, | |
| parentId = replyingToCommentId | |
| ) | |
| ) | |
| inputText = "" | |
| replyingToCommentId = null | |
| replyingToNickname = null | |
| focusManager.clearFocus() | |
| }, |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt
around lines 146 to 164, the UI currently clears inputText, replyingToCommentId,
replyingToNickname and clears focus even when the comment is empty; add an
early-return guard that checks if inputText.trim().isEmpty() and if so do
nothing (i.e. do not call viewModel.onEvent and do not reset
replying/input/focus), otherwise proceed to call CommentsEvent.CreateComment and
then clear inputText, replyingToCommentId, replyingToNickname and clear focus.
| private val _sideEffect = MutableSharedFlow<CommentSideEffect>() | ||
| val sideEffect = _sideEffect.asSharedFlow() | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
SharedFlow emit이 수집자 부재 시 영구 서스펜드될 수 있습니다 → 버퍼/tryEmit로 전환 권장
현재 _sideEffect.emit(...)는 수집자가 없으면 서스펜드됩니다. 본 PR에는 어느 화면에서도 sideEffect.collect가 구현되어 있지 않아(아래 검증 스크립트 참고) 성공 후 코루틴이 멈출 수 있습니다. 버퍼를 주거나 tryEmit로 바꾸세요.
다음 패치를 권장합니다:
- private val _sideEffect = MutableSharedFlow<CommentSideEffect>()
+ private val _sideEffect = MutableSharedFlow<CommentSideEffect>(
+ replay = 0,
+ extraBufferCapacity = 1
+ )
val sideEffect = _sideEffect.asSharedFlow()- _sideEffect.emit(CommentSideEffect.ScrollToItem(parentId))
+ _sideEffect.tryEmit(CommentSideEffect.ScrollToItem(parentId))다음 스크립트로 sideEffect.collect 수신 여부를 코드베이스 전역에서 확인해 주세요:
Also applies to: 166-167
🏁 Script executed:
#!/bin/bash
# sideEffect 수집 여부/위치 확인
rg -nP -C3 '\bsideEffect\s*\.\s*collect\s*\(' --type=kotlin
# MutableSharedFlow 설정 파라미터 확인
rg -nP -C2 'MutableSharedFlow\s*<\s*CommentSideEffect\s*>\s*\(' --type=kotlinLength of output: 716
SharedFlow emit이 수집자 부재 시 영구 서스펜드됨 → extraBufferCapacity/tryEmit 적용 필요
전역 검색 결과 sideEffect.collect 호출이 전혀 없어 _sideEffect.emit(...) 시 코루틴이 멈출 위험이 있습니다. 아래와 같이 수정해주세요:
- MutableSharedFlow 초기화 시 버퍼 용량 추가
- emit 대신 tryEmit 사용
- private val _sideEffect = MutableSharedFlow<CommentSideEffect>()
+ private val _sideEffect = MutableSharedFlow<CommentSideEffect>(
+ replay = 0,
+ extraBufferCapacity = 1
+ )
val sideEffect = _sideEffect.asSharedFlow()- _sideEffect.emit(CommentSideEffect.ScrollToItem(parentId))
+ _sideEffect.tryEmit(CommentSideEffect.ScrollToItem(parentId))적용 위치:
- app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt (45–47, 166–167)
📝 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.
| private val _sideEffect = MutableSharedFlow<CommentSideEffect>() | |
| val sideEffect = _sideEffect.asSharedFlow() | |
| // In CommentsViewModel.kt around lines 45–47 | |
| private val _sideEffect = MutableSharedFlow<CommentSideEffect>( | |
| replay = 0, | |
| extraBufferCapacity = 1 | |
| ) | |
| val sideEffect = _sideEffect.asSharedFlow() | |
| // …later in the same file around lines 166–167… | |
| _sideEffect.tryEmit(CommentSideEffect.ScrollToItem(parentId)) |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt
around lines 45–47 and 166–167, the MutableSharedFlow is created without a
buffer and the code calls emit which will suspend indefinitely if there are no
collectors; change the MutableSharedFlow initialization to include a small
buffer (e.g., MutableSharedFlow<CommentSideEffect>(extraBufferCapacity = 1)) and
replace _sideEffect.emit(...) calls with _sideEffect.tryEmit(...); after tryEmit
check the boolean result and handle failures (log or safely drop the event) so
producers never suspend when no collectors are present.
| viewModel: SplashViewModel = hiltViewModel(), | ||
| onNavigateToLogin: () -> Unit = {} | ||
| ) { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
기본값에서 hiltViewModel() 호출은 Preview에서 크래시 유발 가능
기본 파라미터로 hiltViewModel()을 호출하면 Compose Preview 환경에서 LocalViewModelStoreOwner/HiltViewModelFactory 부재로 예외가 발생할 수 있습니다. 미리보기에서는 명시적으로 뷰모델을 전달하는 패턴이 안전합니다.
두 가지 대안 중 하나를 권장합니다:
- 최소 변경: Preview에서만 명시적 ViewModel 전달(아래 73-75 라인 코멘트 참고).
- 구조 개선: 파라미터를 nullable로 두고 내부에서 fallback 적용.
구조 개선 예시:
-fun SplashScreen(
- viewModel: SplashViewModel = hiltViewModel(),
- onNavigateToLogin: () -> Unit = {}
-) {
- val navigateToLogin by viewModel.navigateToLogin.collectAsStateWithLifecycle()
+fun SplashScreen(
+ viewModel: SplashViewModel? = null,
+ onNavigateToLogin: () -> Unit = {}
+) {
+ // Preview 등 Hilt 미지원 컨텍스트 대비
+ val vm = viewModel ?: runCatching { hiltViewModel<SplashViewModel>() }.getOrNull() ?: SplashViewModel()
+ val navigateToLogin by vm.navigateToLogin.collectAsStateWithLifecycle()📝 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.
| viewModel: SplashViewModel = hiltViewModel(), | |
| onNavigateToLogin: () -> Unit = {} | |
| ) { | |
| fun SplashScreen( | |
| viewModel: SplashViewModel? = null, | |
| onNavigateToLogin: () -> Unit = {} | |
| ) { | |
| // Preview 등 Hilt 미지원 컨텍스트 대비 | |
| val vm = viewModel | |
| ?: runCatching { hiltViewModel<SplashViewModel>() } | |
| .getOrNull() | |
| ?: SplashViewModel() | |
| val navigateToLogin by vm.navigateToLogin.collectAsStateWithLifecycle() | |
| // …rest of your UI logic remains unchanged… | |
| } |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/signin/screen/SplashScreen.kt around
lines 34-36, calling hiltViewModel() as a default parameter can crash Compose
Preview due to missing Hilt/VM factory; change the function signature so the
viewModel parameter is nullable (e.g., viewModel: SplashViewModel? = null) and
inside the composable obtain a real instance with a fallback (val vm = viewModel
?: hiltViewModel()) before use, ensuring previews can pass an explicit mock and
runtime uses Hilt.
| package com.texthip.thip.ui.signin.viewmodel | ||
|
|
||
| import androidx.lifecycle.ViewModel | ||
| import androidx.lifecycle.viewModelScope | ||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||
| import jakarta.inject.Inject | ||
| import kotlinx.coroutines.delay | ||
| import kotlinx.coroutines.flow.MutableStateFlow | ||
| import kotlinx.coroutines.flow.asStateFlow | ||
| import kotlinx.coroutines.launch | ||
|
|
||
| @HiltViewModel | ||
| class SplashViewModel @Inject constructor() : ViewModel() { | ||
| private val _navigateToLogin = MutableStateFlow(false) | ||
| val navigateToLogin = _navigateToLogin.asStateFlow() | ||
|
|
||
| init { | ||
| viewModelScope.launch { | ||
| delay(3000) | ||
| _navigateToLogin.value = true | ||
| } | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
리포 내 jakarta.inject 잔존 여부 확인 권고 스크립트
다른 파일에도 jakarta.inject가 남아있을 수 있습니다. 아래 스크립트로 전체 레포를 점검해 주세요.
🏁 Script executed:
#!/bin/bash
# 레포 내 주입 어노테이션 사용 현황 점검
set -euo pipefail
echo "== jakarta.inject 사용 위치 =="
rg -nP --hidden --no-ignore-vcs '\bjakarta\.inject\.(Inject|Singleton|Named)\b' -g '!**/build/**' || true
echo
echo "== javax.inject 사용 위치 =="
rg -nP --hidden --no-ignore-vcs '\bjavax\.inject\.(Inject|Singleton|Named)\b' -g '!**/build/**' || trueLength of output: 6426
SplashViewModel.kt의 @Inject import 통일 필요
현재 리포 전반에서 javax.inject를 사용 중이며, 해당 파일만 jakarta.inject.Inject를 사용하고 있습니다. 아래처럼 수정해 주세요:
--- a/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SplashViewModel.kt
+++ b/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SplashViewModel.kt
@@
- import jakarta.inject.Inject
+ import javax.inject.Inject만약 전체 코드를 jakarta.inject로 마이그레이션할 계획이라면, 잔존하는 javax.inject import도 모두 동일하게 변경해야 합니다.
📝 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.
| package com.texthip.thip.ui.signin.viewmodel | |
| import androidx.lifecycle.ViewModel | |
| import androidx.lifecycle.viewModelScope | |
| import dagger.hilt.android.lifecycle.HiltViewModel | |
| import jakarta.inject.Inject | |
| import kotlinx.coroutines.delay | |
| import kotlinx.coroutines.flow.MutableStateFlow | |
| import kotlinx.coroutines.flow.asStateFlow | |
| import kotlinx.coroutines.launch | |
| @HiltViewModel | |
| class SplashViewModel @Inject constructor() : ViewModel() { | |
| private val _navigateToLogin = MutableStateFlow(false) | |
| val navigateToLogin = _navigateToLogin.asStateFlow() | |
| init { | |
| viewModelScope.launch { | |
| delay(3000) | |
| _navigateToLogin.value = true | |
| } | |
| } | |
| } | |
| package com.texthip.thip.ui.signin.viewmodel | |
| import androidx.lifecycle.ViewModel | |
| import androidx.lifecycle.viewModelScope | |
| import dagger.hilt.android.lifecycle.HiltViewModel | |
| import javax.inject.Inject | |
| import kotlinx.coroutines.delay | |
| import kotlinx.coroutines.flow.MutableStateFlow | |
| import kotlinx.coroutines.flow.asStateFlow | |
| import kotlinx.coroutines.launch | |
| @HiltViewModel | |
| class SplashViewModel @Inject constructor() : ViewModel() { | |
| private val _navigateToLogin = MutableStateFlow(false) | |
| val navigateToLogin = _navigateToLogin.asStateFlow() | |
| init { | |
| viewModelScope.launch { | |
| delay(3000) | |
| _navigateToLogin.value = true | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SplashViewModel.kt
lines 1-23, the file imports jakarta.inject.Inject while the rest of the
repository uses javax.inject; replace the jakarta.inject.Inject import with
javax.inject.Inject so the project uses a consistent injection package (or if
you intend to migrate to jakarta.inject, update all other files to jakarta
instead) — update the import statement accordingly and ensure the class
annotation remains @HiltViewModel and constructor injection still works.
| import androidx.lifecycle.ViewModel | ||
| import androidx.lifecycle.viewModelScope | ||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||
| import jakarta.inject.Inject |
There was a problem hiding this comment.
Hilt 주입 어노테이션 import 오류: jakarta.inject.Inject → javax.inject.Inject로 교체 필요
Android/Hilt 환경에서는 javax.inject.Inject를 사용합니다. 현재 jakarta.inject.Inject는 일반적으로 종속성이 없고 Hilt와 호환되지 않아 컴파일/런타임 오류가 발생할 수 있습니다.
다음과 같이 수정해주세요:
-import jakarta.inject.Inject
+import javax.inject.Inject📝 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.
| import jakarta.inject.Inject | |
| import javax.inject.Inject |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SplashViewModel.kt
around line 6, the Hilt injection import is wrong: replace the current import of
jakarta.inject.Inject with javax.inject.Inject so Hilt/Android DI resolves
correctly; update the import line accordingly and verify there are no other
jakarta.inject usages in this file or module that should be switched to
javax.inject.
|
|
||
| init { | ||
| viewModelScope.launch { | ||
| delay(3000) |
There was a problem hiding this comment.
delay 인자 타입 불일치: 3000(Int) → 3000L(Long)
kotlinx.coroutines.delay는 Long을 요구합니다. 현재 코드는 컴파일 에러가 납니다.
- delay(3000)
+ delay(3_000L)📝 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.
| delay(3000) | |
| delay(3_000L) |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SplashViewModel.kt
around line 19, the call delay(3000) uses an Int literal but
kotlinx.coroutines.delay requires a Long; change the literal to a Long (e.g.,
delay(3000L)) or explicitly cast to Long so the code compiles.
There was a problem hiding this comment.
Actionable comments posted: 0
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/component/OthersFeedCard.kt (2)
76-87: 좋아요 콜백이 누락되어 작동하지 않습니다
OthersFeedCard의onLikeClick파라미터가ActionBarButton에 전달되지 않아 좋아요 클릭이 무시됩니다. 상위(FeedOthersScreen)에서feedId를 캡처해 내려보내고 있으므로, 여기서 그대로 연결해야 합니다.아래처럼
onLikeClick을 직접 전달하세요.ActionBarButton( isLiked = feedItem.isLiked, likeCount = feedItem.likeCount, commentCount = feedItem.commentCount, isSaveVisible = true, - onLikeClick = { -// onLikeClick(feedItem.feedId) - }, + onLikeClick = onLikeClick, onBookmarkClick = { } )
95-102: Preview 타입 불일치:feedId가 Long이라면1L리터럴 사용 필요PR 전반에서
creatorId및feedId가 Long으로 이행되었습니다.Preview의feedId = 1는 컴파일 오류(또는 암묵 캐스팅 실패) 가능성이 있습니다.- feedId = 1, + feedId = 1L,
🧹 Nitpick comments (3)
app/src/main/java/com/texthip/thip/ui/feed/component/OthersFeedCard.kt (2)
38-38: 카드 전체 클릭 처리: 액션 영역(좋아요/북마크)과의 제스처 충돌 가능성 점검 권장부모 Column 전체에
clickable을 부여하면, 하위의 아이콘 버튼들이 클릭을 소비하지 못하는 경우 콘텐츠 클릭 네비게이션이 의도치않게 트리거될 수 있습니다(특히 새로 도입한 댓글 네비게이션 흐름에서 UX 이슈). 현재ActionBarButton내부가 확실히 클릭을 소비한다면 문제 없겠지만, 회귀 위험이 있어 분리 권장합니다.최소 변경으로 부모의
clickable을 제거하고, 본문(Text/이미지) 구간에만 클릭을 부여하는 방안을 제안합니다.modifier = modifier .fillMaxWidth() .padding(horizontal = 20.dp) - .clickable { onContentClick() }본문 블록 쪽(텍스트와 이미지 영역)에만 클릭을 추가하는 예시:
// Text 위에 Text( text = feedItem.contentBody, // ... modifier = Modifier .fillMaxWidth() .padding(vertical = 16.dp) .clickable { onContentClick() } ) // 이미지가 있는 경우, 이미지 Row에도 클릭을 부여할지 정책에 따라 결정 if (hasImages) { Row( modifier = Modifier .fillMaxWidth() .padding(bottom = 16.dp) .clickable { onContentClick() }, // ... ) { /* ... */ } }검증 포인트:
- 좋아요/북마크 클릭 시 댓글 화면으로 이동하지 않는지
- 본문/이미지 클릭 시에만 댓글 화면으로 이동하는지
40-44: 북마크 클릭 처리 미연결
onBookmarkClick이 비어 있습니다. 이번 PR 스코프 밖이라면 TODO로 명시하거나, 콜백을 파라미터로 받아서 상위에 위임하는 패턴 유지가 좋습니다(현재 onLikeClick과 동일한 구조).app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (1)
46-46: 수명주기 인식 수집으로 변경 제안:collectAsStateWithLifecycle()Compose 화면 수명주기와의 정합을 위해
collectAsStateWithLifecycle사용을 권장합니다. 화면 비가시 상태에서의 불필요한 수집을 방지합니다.-import androidx.compose.runtime.collectAsState +import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ - val uiState by viewModel.uiState.collectAsState() + val uiState by viewModel.uiState.collectAsStateWithLifecycle()
📜 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 (3)
app/src/main/java/com/texthip/thip/ui/feed/component/OthersFeedCard.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt
🔇 Additional comments (6)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (6)
42-44: 새 네비게이션 콜백 추가 LGTM
onNavigateToSubscriptionList,onNavigateToFeedComment추가 및 기본값 제공으로 호출 부담 최소화한 점 좋습니다. 상위 네비게이션 계층과의 연결 방향도 일관적입니다.
51-54: 콜백 전달은 적절하나, 하위 카드에서 실제 호출되는지 확인 필요여기서
OthersFeedCard에onLikeClick/onContentClick을 넘기지만, 카드 내부에서 좋아요 콜백이 호출되지 않으면 기능이 동작하지 않습니다. 현재OthersFeedCard는ActionBarButton의onLikeClick이 no-op 상태입니다. 제안한 수정대로 카드에서 콜백을 연결해 주세요.검증 방법(수정 후):
- 피드 항목의 좋아요를 눌렀을 때
viewModel.changeFeedLike(feedId)가 호출되는지 로그/디버거로 확인- 본문 클릭 시에만
onNavigateToFeedComment(feedId)호출되는지 확인
61-64: 파라미터 구성 적절
FeedOthersContent가 화면-상호작용 콜백을 명확히 수신하도록 정리된 점 좋습니다. 기본값 없는 콜백(onLikeClick,onNavigateToSubscriptionList)은 강제 의존으로 설계되어 오용 여지를 줄입니다.
144-147: 본문 클릭 → 댓글 화면 네비게이션 연결 LGTM리스트 아이템 본문 클릭 시 댓글 화면으로 이동하는 요구사항과 일치합니다. 스페이서/디바이더 처리도 적절합니다.
196-198: Preview 기본 콜백 주입 적절미리보기에서 필수 콜백만 주입하고, 선택 콜백은 기본값으로 두어 간결하게 유지한 점 좋습니다.
105-106: 응답 모델의 creatorId가 여전히 Int로 정의되어 있음
userInfo.creatorId는 Long 타입으로 변경된 것이 확인되었으나, 아래 응답 모델에 여전히 Int로 선언된 creatorId가 존재합니다. 이들이 최종 userInfo로 매핑될 때 Long으로 변환되는지, 또는 해당 모델들의 creatorId 타입을 Long으로 변경해야 하는지 검토 부탁드립니다.
- app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsDailyGreetingResponse.kt:
val creatorId: Int- app/src/main/java/com/texthip/thip/data/model/feed/response/RelatedBooksResponse.kt:
@SerialName("creatorId") val creatorId: Int
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (5)
app/src/main/java/com/texthip/thip/ui/common/forms/WarningTextField.kt (5)
75-83: (선택) OutlinedTextFieldDefaults.colors 사용으로 속성 의도 명확화현재 OutlinedTextField에 TextFieldDefaults.colors를 적용하고 indicator 컬러를 제어하고 있는데, Material3에서는 OutlinedTextFieldDefaults.colors의 border 컬러 파라미터(focusedBorderColor, unfocusedBorderColor)를 쓰는 편이 의도 전달과 유지보수에 더 명확합니다. 동작은 동일하게 가져가면서 API 의미를 맞출 수 있습니다.
적용 예(해당 범위 내 교체):
- colors = TextFieldDefaults.colors( - unfocusedTextColor = colors.White, - focusedTextColor = colors.White, - focusedIndicatorColor = if (showWarning) colors.Red else Color.Transparent, - unfocusedIndicatorColor = if (showWarning) colors.Red else Color.Transparent, - focusedContainerColor = containerColor, - unfocusedContainerColor = containerColor, - cursorColor = colors.NeonGreen - ), + colors = OutlinedTextFieldDefaults.colors( + unfocusedTextColor = colors.White, + focusedTextColor = colors.White, + focusedBorderColor = if (showWarning) colors.Red else Color.Transparent, + unfocusedBorderColor = if (showWarning) colors.Red else Color.Transparent, + focusedContainerColor = containerColor, + unfocusedContainerColor = containerColor, + cursorColor = colors.NeonGreen + ),추가(파일 상단 import, 기존 TextFieldDefaults import는 제거 가능):
import androidx.compose.material3.OutlinedTextFieldDefaults
46-49: 텍스트/배경 대비 확인 요청텍스트 컬러를 White로 고정(focused/unfocused)했기 때문에, containerColor(기본 Black)가 밝은 색으로 오버라이드 되는 화면이 있다면 대비가 떨어질 수 있습니다. 실제 사용처에서 containerColor가 밝게 지정되는 케이스가 있는지 확인 부탁드립니다. 필요 시 theme의 onColor(contentColorFor)를 활용하는 방법도 고려해볼 수 있습니다.
Also applies to: 75-83
86-91: (선택) IconButton 사용으로 터치 타깃/접근성 개선현재 Icon에 clickable을 직접 붙여 터치 타깃이 작습니다(보통 24dp 수준). IconButton을 사용하면 최소 터치 영역, 포커스, 리플 등 접근성/UX가 개선됩니다.
해당 범위 교체:
- Icon( - painter = painterResource(id = R.drawable.ic_x_circle_grey), - contentDescription = "Clear text", - modifier = Modifier.clickable { onValueChange("") }, - tint = Color.Unspecified - ) + IconButton(onClick = { onValueChange("") }) { + Icon( + painter = painterResource(id = R.drawable.ic_x_circle_grey), + contentDescription = "Clear text", + tint = Color.Unspecified + ) + }추가(import):
import androidx.compose.material3.IconButton
60-64: 숫자 필터링 범위 확인(로케일/유니코드 숫자 포함 여부)
Char.isDigit()는 전각/아라비아 숫자 등 유니코드 숫자 전반을 허용합니다. ASCII 숫자만 허용해야 하는 요구(예: PIN, 코드)가 있다면'0'..'9'범위로 한정하는 편이 안전합니다. 요구사항에 따라 확인 부탁드립니다. 필요 시 적용 코드 제안 가능합니다.
175-184: (선택) NumberPassword일 때 가시화 정책 결정
KeyboardType.NumberPassword를 사용하지만visualTransformation이 없어 실제 입력이 마스킹되지 않습니다. 비밀번호 성격이라면PasswordVisualTransformation()적용 또는isPassword플래그 추가를 고려해 주세요. 현재 프리뷰/사용처 의도에 따라 선택 사항입니다.
📜 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 (1)
app/src/main/java/com/texthip/thip/ui/common/forms/WarningTextField.kt(1 hunks)
🔇 Additional comments (1)
app/src/main/java/com/texthip/thip/ui/common/forms/WarningTextField.kt (1)
75-83: unfocusedTextColor 추가 👍포커스 해제 시에도 텍스트 컬러가 White로 유지되도록 한 수정은 UI 일관성과 가독성 측면에서 적절합니다. 실제 현행 스타일(컨테이너 Black)과도 잘 맞습니다.
➕ 이슈 링크
🔎 작업 내용
📸 스크린샷
😢 해결하지 못한 과제
📢 리뷰어들에게
Summary by CodeRabbit
신기능
개선/리팩터링
스타일