Conversation
Walkthrough피드 좋아요/저장 상태 동기화용 SharedFlow와 SavedStateHandle 전달 로직이 추가되고, 사람 검색 화면 및 라우트가 도입되었습니다. 회원가입 플로우가 닉네임→장르→튜토리얼→완료로 재구성되었고, 마이페이지 로그아웃 시 로그인으로 이동하도록 네비게이션이 연결되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Detail as FeedCommentScreen
participant Repo as FeedRepository
participant Shared as SharedFlow
participant Nav as NavController
participant Saved as SavedStateHandle
participant Feed as FeedScreen
participant VM as FeedViewModel
User->>Detail: 좋아요/저장 토글
Detail->>Repo: changeFeedLike/save(feedId, newStatus, currentCounts...)
Repo-->>Shared: emit(FeedStateUpdateResult)
Repo-->>Detail: API Result
Note right of Detail: 성공 시 이전화면에 primitive 저장
Detail->>Saved: set(updated_feed_id, updated_feed_isLiked, ...)
Detail-->>Nav: navigateBack()
Feed->>Saved: observe(updated_feed_*)
Saved-->>Feed: 전달된 primitives
Feed->>VM: updateFeedStateFromResult(result)
VM-->>Feed: UI 상태 갱신
sequenceDiagram
autonumber
actor User
participant AuthNav as AuthNavigation
participant Nick as NicknameScreen
participant Genre as GenreScreen
participant Tutor as TutorialScreen
participant Done as SignupDoneScreen
AuthNav->>Nick: start(SignupScreenRoutes.Nickname)
User->>Nick: 완료
Nick-->>AuthNav: navigate(Genre)
User->>Genre: 완료
Genre-->>AuthNav: navigate(Tutorial)
User->>Tutor: Next/Skip 반복
Tutor-->>AuthNav: navigate(Done)
User->>Done: 시작하기
Done-->>AuthNav: onNavigateToMain()
sequenceDiagram
autonumber
actor User
participant MyPage as MyPageScreen
participant VM as MyPageViewModel
participant Token as TokenManager
participant Nav as AppNav
User->>MyPage: 로그아웃 확인
MyPage->>VM: confirmLogout()
VM->>Token: clearTokens()
Token-->>VM: 완료
VM-->>MyPage: isLogoutCompleted = true
MyPage-->>Nav: onNavigateToLogin()
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)
105-114: deleted_feed_id에 대해서도 observeForever 제거 권장위의 업데이트 처리와 동일하게, 삭제 이벤트도 observeForever로 등록되어 누수 위험이 있습니다. StateFlow로 전환하면 일관성과 안정성이 좋아집니다.
아래와 같이 교체를 제안합니다:
- LaunchedEffect(Unit) { - navController.currentBackStackEntry?.savedStateHandle?.let { handle -> - handle.getLiveData<Long>("deleted_feed_id").observeForever { deletedId -> - if (deletedId != null) { - feedViewModel.removeDeletedFeed(deletedId) - handle.remove<Long>("deleted_feed_id") - } - } - } - } + LaunchedEffect(navController) { + val handle = navController.currentBackStackEntry?.savedStateHandle ?: return@LaunchedEffect + handle.getStateFlow<Long?>("deleted_feed_id", null).collectLatest { deletedId -> + if (deletedId != null) { + feedViewModel.removeDeletedFeed(deletedId) + handle.remove<Long>("deleted_feed_id") + } + } + }
🧹 Nitpick comments (55)
app/src/main/java/com/texthip/thip/ui/feed/component/ImageViewerModal.kt (2)
48-59: 아이콘은 '뒤로가기'인데 contentDescription은 '닫기'입니다. 접근성/의도 불일치 + IconButton으로 48dp 터치 타깃 보장 권장
- 시각적 아이콘(뒤로가기)과 라벨("닫기")가 불일치합니다. 스크린리더 사용 시 혼란을 줄 수 있어 일치시켜 주세요.
- Icon 대신 IconButton을 사용하면 최소 터치 타깃(48dp)과 포커스/리플 등 접근성 이점을 자동으로 얻을 수 있습니다.
- 상단 좌측으로 이동하면서 고정 20dp 패딩만 주면 노치/상태바 영역과 겹칠 수 있습니다. statusBarsPadding을 함께 적용하는 것을 권장합니다.
아래와 같이 교체를 제안합니다. 리소스 문자열은 팀 컨벤션에 맞춰 back 또는 close 중 하나로 통일해 주세요.
- // 이전 버튼 - Icon( - painter = painterResource(R.drawable.ic_arrow_back), - contentDescription = "닫기", - tint = colors.White, - modifier = Modifier - .align(Alignment.TopStart) - .padding(20.dp) - .size(24.dp) - .clickable { onDismiss() } - .zIndex(1f) - ) + // 이전 버튼 + IconButton( + onClick = onDismiss, + modifier = Modifier + .align(Alignment.TopStart) + .statusBarsPadding() + .padding(20.dp) + .size(48.dp) // 최소 터치 타깃 + .zIndex(1f) + ) { + Icon( + painter = painterResource(R.drawable.ic_arrow_back), + contentDescription = stringResource(id = R.string.accessibility_back), // 또는 R.string.close + tint = colors.White, + ) + }추가 필요 import:
import androidx.compose.material3.IconButton import androidx.compose.foundation.layout.statusBarsPadding[system_note] 만약 실제 의도가 “모달 닫기”라면 아이콘을 X(닫기)로 유지하고 contentDescription도 “닫기”로 통일하는 편이 더 명확합니다.
44-47: 모달 배경색이 누락되어 Letterbox(여백)에서 뒤 화면이 비칠 수 있습니다ContentScale.Fit으로 인해 이미지의 상하/좌우에 여백이 생길 때, 루트 컨테이너에 배경색이 없으면 아래 화면이 보일 수 있습니다. PR 목표에 “이미지 모달에 배경색 적용”이 있으므로, 루트 Box에 배경을 명시하는 것을 권장합니다.
- modifier = Modifier - .fillMaxSize() - .clickable { onDismiss() } + modifier = Modifier + .fillMaxSize() + .background(colors.Black) // 모달 배경색 + .clickable { onDismiss() }필요 시 투명도를 주어 딤 처럼 보이게 할 수도 있습니다.
.background(colors.Black.copy(alpha = 0.9f))app/src/main/res/values/strings.xml (1)
309-309: 브랜드 문자열은 번역 제외 권장브랜드명(Thip+)은 다국어에서도 동일 표기가 일반적이라 translatable="false"를 권장합니다.
다음 변경을 제안합니다:
- <string name="thip_plus">Thip+</string> + <string name="thip_plus" translatable="false">Thip+</string>app/src/main/java/com/texthip/thip/ui/signin/mock/SignupUserInfo.kt (1)
5-5: profileImage 타입 변경(Int? → String?)은妥 — 네이밍 명확화 제안이미지 리소스 ID에서 URL/경로 문자열로 전환은 적절합니다. 다만 의미를 더 명확히 하기 위해 profileImage → profileImageUrl로의 네이밍 변경을 고려해주세요. 또한 null일 때의 플레이스홀더 처리(기본 아바타/이니셜)를 모든 호출부에서 일관되게 적용했는지 확인 바랍니다.
가능하다면 다음과 같이 리네이밍을 권장합니다(연쇄 수정 필요):
-data class SignupUserInfo( - val nickname: String, - val profileImage: String?, - val role: String -) +data class SignupUserInfo( + val nickname: String, + val profileImageUrl: String?, + val role: String +)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageEditScreen.kt (1)
118-118: 닉네임 힌트를 고정 문자열로 변경 — 요구사항/디자인 의도 확인 및 길이 상수와의 동기화 권장동적 힌트(초기 닉네임 여부 등)에서 고정 문자열로 바뀌었습니다. Figma/QA 기준으로 고정 문구가 맞는지 확인 부탁드립니다. 또한 힌트 문구가 실제 제한(maxLength=10)과 불일치할 여지가 있어, 길이 상수를 한 곳에서 관리하도록 권장드립니다.
아래와 같이 길이 상수를 추출해 힌트/제한을 동일 소스로 맞추면 드리프트를 줄일 수 있습니다.
@@ WarningTextField( containerColor = colors.DarkGrey02, value = uiState.nickname, onValueChange = onNicknameChange, - hint = stringResource(R.string.nickname_condition), + hint = stringResource(R.string.nickname_condition), // 힌트가 길이 정보를 포함한다면 포맷 리소스로 전환 고려 showWarning = uiState.nicknameWarningMessageResId != null, showIcon = false, showLimit = true, - maxLength = 10, + maxLength = MAX_NICKNAME_LENGTH, warningMessage = uiState.nicknameWarningMessageResId?.let { stringResource(it) } ?: "" )파일 상단(동일 파일 내 최상위)에 다음 상수 정의 추가를 권장드립니다:
private const val MAX_NICKNAME_LENGTH = 10app/src/main/java/com/texthip/thip/ui/feed/mock/FeedStateUpdateResult.kt (1)
1-11: 패키지 명칭mock재검토 권장프로덕션 이벤트 플로우에서 사용되는 DTO가
ui.feed.mock에 위치하면 오해의 소지가 있습니다. 의미상ui.feed.model또는data.feed.model등 프로덕션 지향 네임스페이스로 이전을 고려해 주세요.app/src/main/java/com/texthip/thip/ui/feed/mock/MySubscriptionData.kt (2)
9-9: 사소한 스타일: 공백 정리가독성을 위해
=뒤에 공백을 추가해 주세요.- val userId: Long? =null, + val userId: Long? = null,
20-20: userId 변환 안전 확인됨
UserItem.userId는 non-null Int이므로this.userId.toLong()호출은 안전하며 예외가 발생하지 않습니다.
- 변환 로직 유지 (
toLongOrNull()불필요)- MySubscriptionData.userId가 항상 값이 전달되므로
val userId: Long? = null→val userId: Long으로 변경해 nullable을 제거하는 리팩토링을 권장합니다.app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt (1)
49-52: 경로 이동 함수 추가 적절 — 코멘트 스타일 사소한 정리탐색 함수는 적절합니다. 다만 주석에 공백 추가를 권장합니다.
-//사용자 찾기 화면으로 +// 사용자 찾기 화면으로 fun NavHostController.navigateToSearchPeople() { navigate(FeedRoutes.SearchPeople) }app/src/main/java/com/texthip/thip/MainScreen.kt (1)
17-19: MainScreen 공개 API 변경 확인 — 추가 호출부 없음rg 스캔 결과:
- 선언: app/src/main/java/com/texthip/thip/MainScreen.kt (lines 17–19)
- 호출: app/src/main/java/com/texthip/thip/MainActivity.kt (lines 47–49)
다른
.kt파일에서 MainScreen 호출부가 발견되지 않아, 현재로선 빌드나 런타임에서 누락된 콜백으로 인한 이슈는 없습니다.하위 호환이 필요하다면 기본값(no-op) 콜백을 두고 점진 이관할 수 있습니다. 이후 제거 TODO를 추가하세요.
- fun MainScreen( - onNavigateToLogin: () -> Unit - ) { + fun MainScreen( + onNavigateToLogin: () -> Unit = {} + ) {app/src/main/java/com/texthip/thip/MainActivity.kt (1)
47-56: 로그인 이동 시 백스택 초기화 처리 적절 — launchSingleTop 추가를 권장합니다popUpTo(graph.id, inclusive = true)로 메인 스택 초기화는 적절합니다. 동일 시점에 중복 트리거 가능성을 낮추기 위해 launchSingleTop를 함께 설정해 두면 더 안전합니다.
아래와 같이 보완해 주세요.
- navController.navigate(CommonRoutes.Login) { + navController.navigate(CommonRoutes.Login) { + launchSingleTop = true // 메인 화면으로 돌아올 수 없도록 모든 화면 기록 삭제 popUpTo(navController.graph.id) { inclusive = true } }app/src/main/res/drawable/ic_text_more.xml (1)
1-24: VectorDrawable 그라디언트 호환성 확인 + Compose 브러시 대체 제안aapt:attr를 통한 그라디언트(fillColor)는 VectorDrawableCompat에서 API 레벨별 지원이 다릅니다. minSdk 및 지원 라이브러리 버전에 따라 그라디언트가 무시되거나 단색으로 보일 수 있으니 실제 저사양 기기에서 확인해 주세요. 또한 완전 불투명(#FF121212) 구간이 있어 다크 테마/저명도 텍스트 위에서 가독성에 영향이 없는지 UX 확인을 권장합니다.
다음 항목을 확인해 주세요:
- minSdk 및 VectorDrawableCompat 버전에서 그라디언트가 의도대로 렌더링되는지 실기기 테스트
- 라이트/다크 테마 모두에서 오버레이 농도가 적절한지
정적 에셋 대신 Compose 측에서 페이드 오버레이를 그리면 해상도/비율/라인 높이에 더 잘 적응합니다. 예:
// 텍스트가 들어있는 Box의 end에 겹쳐 그라디언트를 깔아줍니다. Box( modifier = Modifier.fillMaxWidth(), ) { // ... Text(...) Box( modifier = Modifier .align(Alignment.CenterEnd) .width(80.dp) // 필요 시 줄 높이에 맞춰 계산 .fillMaxHeight() .background( brush = Brush.horizontalGradient( 0f to Color(0x00121212), 1f to Color(0xFF121212) ) ) ) }추가로, 이 리소스는 아이콘이라기보다 텍스트 페이드 오버레이 기능이므로 네이밍을 ic_* 대신 bg_/overlay_ 계열로 정리하면 검색/재사용 시 의도가 더 분명해집니다.
app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt (1)
63-89: Preview userId가 동일(1L) — 샘플 ID 분리 및 LazyColumn key 설정 권장미리보기 3개 항목의 userId가 모두 1L입니다. LazyColumn에서 key를 지정할 때 안정 키가 중복되면 항목 식별이 흔들릴 수 있어, 샘플 데이터라도 ID를 분리해 두는 편이 안전합니다.
프리뷰 데이터의 ID를 구분해 주세요.
- MySubscriptionData( - userId = 1L, + MySubscriptionData( + userId = 1L, ... - MySubscriptionData( - userId = 1L, + MySubscriptionData( + userId = 2L, ... - MySubscriptionData( - userId = 1L, + MySubscriptionData( + userId = 3L,또한 리스트 성능/정합성을 위해 key를 지정해 주세요(실데이터에서 userId가 nullable이라면 닉네임+index 등으로 안전하게 보정):
// 현재 itemsIndexed(peopleList) { index, user -> /* ... */ } // 제안 itemsIndexed( items = peopleList, key = { index, item -> item.userId ?: "${item.nickname}-$index" } ) { index, user -> // ... }onThipNumClick의 시그니처를 (MySubscriptionData) → Unit 대신 (userId: Long) → Unit으로 축소하면 호출부/테스트가 더 단순해질 수 있습니다. 필요 시 후속 리팩터로 검토해 주세요.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)
83-94: SavedStateHandle로 업데이트 전파: 키 상수화 및 타깃 백스택 선택을 권장합니다.
- 문자열 키를 하드코딩하면 발신/수신 양쪽에서 오타로 인한 버그가 생기기 쉽습니다. 공용 상수로 분리해 주세요.
- previousBackStackEntry에만 쓰면 내비 구조 변경(중간 단계 추가/삭제)에 취약합니다. 업데이트를 받아야 하는 특정 라우트의 BackStackEntry를 명시적으로 가져와 set하는 방식을 고려해 주세요.
- feedDetail 변경이 잦을 경우 동일 값 반복 set이 발생할 수 있습니다. 로컬 remember 캐시로 마지막으로 전파한 값과 비교 후 변경시에만 set하는 최적화를 권장합니다.
[추가 맥락] PR 설명대로 “커스텀 객체 직렬화 문제” 회피를 위해 primitive로 분해해 전달하는 접근은 타당합니다.다음처럼 키 상수화를 제안합니다. 필요하시면 공용 파일 생성까지 패치 드리겠습니다.
적용 diff(본 범위 내 문자열 키 → 상수 사용):
- handle.set("updated_feed_id", detail.feedId.toLong()) - handle.set("updated_feed_isLiked", detail.isLiked) - handle.set("updated_feed_likeCount", detail.likeCount) - handle.set("updated_feed_isSaved", detail.isSaved) + handle.set(FeedSavedStateKeys.UPDATED_FEED_ID, detail.feedId.toLong()) + handle.set(FeedSavedStateKeys.UPDATED_FEED_IS_LIKED, detail.isLiked) + handle.set(FeedSavedStateKeys.UPDATED_FEED_LIKE_COUNT, detail.likeCount) + handle.set(FeedSavedStateKeys.UPDATED_FEED_IS_SAVED, detail.isSaved)상수 정의(새 파일 또는 적절한 공용 위치):
package com.texthip.thip.ui.feed.navigation object FeedSavedStateKeys { const val UPDATED_FEED_ID = "updated_feed_id" const val UPDATED_FEED_IS_LIKED = "updated_feed_isLiked" const val UPDATED_FEED_LIKE_COUNT = "updated_feed_likeCount" const val UPDATED_FEED_IS_SAVED = "updated_feed_isSaved" const val DELETED_FEED_ID = "deleted_feed_id" }타깃 백스택 지정(개념 예시):
// 예: 피드 탭 루트로 결과 전파 val feedEntry = runCatching { navController.getBackStackEntry(com.texthip.thip.ui.navigator.routes.MainTabRoutes.Feed) }.getOrNull() feedEntry?.savedStateHandle?.set(FeedSavedStateKeys.UPDATED_FEED_ID, detail.feedId.toLong())또한 수신 측(예: FeedScreen)에서는 소비 후 remove로 정리하는 패턴을 권장합니다.
app/src/main/java/com/texthip/thip/ui/feed/usecase/ChangeFeedSaveUseCase.kt (1)
9-18: 파라미터 수 증가로 가독성이 떨어질 수 있습니다 — 의미 단위를 묶는 경량 컨텍스트 도입 고려.현재 (feedId, newSaveStatus, currentIsLiked, currentLikeCount) 4개의 위치 기반 인자는 호출부에서 순서 착오를 유발하기 쉽습니다. Params 객체를 도입하거나, 최소한 호출부에서 named argument 사용을 팀 컨벤션으로 강제하는 것을 권장합니다.
예시:
data class ChangeSaveParams( val feedId: Long, val newSaveStatus: Boolean, val currentIsLiked: Boolean, val currentLikeCount: Int ) suspend operator fun invoke(params: ChangeSaveParams) = feedRepository.changeFeedSave( params.feedId, params.newSaveStatus, params.currentIsLiked, params.currentLikeCount )또는 호출부에서:
changeFeedSaveUseCase( feedId = id, newSaveStatus = !isSaved, currentIsLiked = isLiked, currentLikeCount = likeCount )app/src/main/java/com/texthip/thip/ui/signin/screen/SignupDoneScreen.kt (2)
83-101: 프로필 이미지 로딩 조건 강화 및 플레이스홀더/에러 처리 권장.현재 null만 체크하고 있어 빈 문자열("")도 로딩을 시도합니다. isNullOrBlank로 보완하고, Coil의 placeholder/error를 지정해 UX를 개선하세요.
다음 diff를 적용해 주세요:
- if (profileImage != null) { + if (!profileImage.isNullOrBlank()) { Box( modifier = Modifier .size(54.dp) .clip(CircleShape) .border(width = 1.dp, color = colors.White, shape = CircleShape) .background(colors.Black), contentAlignment = Alignment.BottomCenter ) { - AsyncImage( - model = userInfo.profileImage, + AsyncImage( + model = profileImage, contentDescription = null, contentScale = ContentScale.Fit, modifier = Modifier .size(45.dp) .offset(y = 2.dp) + , + placeholder = painterResource(R.drawable.ic_profile_placeholder), + error = painterResource(R.drawable.ic_profile_placeholder) ) } } else {추가로 접근성 강화를 원하시면 contentDescription을 stringResource로 전달하는 것도 고려해 주세요.
143-150: Preview 데이터: 이미지 URL은 null로 두어 로딩 시도 방지.빈 문자열은 위 조건을 통과할 수 있습니다. Preview에서는 null로 두는 편이 안전합니다.
- userInfo = SignupUserInfo( + userInfo = SignupUserInfo( nickname = "JJUYAA", - profileImage = "", + profileImage = null, role = "칭호칭호" ),app/src/main/java/com/texthip/thip/ui/signin/screen/TutorialScreen.kt (1)
66-86: 마지막 페이지 CTA 텍스트/동작을 구분해 UX 개선.마지막 페이지에서도 버튼 라벨이 “다음”으로 고정됩니다. 마지막 페이지에서는 “시작하기” 등으로 라벨을 바꾸면 사용자가 플로우 종료를 더 명확히 인지할 수 있습니다.
- InputTopAppBar( - title = "", - rightButtonName = stringResource(R.string.next), + val isLast = pagerState.currentPage == tutorialPages.lastIndex + InputTopAppBar( + title = "", + rightButtonName = if (isLast) stringResource(R.string.start_thip) else stringResource(R.string.next), isLeftIconVisible = false, isRightButtonEnabled = true, onRightClick = { scope.launch { - if (pagerState.currentPage < tutorialPages.lastIndex) { + if (!isLast) { pagerState.animateScrollToPage(pagerState.currentPage + 1) } else { onFinish() } } } )app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)
55-55: FeedStateUpdateResult가 ui.feed.mock 패키지에 위치런타임에서 사용하는 교차 화면 상태 전달 모델이라면 mock 패키지보다는 ui.feed.model 또는 domain 계층으로 이동하는 편이 의미와 모듈성에 더 부합합니다.
app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (1)
52-52: 리스트 아이템 재바인딩 시 잘못된 오버레이 상태가 남을 수 있음 — remember 키 지정LazyColumn에서 재활용/재구성이 발생하면 onTextLayout 콜백 이전 프레임에서 isTextTruncated가 이전 아이템의 값을 잠시 유지할 수 있습니다. feedItem이나 maxLines 기준으로 상태를 초기화하도록 키를 부여해 주세요.
아래 diff를 적용하면 안전합니다:
- var isTextTruncated by remember { mutableStateOf(false) } + var isTextTruncated by remember(feedItem.id, maxLines, feedItem.content) { mutableStateOf(false) }app/src/main/java/com/texthip/thip/ui/feed/usecase/ChangeFeedLikeUseCase.kt (1)
9-17: ChangeFeedLikeUseCase 호출 검증 완료 — 모든 호출부가 4개 인자 사용 중기존 4개의 인자(특히 Boolean + Int 혼용)는 가독성을 떨어뜨리고 오입력 위험이 있습니다. 아래와 같이
LikeUpdateInput데이터 클래스를 도입해 파라미터 객체로 감싸는 리팩토링을 권장드립니다.대상 위치
- app/src/main/java/com/texthip/thip/ui/feed/usecase/ChangeFeedLikeUseCase.kt:
invoke시그니처- 호출부
- app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt:335
- app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedOthersViewModel.kt:116
- app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt:116
제안 예시:
data class LikeUpdateInput( val feedId: Long, val newLikeStatus: Boolean, val currentLikeCount: Int, val currentIsSaved: Boolean ) suspend operator fun invoke(input: LikeUpdateInput) = feedRepository.changeFeedLike( input.feedId, input.newLikeStatus, input.currentLikeCount, input.currentIsSaved )모든 호출부를
invoke(input)형태로 변경해 주세요.app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageViewModel.kt (1)
66-69: 로그아웃 처리 성공/실패 분기 및 에러 메시지 반영토큰 삭제가 실패할 경우(예: DataStore I/O) 사용자에게 피드백이 없어 디버깅이 어렵습니다. try/catch로 최소한의 실패 처리와 메시지 반영을 권장합니다.
아래 diff를 적용해 주세요:
- fun confirmLogout() { - viewModelScope.launch { - tokenManager.clearTokens() - _uiState.update { it.copy(showLogoutDialog = false, isLogoutCompleted = true) } - } - } + fun confirmLogout() { + viewModelScope.launch { + try { + tokenManager.clearTokens() + _uiState.update { it.copy(showLogoutDialog = false, isLogoutCompleted = true) } + } catch (e: Exception) { + _uiState.update { + it.copy( + showLogoutDialog = false, + isLogoutCompleted = false, + errorMessage = e.message ?: "알 수 없는 오류가 발생했습니다." + ) + } + } + } + }원하시면 일회성 이벤트(SharedFlow/Channel)로 로그아웃 완료 신호를 분리해 재실행 리스크도 줄일 수 있습니다.
app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt (4)
93-112: HttpException → 도메인 예외 변환 로직은 적절하나, 공통 유틸로 추출을 추천updateProfile에서만 에러 파싱을 수행하면 중복이 발생할 수 있습니다. 동일 패턴이 다른 API에도 필요할 가능성이 높으므로 공통화하면 유지보수성이 올라갑니다.
아래처럼 현재 블록을 얇게 유지하고, 파일 내 private 함수 또는 공용 유틸로 위임을 고려해보세요.
} catch (e: HttpException) { - val errorBody = e.response()?.errorBody()?.string() - if (errorBody != null) { - try { - val baseResponse = json.decodeFromString<BaseResponse<Unit>>(errorBody) - Result.failure(ThipApiFailureException(baseResponse.code, baseResponse.message)) - } catch (jsonException: Exception) { - Result.failure(e) // JSON 파싱 실패 시 원래 HttpException 반환 - } - } else { - Result.failure(e) // 에러 본문이 없으면 원래 HttpException 반환 - } + Result.failure(parseApiFailure(e)) } catch (e: Exception) { Result.failure(e) }그리고 파일 하단(선택) 또는 공용 모듈에 유틸을 추가:
// UserRepository.kt 내 private fun parseApiFailure(e: HttpException): Throwable { val errorBody = e.response()?.errorBody()?.string() return if (!errorBody.isNullOrBlank()) { try { val base = json.decodeFromString<BaseResponse<Unit>>(errorBody) ThipApiFailureException(base.code, base.message) } catch (_: Exception) { e } } else { e } }
87-92: 주석 처리된 이전 구현 제거구현이 바로 아래에 존재하므로 주석 블록은 혼란만 유발합니다. 삭제해 주세요.
-/* - suspend fun updateProfile(request: ProfileUpdateRequest): Result<Unit?> = runCatching { - userService.updateProfile(request) - .handleBaseResponse() - .getOrThrow() - }*/
99-109: 서버가 baseresponse 없이 400만 주는 케이스에 대한 메시지 보강 여지현재는 errorBody 부재/파싱 실패 시 HttpException 그대로 반환합니다. 사용자 메시지 일관성을 위해, 이 케이스를 도메인 예외(예: 알 수 없는 서버 오류 코드/문구)로 매핑하는 전략도 고려해볼 만합니다.
원하시면 공통 에러 매핑 규칙(HTTP 코드 → 사용자 메시지)을 제안해 드릴게요.
93-98: updateProfile 반환 타입 단순화 제안성공 시 항상 Unit을 반환하므로
Result<Unit?>대신Result<Unit>로 반환 타입을 명확히 할 수 있습니다. 호출부(MyPageEditViewModel.kt:116)에서도 반환값을 사용하지 않아 변경에 따른 영향이 없습니다.· 수정 위치
- app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt (93–98행)
· 제안하는 변경사항
- suspend fun updateProfile(request: ProfileUpdateRequest): Result<Unit?> { + suspend fun updateProfile(request: ProfileUpdateRequest): Result<Unit> {app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt (2)
183-188: 외부 URL 열기 시 Activity 미존재 예외 처리 권장일부 기기/환경에서 브라우저가 없으면 크래시가 발생할 수 있습니다. startActivity를 try/catch로 감싸거나 runCatching으로 안전하게 처리해 주세요. (아래 예시는 고객센터 한 곳에만 표시했지만, 동일 패턴을 나머지 메뉴에도 적용 권장)
- onClick = { - val intent = - Intent(Intent.ACTION_VIEW, URL_CUSTOMER_SERVICE.toUri()) - context.startActivity(intent) - } + onClick = { + val intent = Intent(Intent.ACTION_VIEW, URL_CUSTOMER_SERVICE.toUri()) + try { + context.startActivity(intent) + } catch (e: Exception) { + // TODO: 사용자 피드백 (토스트/스낵바) 또는 로그 + } + }Also applies to: 197-201, 211-214, 225-227, 238-240
51-52: navController 미사용해당 Composable에서 navController를 사용하지 않습니다. 필요 없다면 제거를 고려해 주세요. (호출부 영향 주의)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (2)
116-123: 낙관적 업데이트 롤백 시 외부 업데이트 충돌 가능성API 실패 시 원본으로 되돌리는데, 그 사이 feedStateUpdateResult로 최신 상태가 들어오면 되돌리기가 최신 상태를 덮어쓸 수 있습니다. 로컬 변경 트랜잭션 ID(또는 타임스탬프)를 비교하거나, 롤백 전에 최신 상태와 동등성 체크를 추가하는 방식을 고려하세요.
Also applies to: 137-144
47-48: feedId 타입 일관성 검토 및 통일 제안현재
FeedDetailResponse.feedId가Int로 정의되어 있어toLong()변환은 안전하지만, 다른 응답 모델(FeedLikeResponse,FeedSaveResponse,FeedUsersResponse.FeedList등)은Long을 사용하고 있어 혼란이 예상됩니다. 피드 ID 타입을 전사적으로 일관되게 유지하는 것을 권장드립니다.
- app/src/main/java/com/texthip/thip/data/model/feed/response/FeedDetailResponse.kt (8행):
val feedId: Int→val feedId: Long변경 검토- MyFeedItem(AllFeedResponse, CreateFeedResponse, RelatedFeedItem 등 Int 사용 모델)도 동일하게
Long전환 여부 검토- 또는
update.feedId를Int로 변경해 비교 로직을 통일하는 방안 고려런타임에서
toLong()예외는 발생하지 않으나, 타입 불일치로 인한 오해·버그 방지를 위해 한 가지 타입으로 통일할 것을 권장드립니다.app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (3)
50-63: 팔로우 토스트 메시지 구성 및 자동 숨김 트리거 연결 좋습니다UI에서 메시지를 구성하고 ViewModel에 전달하는 흐름이 명확합니다. 필요시 메시지 구성을 ViewModel로 올려서 화면을 더 ‘덤’하게 만들 수도 있습니다.
76-81: 토스트 자동 숨김 처리 적절하나, 메시지 단위 키를 고려해보세요showToast만 키로 쓰면 연속 토스트에서 타이밍이 겹칠 수 있습니다.
LaunchedEffect(uiState.toastMessage, uiState.showToast)로 바꾸면 연속 메시지에 더 견고합니다.- LaunchedEffect(uiState.showToast) { + LaunchedEffect(uiState.toastMessage, uiState.showToast) { if (uiState.showToast) { delay(2000) onHideToast() } }Also applies to: 177-190
160-164: 피드 상세 이동 TODO 해소 제안피드 항목 클릭 시 상세 화면으로 이동을 연결하면 사용자 플로우가 완성됩니다. 네비게이션 라우트/파라미터를 알려주시면 코드까지 제안드릴 수 있습니다.
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (2)
335-338: 가독성을 위해 named argument 사용 권장ChangeFeedLikeUseCase가 파라미터 확장되었으니, 호출부도 named args로 유지보수성을 높이는 게 좋습니다.
- changeFeedLikeUseCase( - feedId, newLikeStatus, feedToUpdate.likeCount, - feedToUpdate.isSaved - ) + changeFeedLikeUseCase( + feedId = feedId, + newLikeStatus = newLikeStatus, + currentLikeCount = feedToUpdate.likeCount, + currentIsSaved = feedToUpdate.isSaved + )
315-343: 낙관적 업데이트 롤백 시 외부 업데이트와의 충돌 가능성FeedDetailViewModel 코멘트와 동일한 이슈가 있습니다. 롤백 전에 현재 아이템 상태가 “롤백 타겟”과 여전히 일치하는지 검사하는 방식을 고려해 주세요.
Also applies to: 345-370
app/src/main/java/com/texthip/thip/ui/feed/component/MyFeedCard.kt (4)
97-126: 텍스트 잘림(…더보기) 처리 접근은 적절합니다. onTextLayout에 값 변경 가드를 추가해 불필요한 재구성을 줄이세요.현재 onTextLayout에서 매 프레임 상태를 설정할 수 있어, 동일 값으로 set되는 경우에도 오버헤드가 발생할 수 있습니다. 값이 바뀔 때만 상태를 갱신하도록 가드를 두면 미세 최적화가 됩니다.
아래와 같이 변경을 제안합니다:
- onTextLayout = { textLayoutResult -> - isTextTruncated = textLayoutResult.hasVisualOverflow - } + onTextLayout = { textLayoutResult -> + val overflow = textLayoutResult.hasVisualOverflow + if (isTextTruncated != overflow) { + isTextTruncated = overflow + } + }
116-125: “…더보기” 오버레이 크기 지정 방식 단순화width/height 대신 size를 사용하면 가독성이 좋아집니다. 기능 동일.
- .width(80.dp) - .height(24.dp) + .size(width = 80.dp, height = 24.dp)
61-96: 대규모 주석 처리 블록은 제거를 권장합니다.과거 레이아웃이 주석으로 남아 있어 유지보수에 혼란을 줄 수 있습니다. Git 이력이 있으므로 과감히 삭제하는 편이 좋습니다.
- /*Column( - modifier = Modifier - .clickable { onContentClick() }, - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = feedItem.content, - style = typography.feedcopy_r400_s14_h20, - color = colors.White, - maxLines = maxLines, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp) - ) - - if (hasImages) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - feedItem.imageUrls.take(3).forEach { imageUrl -> - AsyncImage( - model = imageUrl, - contentDescription = null, - modifier = Modifier - .padding(end = 10.dp) - .size(100.dp), - contentScale = ContentScale.Crop - ) - } - } - } - }*/
127-145: 이미지 리스트 렌더링 로직의 중복 제거를 고려하세요.OthersFeedCard 등과 거의 동일한 “최대 3장 썸네일” 렌더링 패턴입니다. 공용 컴포저블(예: FeedImagesRow(imageUrls: List, modifier: Modifier))로 추출하면 재사용성과 유지보수성이 향상됩니다.
원하시면 공용 컴포저블 초안 코드를 제공하겠습니다.
app/src/main/java/com/texthip/thip/ui/feed/component/OthersFeedCard.kt (2)
60-87: 텍스트 잘림(…더보기) 처리 접근은 적절합니다. onTextLayout에 값 변경 가드를 추가해 불필요한 재구성을 줄이세요.- onTextLayout = { textLayoutResult -> - isTextTruncated = textLayoutResult.hasVisualOverflow - } + onTextLayout = { textLayoutResult -> + val overflow = textLayoutResult.hasVisualOverflow + if (isTextTruncated != overflow) { + isTextTruncated = overflow + } + }
81-85: “…더보기” 오버레이 크기 지정 방식 단순화width/height 대신 size 사용을 권장합니다.
- .width(80.dp) - .height(24.dp) + .size(width = 80.dp, height = 24.dp)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/SearchPeopleViewModel.kt (4)
43-57: "USER"와 같은 매직 스트링은 enum/const로 치환을 권장오타 및 변경에 취약합니다. 서버 타입 파라미터가 제한된 도메인이라면 enum class 혹은 상수로 관리하세요.
67-67: ID의 Int 강제 캐스팅은 잠재적 오버플로/축소 위험
searchId: Long을toInt()로 변환하고 있습니다. API가 Long을 지원한다면 시그니처 확장(Long 사용) 또는 안전한 범위 체크를 권장합니다.
68-71: 삭제 실패 시 에러 메시지 설정 추가 권장UI 복구뿐만 아니라 오류 원인을 노출하는 편이 디버깅과 UX에 유리합니다.
- .onFailure { - // 실패 시 UI를 원래대로 복구 - _uiState.update { it.copy(recentSearches = currentSearches) } - } + .onFailure { e -> + // 실패 시 UI를 원래대로 복구하고 에러 메시지 반영 + _uiState.update { + it.copy( + recentSearches = currentSearches, + errorMessage = e.message + ) + } + }
124-131: 최근 검색어 추가가 클라이언트 로컬 상태에만 반영됩니다. 서버 반영 의도 확인 필요요구사항에 서버 저장이 포함된다면, 추가 시점에 저장 API 호출(+ 낙관적 업데이트)도 고려하세요. 현재는 fetchRecentSearches 호출 시 서버 데이터로 다시 덮일 수 있습니다.
원하시면 낙관적 업데이트 + 서버 연동 흐름 샘플을 제안드리겠습니다.
app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt (2)
20-20: 데이터 레이어에서 UI 패키지 타입에 의존 중입니다. 계층 간 의존성 역전 이슈
com.texthip.thip.ui.feed.mock.FeedStateUpdateResult는 UI 패키지에 있어, Repository(데이터 레이어)가 UI에 의존하는 구조입니다. 결과 모델을 data/domain 계층으로 이동하거나, 도메인 이벤트 타입을 별도 모듈에 정의하는 것을 권장합니다.
23-26: SharedFlow 버퍼 오버플로 정책 import 추가 필요아래 제안과 함께 BufferOverflow가 필요합니다.
import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlowapp/src/main/java/com/texthip/thip/ui/navigator/navigations/AuthNavigation.kt (2)
65-72: 튜토리얼 종료 시 popUpTo 자기자신(inclusive) 사용: 기능상 문제 없음, 다만 가독성 개선 권장동작은 맞습니다(현재 화면만 스택에서 제거). 다만 중괄호와 개행이 섞여 가독성이 떨어집니다. 아래처럼 정리하면 읽기 수월합니다.
- TutorialScreen( - onFinish = { - navController.navigate(CommonRoutes.SignupScreenRoutes.Done) { - popUpTo<CommonRoutes.SignupScreenRoutes.Tutorial> { inclusive = true } - } } - ) + TutorialScreen( + onFinish = { + navController.navigate(CommonRoutes.SignupScreenRoutes.Done) { + popUpTo<CommonRoutes.SignupScreenRoutes.Tutorial> { inclusive = true } + } + } + )
73-94: 모델 위치 조정 권장 & navigateToMainAfterSignup 검증 완료
- SignupUserInfo가 현재
mock패키지에 있어 실서비스 코드 가독성에 혼동을 줄 수 있습니다.
→ui.signup.model등 실제 사용 영역으로 이동을 권장드립니다.navigateToMainAfterSignup()에서를 사용해 인증 플로우의 모든 백스택 엔트리를 정상적으로 제거하는 것을 확인했습니다.popUpTo(CommonRoutes.Login) { inclusive = true }app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedOthersViewModel.kt (5)
96-125: 좋아요 낙관적 업데이트 롤백이 스냅샷 전체를 되돌림 — 부분 롤백으로 플리커/경합 최소화 권장실패 시 currentFeeds로 전체 리스트를 덮어쓰면, observeFeedUpdates 등 동시 업데이트를 덮어쓸 수 있습니다. 대상 피드만 롤백하세요.
- ).onFailure { - _uiState.update { it.copy(feeds = currentFeeds) } - } + ).onFailure { + _uiState.update { state -> + state.copy( + feeds = state.feeds.map { f -> + if (f.feedId == feedId) feedToUpdate else f + } + ) + } + }
126-152: 저장(북마크) 낙관적 업데이트도 동일한 롤백 전략 적용 권장실패 시 전체 스냅샷 덮어쓰기 대신 해당 피드만 복구하면 동시성에 더 안전합니다.
- ).onFailure { - _uiState.update { it.copy(feeds = currentFeeds) } - } + ).onFailure { + _uiState.update { state -> + state.copy( + feeds = state.feeds.map { f -> + if (f.feedId == feedId) feedToUpdate else f + } + ) + } + }
153-174: 팔로우 토글 실패 시 사용자 피드백 보강 권장실패 시 userInfo만 롤백되고 토스트 메시지는 성공/변경 기준으로만 표시됩니다. 네트워크/서버 오류 시 별도 에러 토스트를 노출하거나 showToast를 재설정하는 로직을 추가하면 UX가 좋아집니다.
원하시면 실패 케이스용 메시지 상태를 추가하고, 중복 토스트 노출 방지를 위한 debounce/consume 로직도 함께 제안드리겠습니다.
80-82: 개발용 로그는 BuildConfig.DEBUG 가드 권장출시 빌드에서 불필요한 로그 노출을 피하기 위해 DEBUG 가드나 로깅 추상화를 고려해 주세요.
- Log.d("FeedOthersViewModel", "User Info Result: ${userInfoResult.getOrNull()}") - Log.d("FeedOthersViewModel", "Fetched Feeds Count: ${fetchedFeeds.size}") + if (BuildConfig.DEBUG) { + Log.d("FeedOthersViewModel", "User Info Result: ${userInfoResult.getOrNull()}") + Log.d("FeedOthersViewModel", "Fetched Feeds Count: ${fetchedFeeds.size}") + }(필요 시 BuildConfig 임포트 추가)
4-4: ViewModel에서 LocalContext 임포트 불필요사용되지 않고, ViewModel 레이어에서는 Compose Local* 참조를 지양합니다. 삭제 권장.
-import androidx.compose.ui.platform.LocalContextapp/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt (1)
158-170: 프리뷰 데이터 최신 시그니처 반영 좋음
- RecentSearchUiItem, MySubscriptionData(id: Long) 시그니처가 잘 반영되어 있습니다. onNavigateBack/onUserClick 더미 콜백도 추가되어 미리보기 컴파일에 유리합니다.
프리뷰에서도 onUserClick에 전달되는 id 필드 사용이 실제 본문(onUserClick 매핑)의 수정 내용과 일치하는지 한 번 더 점검해 주세요.
Also applies to: 176-193, 201-217, 226-237
📜 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 (6)
app/src/main/res/drawable/img_tutorial_1.pngis excluded by!**/*.pngapp/src/main/res/drawable/img_tutorial_2.pngis excluded by!**/*.pngapp/src/main/res/drawable/img_tutorial_3.pngis excluded by!**/*.pngapp/src/main/res/drawable/img_tutorial_4.pngis excluded by!**/*.pngapp/src/main/res/drawable/img_tutorial_5.pngis excluded by!**/*.pngapp/src/main/res/drawable/img_tutorial_6.pngis excluded by!**/*.png
📒 Files selected for processing (41)
app/google-services.json(1 hunks)app/src/main/java/com/texthip/thip/MainActivity.kt(1 hunks)app/src/main/java/com/texthip/thip/MainScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt(4 hunks)app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/feed/component/ImageViewerModal.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/component/MyFeedCard.kt(7 hunks)app/src/main/java/com/texthip/thip/ui/feed/component/OthersFeedCard.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/feed/component/PeopleRecentSearch.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/feed/mock/FeedStateUpdateResult.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/feed/mock/MySubscriptionData.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt(6 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt(11 hunks)app/src/main/java/com/texthip/thip/ui/feed/usecase/ChangeFeedLikeUseCase.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/feed/usecase/ChangeFeedSaveUseCase.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedOthersViewModel.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/SearchPeopleViewModel.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageEditScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageViewModel.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/navigator/extensions/AuthNavigationExtensions.kt(1 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/AuthNavigation.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/signupNavigation.kt(0 hunks)app/src/main/java/com/texthip/thip/ui/navigator/routes/CommonRoutes.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/signin/mock/SignupUserInfo.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/signin/screen/SignupDoneScreen.kt(6 hunks)app/src/main/java/com/texthip/thip/ui/signin/screen/TutorialScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt(0 hunks)app/src/main/res/drawable/ic_text_more.xml(1 hunks)app/src/main/res/values/strings.xml(2 hunks)
💤 Files with no reviewable changes (2)
- app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt
- app/src/main/java/com/texthip/thip/ui/navigator/navigations/signupNavigation.kt
🧰 Additional context used
🧬 Code Graph Analysis (14)
app/src/main/java/com/texthip/thip/MainScreen.kt (1)
app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt (1)
MainNavHost(14-41)
app/src/main/java/com/texthip/thip/MainActivity.kt (1)
app/src/main/java/com/texthip/thip/MainScreen.kt (1)
MainScreen(16-39)
app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt (1)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt (1)
myPageNavigation(22-63)
app/src/main/java/com/texthip/thip/ui/feed/component/MyFeedCard.kt (1)
app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
ThipTheme(41-69)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt (1)
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt (1)
MyPageScreen(49-80)
app/src/main/java/com/texthip/thip/ui/signin/screen/TutorialScreen.kt (2)
app/src/main/java/com/texthip/thip/ui/common/topappbar/InputTopAppBar.kt (1)
InputTopAppBar(25-69)app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
ThipTheme(41-69)
app/src/main/java/com/texthip/thip/ui/feed/component/PeopleRecentSearch.kt (1)
app/src/main/java/com/texthip/thip/ui/common/buttons/GenreChipButton.kt (1)
GenreChipButton(29-74)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (1)
app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt (1)
SearchPeopleScreen(37-65)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedOthersViewModel.kt (1)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (1)
observeFeedUpdates(43-60)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (1)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedOthersViewModel.kt (1)
observeFeedUpdates(48-65)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/AuthNavigation.kt (2)
app/src/main/java/com/texthip/thip/ui/signin/screen/TutorialScreen.kt (1)
TutorialScreen(59-118)app/src/main/java/com/texthip/thip/ui/signin/screen/SignupDoneScreen.kt (1)
SignupDoneScreen(37-138)
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/feed/component/OthersFeedCard.kt (1)
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/SearchPeopleScreen.kt (2)
app/src/main/java/com/texthip/thip/ui/common/topappbar/DefaultTopAppBar.kt (1)
DefaultTopAppBar(22-69)app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt (1)
SearchPeopleResult(21-50)
🔇 Additional comments (46)
app/google-services.json (1)
16-23: 새 Android OAuth 클라이언트 추가 — keystore(SHA‑1) 매칭과 로그인 흐름 확인 필요패키지명은 동일하고 SHA‑1이 다른 Android OAuth 클라이언트가 추가되었습니다. 보통 debug/release(또는 CI 서명) 분리 용도입니다. 아래를 확인해주세요:
- 이 SHA‑1(1f2681ba...)가 실제 배포/개발에 사용하는 keystore와 일치하는지.
- Google Sign-In/One Tap에서 사용하는 default_web_client_id(strings.xml Line 447, 웹 클라이언트 타입 3: ck9g1v0q...)와의 조합이 올바른지. 서버 auth code 교환이 있다면 web client id가 맞아야 합니다.
- Cloud Console에서 API 키 제한(앱 패키지+SHA‑1) 및 OAuth 동의 화면 설정이 환경별로 일치하는지.
필요 시, Firebase 콘솔의 Android 앱 등록에 해당 SHA‑1을 모두 추가하는 것을 잊지 마세요.
app/src/main/res/values/strings.xml (2)
302-308: 튜토리얼 문자열 추가 LGTM구현 의도(6단계 온보딩)와 문구 톤이 일관되고 줄바꿈 처리도 적절합니다. UI 적용에 문제 없어 보입니다.
325-325: "건너뛰기" 추가 LGTM튜토리얼/온보딩에서 접근성 측면에서도 필수 텍스트입니다. 문제 없습니다.
app/src/main/java/com/texthip/thip/ui/navigator/routes/CommonRoutes.kt (1)
17-18: SignupFlow 도입 방향 좋습니다회원가입 플로우의 상위 그룹 라우트로 묶은 점 적절합니다. 네이밍과 계층 구조 모두 직관적입니다.
app/src/main/java/com/texthip/thip/ui/navigator/extensions/AuthNavigationExtensions.kt (1)
26-26: navigate(CommonRoutes.SignupScreenRoutes.Genre) 타입 호환성 재확인 필요CommonRoutes.SignupScreenRoutes가 CommonRoutes/Routes 계층에 속하지 않으면, 현재 navigate 확장(타입 안전 네비게이션)의 제네릭 제약을 충족하지 못해 컴파일 오류가 납니다. 앞서 제안한 대로 SignupScreenRoutes를 sealed class로 변경하면 이 호출은 정상 컴파일될 것입니다.
참고: 변경 후에도 NavGraph에 SignupFlow 하위로 Genre 목적지가 선언되어 있어야 합니다. startDestination은 Nickname으로, 내부 이동은 Nickname → Genre → Tutorial → Done 순으로 확인 바랍니다.
app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt (1)
28-29: LGTM — 새로운 SearchPeople 경로 추가 적절탭/피드 내 사용자 찾기 진입점이 명확해졌습니다. 인자 없는 data object 선택도 합리적입니다.
app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt (1)
47-47: 구문 오류 수정 확인닫는 중괄호 추가로 컴파일 이슈가 해소됩니다.
app/src/main/java/com/texthip/thip/MainScreen.kt (1)
33-36: MainNavHost로 콜백 전달 OK — 내비게이션 위임이 일관적입니다MainScreen → MainNavHost → myPageNavigation → MyPageScreen으로 onNavigateToLogin 경로가 잘 연결되었습니다. 로그아웃 시 상위 그래프에서 처리하도록 위임한 설계가 명확합니다.
app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt (2)
33-35: MyPageScreen으로 콜백 전달 OK — 로그아웃 완료 시 상위 그래프 위임 흐름이 선명합니다MyPageScreen의 LaunchedEffect(uiState.isLogoutCompleted)와 자연스럽게 결합되어, 토큰 삭제/로그아웃 완료 → 로그인 화면 이동 플로우가 깔끔합니다.
22-25: myPageNavigation 호출부 검증 완료 — 유일 호출(MainNavHost.kt)만 존재
시그니처 변경에 따라 onNavigateToLogin 콜백이 NavGraphBuilder 레벨에서 주입되도록 업데이트되었으며, 다음 한 곳에서만 호출되고 있습니다. 추가 수정이나 점검은 필요 없습니다.• 선언:
– app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt (22–25)
• 호출:
– app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt (32–35)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)
57-57: 해당 import는 이 파일에서 사용되지 않습니다 — 코멘트 스킵합니다.빌드/린트 파이프라인에서 경고가 없다면 굳이 손대지 않아도 됩니다. 다른 파일들에서의 사용 여부와 정리 타이밍은 팀 컨벤션에 맞춰 판단해 주세요.
app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt (2)
32-35: popUpTo로 백스택 정리 확인됨onNavigateToLogin 구현부에서
navController.navigate(CommonRoutes.Login) { popUpTo(navController.graph.id) { inclusive = true } }를 사용해 메인 스택을 모두 삭제하고 있어 뒤로가기 시 메인으로 회귀되지 않습니다. LGTM.
- app/src/main/java/com/texthip/thip/MainActivity.kt (라인 48–54)
15-18: MainNavHost 호출부 모두 업데이트됨 — 기본값 제공 불필요
app/src/main/java/com/texthip/thip/MainScreen.kt(31–35)를 확인한 결과, 모든 호출부에서 이미 onNavigateToLogin 파라미터를 명시적으로 전달하고 있어, 시그니처 변경 시 기본값 없이도 컴파일 에러가 발생하지 않습니다.app/src/main/java/com/texthip/thip/ui/signin/screen/TutorialScreen.kt (1)
149-165: 페이지 인디케이터 구현 깔끔합니다.간결하고 가독성 좋습니다. 추후 접근성 고려가 필요하면 현재 페이지에 contentDescription을 부여하는 정도만 보완하면 충분해 보입니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (2)
76-76: FeedScreen에 검색 화면 진입 콜백 추가 — API 표면 확장 적절탑바의 좌측 아이콘과 내비게이션이 자연스럽게 연결되도록 public 파라미터를 추가한 방향이 합리적입니다.
195-195: 탑바 좌측 아이콘 클릭 시 사람찾기 화면으로 이동 — 매핑 OKUI/내비게이션 연결이 명확하며, 기존 no-op 대비 사용성 향상이 큽니다.
app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (3)
86-105: 텍스트 말줄임 오버레이 추가 — UX 향상 확인말줄임 여부 감지 + 아이콘 오버레이 접근 방식이 명확합니다. 아이콘은 장식용 요소이므로 contentDescription=null 처리도 적절합니다.
82-82: 전체 영역 클릭 유지 주석 좋음 + 터치 영역 보장 OKColumn 상위에 clickable을 배치한 결정은 사용성 측면에서 합리적입니다. 오버레이 이미지는 클릭 가능한 요소가 아니어서 이벤트를 가로채지 않습니다.
111-112: 이미지 그리드 여백 조정 — 일관성 OKvertical padding으로 통일해 상하 간격이 자연스럽습니다.
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (2)
51-53: FeedScreen.onNavigateToSearchPeople 연결 — 내비게이션 배선 적절탑바 좌측 액션과 자연스럽게 연동되어 UX 흐름이 매끄럽습니다.
147-154: SearchPeople 경로 추가 — 진입/뒤로가기/프로필 이동 흐름 정합뒤로가기는 상위에서 주입한 navigateBack으로 일관성 유지, 사용자 클릭은 UserProfile로 라우팅되어 기대 동작과 일치합니다.
app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageViewModel.kt (1)
21-23: isLogoutCompleted 플래그 도입 — 내비게이션 트리거로 적절UI와의 계약이 명확하며, MyPageScreen에서 LaunchedEffect로 one-shot 네비게이션을 수행하기에 충분합니다.
app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt (2)
27-31: Json 주입 추가는 타당하며 에러 파싱 전략과 일관됨DI로 Json을 주입받는 방향 좋습니다. 서버 에러 바디를 역직렬화해 도메인 예외로 변환하는 패턴과 잘 맞습니다.
3-8: Json DI 제공 모듈 존재 확인됨
app/src/main/java/com/texthip/thip/data/di/NetworkModule.kt (24–28행)에서provideJson()으로Json인스턴스를 Hilt로 제공하고 있으므로 DI 주입 실패 이슈가 없습니다.app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt (2)
64-68: 로그아웃 완료 → 로그인 네비게이션 연동 적절상태 기반 네비게이션으로 중복 클릭 없이 안전하게 전환됩니다. ViewModel에서 플래그를 적절히 리셋하면 재진입도 안전합니다.
53-59: 문제 없음: 고객센터 콜백 및 로그인 네비게이션 시그니처 정상 작동 확인고객센터 메뉴의 onCustomerService 콜백이
MyPageScreen→MyPageContent로 올바르게 전달되어 사용되고 있으며,
onNavigateToLogin파라미터도MyPageNavigation.kt에서 모든 호출부에 정상 반영된 것을 확인했습니다.
별도의 수정이 필요 없습니다.app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (1)
36-39: 상세 화면의 실시간 동기화 플로우 적절feedStateUpdateResult를 구독하여 현재 상세 피드에만 반영하는 방식이 과도한 리컴포지션을 방지하면서 일관된 상태를 유지합니다.
Also applies to: 43-60
app/src/main/java/com/texthip/thip/ui/feed/component/MyFeedCard.kt (2)
149-151: 하단 액션 Row 패딩 추가 변경 LGTM레이아웃 여유가 생겨 시각적으로 안정적입니다.
227-236: 프리뷰에서 ThipTheme 래핑 및 샘플 데이터 2건 배치 LGTM리그레션 확인과 다크 배경 대비 확인에 유용합니다.
app/src/main/java/com/texthip/thip/ui/feed/component/OthersFeedCard.kt (2)
109-118: ActionBarButton 파라미터 매핑 LGTM
- 좋아요: onLikeClick
- 댓글: onContentClick
- 북마크: onBookmarkClick
- 저장 가시성 및 상태 바인딩도 정상입니다.
135-144: 프리뷰에서 ThipTheme 적용 및 두 가지 카드 배치 LGTM텍스트 트렁케이션과 이미지 영역 동작 확인에 적합합니다.
app/src/main/java/com/texthip/thip/ui/feed/component/PeopleRecentSearch.kt (2)
54-59: UI와 ViewModel의 “최신 5개 유지” 정책 일관성 LGTMUI는 take(5), ViewModel은 distinct + take(5)로 맞춰져 있어 예측 가능한 동작입니다.
75-83: 프리뷰에서 id 기반 삭제 시뮬레이션 정상 동작 LGTM상태 변경 및 재구성이 간결하게 표현되어 있습니다.
Also applies to: 88-94
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/SearchPeopleViewModel.kt (1)
34-36: RecentSearchRepository DI 바인딩 확인 완료
RecentSearchRepository는 @singleton + @Inject 생성자 덕분에 Hilt에서 자동으로 제공되므로, 별도의 @Module/@Provides/@BINDS 설정이 필요 없습니다.app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt (2)
246-271: 좋아요 카운트 계산과 이벤트 emit 방식을 보강하세요.
- API 응답의 isLiked와 파라미터 newLikeStatus가 어긋나는 경우를 방지하기 위해 delta 계산은 요청 의도를 따르는 편이 안전합니다.
- 음수 카운트를 방지하세요.
- emit는 tryEmit으로 비차단 처리 권장(상단 SharedFlow 설정과 세트).
[Suggest_essential_refactor]- val newLikeCount = if (it.isLiked) currentLikeCount + 1 else currentLikeCount - 1 + // 요청 의도(newLikeStatus)를 기준으로 delta 계산, 음수 방지 + val newLikeCount = (if (newLikeStatus) currentLikeCount + 1 else currentLikeCount - 1) + .coerceAtLeast(0) val update = FeedStateUpdateResult( feedId = feedId, - isLiked = it.isLiked, + // 최종 상태는 서버 응답에 맞추되, + // 카운트 계산은 요청 의도로 처리해 UI 일관성을 높임 + isLiked = it.isLiked, likeCount = newLikeCount, isSaved = currentIsSaved // isSaved 상태는 그대로 유지 ) - _feedStateUpdateResult.emit(update) + _feedStateUpdateResult.tryEmit(update)
208-219: 임시 파일 정리 유틸리티 도입 LGTMIO 예외 방지 및 누수 방지에 유익합니다.
app/src/main/java/com/texthip/thip/ui/navigator/navigations/AuthNavigation.kt (3)
3-7: 라이프사이클 친화적 상태 수집 및 신규 화면/모델 연결 LGTMcollectAsStateWithLifecycle 도입과 Tutorial/SignupDone 화면, SignupUserInfo 연결이 자연스럽고 의존성도 적절히 정리되어 있습니다.
Also applies to: 15-22
36-52: 회원가입 플로우 그래프 구성과 ViewModel 스코핑 적절함
- startDestination을 Nickname으로 변경한 점과, parentEntry로 SignupViewModel을 공유하는 패턴이 올바르게 적용되었습니다.
52-64: 장르 → 튜토리얼 전환 흐름 적절함onSignupSuccess에서 튜토리얼로의 네비게이션이 명확합니다. 추가로, 뒤로가기를 통해 Genre로 돌아갈 수 있게 유지하고 싶다면 현재 설계가 적합합니다.
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedOthersViewModel.kt (2)
26-29: UI 상태에 토스트 필드 추가 적절일시적 피드백을 위한 showToast/toastMessage 추가는 화면 처리에 유용합니다.
35-37: 의존성 확장(Likes/Save/UserRepository) 적절UseCase와 Repository 주입 구성이 역할에 맞게 확장되었습니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt (5)
39-42: 화면 시그니처 확장(Lifted navigation callbacks) 적절뒤로가기/유저 클릭 콜백을 화면 외부로 승격시켜 네비게이션 의존성을 낮춘 점 좋습니다.
46-48: 최근 검색어 초기 로딩 타이밍: LaunchedEffect(Unit) 적용 적절초기 합성 시 한 번만 호출되어 과도한 재요청을 막습니다. ViewModel init로 옮기는 옵션도 있으나, 현재도 충분히 명확합니다.
70-76: 컨텐츠 컴포저블 시그니처 변경 타당
- onNavigateBack 분리와 onRecentSearchRemove의 (Long) 타입 변경이 상위 뷰모델 API와 일관됩니다.
82-84: TopAppBar 뒤로가기 콜백 연결 적절DefaultTopAppBar의 onLeftClick에 외부 콜백을 직접 전달한 점 좋습니다.
123-135: 검색 결과 리스트 콜백 연결 일관성 확보
- 타이핑 중/검색 완료 케이스 모두 동일한 onUserClick 콜백을 전달하는 방식으로 일관성 유지된 점 좋습니다. 상단 userId/id 필드만 정정되면 문제 없습니다.
user 식별자 필드 정정 후, 두 분기 모두 동일하게 작동하는지 UI 테스트로 확인 부탁드립니다.
| private val _feedStateUpdateResult = MutableSharedFlow<FeedStateUpdateResult>() | ||
| val feedStateUpdateResult: Flow<FeedStateUpdateResult> = _feedStateUpdateResult.asSharedFlow() | ||
|
|
There was a problem hiding this comment.
SharedFlow 설정 기본값으로 인해 emit가 서스펜딩되어 API 호출이 지연/교착될 수 있음
MutableSharedFlow 기본값(replay=0, buffer=0)은 수집자가 없을 때 emit가 suspend됩니다. Repository의 API 호출 흐름이 수집자 존재에 종속되면 위험합니다. 버퍼를 주고 tryEmit을 사용해 비차단으로 바꾸세요.
-private val _feedStateUpdateResult = MutableSharedFlow<FeedStateUpdateResult>()
-val feedStateUpdateResult: Flow<FeedStateUpdateResult> = _feedStateUpdateResult.asSharedFlow()
+private val _feedStateUpdateResult = MutableSharedFlow<FeedStateUpdateResult>(
+ replay = 0,
+ extraBufferCapacity = 64,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+)
+val feedStateUpdateResult: Flow<FeedStateUpdateResult> = _feedStateUpdateResult.asSharedFlow()📝 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 _feedStateUpdateResult = MutableSharedFlow<FeedStateUpdateResult>() | |
| val feedStateUpdateResult: Flow<FeedStateUpdateResult> = _feedStateUpdateResult.asSharedFlow() | |
| private val _feedStateUpdateResult = MutableSharedFlow<FeedStateUpdateResult>( | |
| replay = 0, | |
| extraBufferCapacity = 64, | |
| onBufferOverflow = BufferOverflow.DROP_OLDEST | |
| ) | |
| val feedStateUpdateResult: Flow<FeedStateUpdateResult> = _feedStateUpdateResult.asSharedFlow() |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt around
lines 43-45, the MutableSharedFlow is created with default replay=0 and buffer=0
which makes emit suspend when no collectors are present; change the
MutableSharedFlow creation to provide a non-zero buffer (e.g.,
extraBufferCapacity = 1 or more and set onBufferOverflow to DROP_OLDEST or
DROP_LATEST as appropriate) and replace suspend emit calls with
_feedStateUpdateResult.tryEmit(...) so repository API calls are non-blocking;
keep exposing it as a SharedFlow via asSharedFlow() and ensure downstream
callers handle possible dropped events if you choose a drop overflow policy.
| suspend fun changeFeedSave( | ||
| feedId: Long, newSaveStatus: Boolean, currentIsLiked: Boolean, | ||
| currentLikeCount: Int | ||
| ): Result<FeedSaveResponse?> = | ||
| runCatching { | ||
| val request = FeedSaveRequest(type = newSaveStatus) | ||
| feedService.changeFeedSave(feedId, request) | ||
| .handleBaseResponse() | ||
| .getOrThrow() | ||
| }.onSuccess { response -> | ||
| response?.let { | ||
| // API 응답(isSaved)과 파라미터로 받은 값들을 조합 | ||
| val update = FeedStateUpdateResult( | ||
| feedId = feedId, | ||
| isLiked = currentIsLiked, // isLiked 상태는 그대로 유지 | ||
| likeCount = currentLikeCount, | ||
| isSaved = it.isSaved | ||
| ) | ||
| _feedStateUpdateResult.emit(update) | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
저장 상태 이벤트 emit도 비차단으로 변경
SharedFlow 설정과 일관되게 tryEmit 사용을 권장합니다.
- _feedStateUpdateResult.emit(update)
+ _feedStateUpdateResult.tryEmit(update)📝 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.
| suspend fun changeFeedSave( | |
| feedId: Long, newSaveStatus: Boolean, currentIsLiked: Boolean, | |
| currentLikeCount: Int | |
| ): Result<FeedSaveResponse?> = | |
| runCatching { | |
| val request = FeedSaveRequest(type = newSaveStatus) | |
| feedService.changeFeedSave(feedId, request) | |
| .handleBaseResponse() | |
| .getOrThrow() | |
| }.onSuccess { response -> | |
| response?.let { | |
| // API 응답(isSaved)과 파라미터로 받은 값들을 조합 | |
| val update = FeedStateUpdateResult( | |
| feedId = feedId, | |
| isLiked = currentIsLiked, // isLiked 상태는 그대로 유지 | |
| likeCount = currentLikeCount, | |
| isSaved = it.isSaved | |
| ) | |
| _feedStateUpdateResult.emit(update) | |
| } | |
| } | |
| suspend fun changeFeedSave( | |
| feedId: Long, newSaveStatus: Boolean, currentIsLiked: Boolean, | |
| currentLikeCount: Int | |
| ): Result<FeedSaveResponse?> = | |
| runCatching { | |
| val request = FeedSaveRequest(type = newSaveStatus) | |
| feedService.changeFeedSave(feedId, request) | |
| .handleBaseResponse() | |
| .getOrThrow() | |
| }.onSuccess { response -> | |
| response?.let { | |
| // API 응답(isSaved)과 파라미터로 받은 값들을 조합 | |
| val update = FeedStateUpdateResult( | |
| feedId = feedId, | |
| isLiked = currentIsLiked, // isLiked 상태는 그대로 유지 | |
| likeCount = currentLikeCount, | |
| isSaved = it.isSaved | |
| ) | |
| _feedStateUpdateResult.tryEmit(update) | |
| } | |
| } |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt around
lines 274 to 295, the repository currently uses the suspending emit(...) to
publish save-state updates which can block; change this to use the non-blocking
tryEmit(...) consistent with the SharedFlow configuration: replace the call to
_feedStateUpdateResult.emit(update) with _feedStateUpdateResult.tryEmit(update)
so the event is emitted non-blockingly (no additional behavior changes
required).
| onContentClick: () -> Unit = {}, | ||
| onBookmarkClick: () -> Unit = {} | ||
| ) { |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
OthersFeedCard에 onBookmarkClick 추가: 기본값 제공으로 호출부는 깨지지 않으나, 실제 북마크 동작 연결을 점검하세요.
기본 파라미터로 빌드는 통과하겠지만, 호출부가 새 콜백을 전달하지 않으면 북마크 버튼이 무동작일 수 있습니다.
다음 스크립트로 호출부를 확인해 주세요(필요 시 콜백 연결 추가):
🏁 Script executed:
#!/bin/bash
# OthersFeedCard 호출부 점검
rg -nP --type=kt -C2 '\bOthersFeedCard\s*\('Length of output: 73
🏁 Script executed:
#!/bin/bash
# OthersFeedCard 호출부 재검증: kt 파일 glob 패턴 사용
rg -nP -g '*.kt' -C2 '\bOthersFeedCard\s*\(' .Length of output: 2012
OthersFeedCard onBookmarkClick 콜백 연결 필요
기본값으로 제공된 onBookmarkClick을 호출부에서 전달하지 않으면 북마크 버튼이 무동작합니다. 실제 동작을 위해 콜백을 연결하세요.
· app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt 라인 159
· (Preview용 호출부) app/src/main/java/com/texthip/thip/ui/feed/component/OthersFeedCard.kt 라인 137, 140
예시 수정:
-OthersFeedCard(
- feedItem = feed,
- onLikeClick = { onLikeClick(feed.feedId) },
-)
+OthersFeedCard(
+ feedItem = feed,
+ onLikeClick = { onLikeClick(feed.feedId) },
+ onBookmarkClick = { onBookmarkClick(feed.feedId) }
+)📝 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.
| onContentClick: () -> Unit = {}, | |
| onBookmarkClick: () -> Unit = {} | |
| ) { | |
| OthersFeedCard( | |
| feedItem = feed, | |
| onLikeClick = { onLikeClick(feed.feedId) }, | |
| onBookmarkClick = { onBookmarkClick(feed.feedId) } | |
| ) |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/component/OthersFeedCard.kt around
lines 40-42, the onBookmarkClick parameter currently defaults to an empty lambda
which leaves the bookmark button non-functional when the caller doesn't pass a
handler; update the component so the bookmark button's onClick invokes this
onBookmarkClick, and then wire real callbacks from call sites: in
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt at line
159 pass the view-model or navigation bookmark handler into OthersFeedCard, and
update the preview/example invocations in OthersFeedCard.kt at lines 137 and 140
to supply a no-op or sample handler as appropriate (or forward a state-updating
lambda) so the bookmark button has a connected callback.
| recentSearches: List<RecentSearchUiItem>, | ||
| onSearchClick: (String) -> Unit, | ||
| onRemove: (String) -> Unit | ||
| onRemove: (Long) -> Unit | ||
| ) { |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
컴포넌트 시그니처 변경(아이템 타입 + onRemove(Long))의 호출부 영향 점검 필요
ViewModel과 Screen에서 타입 변경이 일관되게 반영되었는지, 그리고 삭제 콜백이 id 기반으로 연결되었는지 확인이 필요합니다.
호출부를 찾는 스크립트:
🏁 Script executed:
#!/bin/bash
# PeopleRecentSearch 호출부 점검
rg -nP --type=kt -C2 '\bPeopleRecentSearch\s*\('Length of output: 77
호출부를 glob 필터로 재검색해 보겠습니다. 아래 스크립트를 실행해주세요.
🏁 Script executed:
#!/bin/bash
# PeopleRecentSearch 호출부 점검 (glob으로 .kt 파일만 검색)
rg -n -C2 '\bPeopleRecentSearch\s*\(' --glob '*.kt'Length of output: 2234
PeopleRecentSearch 호출부에 onRemove 파라미터 추가 필요
PeopleRecentSearch 컴포넌트 시그니처에 onRemove: (Long) -> Unit이 새로 추가되었으므로, 아래 호출부들에 삭제 콜백을 전달하도록 수정해주세요.
– ViewModel의 삭제 함수(viewModel::removeRecentSearch 등)를 onRemove에 연결해야 합니다.
수정 대상 위치:
• app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt — 라인 138
• app/src/main/java/com/texthip/thip/ui/feed/component/PeopleRecentSearch.kt — 라인 86
• app/src/main/java/com/texthip/thip/ui/feed/component/PeopleRecentSearch.kt — 라인 107
또한, recentSearches: List<RecentSearchUiItem> 타입 변경이 관련 ViewModel·Screen 전반에 일관되게 반영되었는지도 함께 확인해주세요.
🤖 Prompt for AI Agents
app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt around
line 138 and
app/src/main/java/com/texthip/thip/ui/feed/component/PeopleRecentSearch.kt
around lines 86 and 107: PeopleRecentSearch signature now requires an onRemove:
(Long) -> Unit callback and the recentSearches type change must be propagated;
update each call site to pass the ViewModel removal function (e.g.
viewModel::removeRecentSearch) into the onRemove parameter when invoking
PeopleRecentSearch, and ensure the recentSearches argument uses the new
List<RecentSearchUiItem> type everywhere (adjust imports/types in the ViewModel
and Screen if needed) so the types and callback are consistent across ViewModel,
Screen, and Component.
| @Serializable | ||
| data class FeedStateUpdateResult( | ||
| val feedId: Long, | ||
| val isLiked: Boolean, | ||
| val likeCount: Int, | ||
| val isSaved: Boolean | ||
| ) |
There was a problem hiding this comment.
SavedStateHandle 전달 시 직렬화 타입 불일치 가능성 — Parcelable/Java Serializable 구현 필요
이 타입이 back stack 간 savedStateHandle로 전달된다면 Android Bundle 호환 타입(Parcelable 또는 java.io.Serializable)이 필요합니다. 현재 @kotlinx.serialization.Serializable만으로는 Bundle 직렬화가 보장되지 않아 런타임 예외가 발생할 수 있습니다.
다음과 같이 Java Serializable을 추가(또는 Parcelize 사용)해 Bundle 호환을 보장하세요:
@Serializable
data class FeedStateUpdateResult(
val feedId: Long,
val isLiked: Boolean,
val likeCount: Int,
val isSaved: Boolean
-)
+) : java.io.Serializable참고: 이미 kotlinx.serialization을 유지해도 무방하며, SavedStateHandle 전파 용도만을 위해 java.io.Serializable을 병행하는 방식이 가장 간단합니다. 혹은 @Parcelize ... : Parcelable을 채택해도 됩니다(프로젝트에 parcelize 플러그인이 활성화되어 있어야 함).
📝 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.
| @Serializable | |
| data class FeedStateUpdateResult( | |
| val feedId: Long, | |
| val isLiked: Boolean, | |
| val likeCount: Int, | |
| val isSaved: Boolean | |
| ) | |
| @Serializable | |
| data class FeedStateUpdateResult( | |
| val feedId: Long, | |
| val isLiked: Boolean, | |
| val likeCount: Int, | |
| val isSaved: Boolean | |
| ) : java.io.Serializable |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/mock/FeedStateUpdateResult.kt
around lines 5–11, the data class is only annotated with
kotlinx.serialization.Serializable which is not guaranteed to be
Bundle-compatible for SavedStateHandle transfers; update the class to also
implement a Bundle-safe serialization (either add " : java.io.Serializable" to
the data class or annotate with @Parcelize and implement Parcelable), keep the
kotlinx.serialization annotation if needed, add the required import(s), and
ensure the project has the Parcelize plugin enabled if you choose Parcelable;
rebuild and run to verify SavedStateHandle transfers no longer throw runtime
serialization exceptions.
| import kotlin.onSuccess | ||
|
|
There was a problem hiding this comment.
불필요하고 잘못된 import로 인해 컴파일 오류 발생 가능
import kotlin.onSuccess는 유효하지 않습니다. 제거해 주세요. onSuccess/onFailure는 Result의 확장 함수로 별도 import 없이 사용 가능합니다.
-import kotlin.onSuccess📝 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 kotlin.onSuccess |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/viewmodel/SearchPeopleViewModel.kt
around lines 17 to 18, remove the invalid import statement `import
kotlin.onSuccess` which is not a real symbol and causes compile errors;
`onSuccess`/`onFailure` are Result extension functions and require no import, so
delete that import line and ensure the file compiles without adding any new
imports.
|
|
||
| import androidx.lifecycle.ViewModel | ||
| import androidx.lifecycle.viewModelScope | ||
| import com.texthip.thip.data.manager.TokenManager |
There was a problem hiding this comment.
💡 Verification agent
❓ Verification inconclusive
TokenManager DI 추가 — 의존성 구성 확인 필요
TokenManager에 대한 Hilt 제공자(Module/Binding)가 이미 존재하는지 확인해 주세요. 없다면 런타임 주입 실패가 발생합니다.
다음 스크립트로 TokenManager 제공자 선언을 찾을 수 있습니다:
🏁 Script executed:
#!/bin/bash
# TokenManager 제공자/바인딩 검색
rg -nP --type=kt -C2 '\b@Provides\b|\b@Binds\b' | rg -nP -C2 'TokenManager'Length of output: 102
Hilt 제공자 검색을 위해 Kotlin 파일 필터링 방식을 수정해 다시 실행해 주세요:
🏁 Script executed:
#!/bin/bash
# TokenManager에 대한 Hilt Module/Provider(Binds/Provides) 선언 검색
rg -nP -C2 '@Module\b|@Provides\b|@Binds\b' -g '*.kt' | rg -n 'TokenManager'Length of output: 76
Hilt 제공자 누락: TokenManager DI 구성 필요
파일: app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageViewModel.kt
라인: 5 (import com.texthip.thip.data.manager.TokenManager)
현재 코드베이스에서 TokenManager를 주입하기 위한 Hilt Module(@Module/@Provides/@BINDS)이 발견되지 않았습니다. 런타임에 DI 주입이 실패할 수 있으므로, 아래 중 하나를 반드시 적용해 주세요:
- 새로운 Hilt Module 생성
• 경로 예시:app/src/main/java/com/texthip/thip/di/TokenManagerModule.kt
• 예시 코드:@Module @InstallIn(SingletonComponent::class) object TokenManagerModule { @Provides fun provideTokenManager( // 필요한 의존성 예: Context, NetworkService 등 ): TokenManager = TokenManager(/* ... */) } - 기존 Hilt Module에
TokenManager에 대한 @Binds/@provides 메서드 추가
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageViewModel.kt
around line 5, TokenManager is being injected but there is no Hilt provider
registered; add a Hilt Module that provides or binds TokenManager (e.g., create
app/src/main/java/com/texthip/thip/di/TokenManagerModule.kt) annotated with
@Module and @InstallIn(SingletonComponent::class) and include either a @Provides
function that constructs TokenManager (injecting required dependencies such as
Context, network service, or other repos) or an abstract @Binds method to bind
an implementation to the TokenManager interface; ensure the module imports
necessary types and that the provider/binding is visible to Hilt at compile time
so DI for MyPageViewModel succeeds.
| import com.texthip.thip.ui.navigator.extensions.navigateToSearchPeople | ||
| import com.texthip.thip.ui.navigator.extensions.navigateToUserProfile |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
SearchPeople로 다중 중복 진입 방지 확인 권장
탑바 좌측 버튼 반복 탭 시 동일 화면이 스택에 중첩될 수 있습니다. navigateToSearchPeople 확장 함수에서 launchSingleTop=true 또는 적절한 popUpTo 설정을 사용하는지 확인해 주세요.
아래 스크립트로 확장 함수 구현을 확인해 주세요:
🏁 Script executed:
#!/bin/bash
# navigateToSearchPeople 확장 함수 사용 옵션 확인
rg -nP -C3 '\bnavigateToSearchPeople\s*\('Length of output: 1461
SearchPeople 진입 시 중복 방지 로직 추가 필요
navigateToSearchPeople 확장 함수가 현재 NavOptions 없이 호출되어, 탑바 왼쪽 버튼을 연속 탭하면 동일 화면이 백스택에 중첩될 수 있습니다. launchSingleTop = true 또는 적절한 popUpTo 설정을 추가해 중복 진입을 방지해주세요.
• 대상 위치
- app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt (함수
navigateToSearchPeople)
• 제안하는 변경 예시
-fun NavHostController.navigateToSearchPeople() {
- navigate(FeedRoutes.SearchPeople)
-}
+fun NavHostController.navigateToSearchPeople() {
+ navigate(FeedRoutes.SearchPeople) {
+ launchSingleTop = true
+ // 필요 시 popUpTo(FeedRoutes.SearchPeople) { inclusive = false }
+ }
+}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt
(imports around lines 20-21) and specifically in
app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt
where navigateToSearchPeople is defined, the extension currently navigates
without NavOptions causing duplicate SearchPeople entries when the top-bar
button is tapped repeatedly; update navigateToSearchPeople to pass NavOptions
that set launchSingleTop = true or use popUpTo targeting the SearchPeople
destination (inclusive = false) so repeated navigations reuse the existing
destination instead of stacking, ensuring you create and pass the NavOptions to
the NavController.navigate(...) call.
| sealed interface SignupScreenRoutes { | ||
| @Serializable | ||
| data object Nickname : SignupScreenRoutes | ||
|
|
||
| @Serializable | ||
| data object SignupFlow : CommonRoutes() | ||
| @Serializable | ||
| data object Genre : SignupScreenRoutes | ||
|
|
||
| @Serializable | ||
| data object Tutorial : SignupScreenRoutes | ||
|
|
||
| @Serializable | ||
| data object Done : SignupScreenRoutes | ||
| } | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
sealed interface 사용으로 타입 불일치 가능성 — CommonRoutes 계층으로 승격 필요
현재 SignupScreenRoutes가 sealed interface로 선언되어 CommonRoutes(= Routes 상속) 계층에 속하지 않습니다. navigate(CommonRoutes.SignupScreenRoutes.Genre) 호출부(AuthNavigationExtensions.kt Line 26)에서 네비게이션 확장함수가 Routes 계열을 요구한다면 컴파일 에러가 발생합니다.
해결책: SignupScreenRoutes를 sealed class로 바꾸고 CommonRoutes를 상속시키세요.
다음 변경을 제안합니다:
- @Serializable
- sealed interface SignupScreenRoutes {
- @Serializable
- data object Nickname : SignupScreenRoutes
-
- @Serializable
- data object Genre : SignupScreenRoutes
-
- @Serializable
- data object Tutorial : SignupScreenRoutes
-
- @Serializable
- data object Done : SignupScreenRoutes
- }
+ @Serializable
+ sealed class SignupScreenRoutes : CommonRoutes() {
+ @Serializable
+ data object Nickname : SignupScreenRoutes()
+
+ @Serializable
+ data object Genre : SignupScreenRoutes()
+
+ @Serializable
+ data object Tutorial : SignupScreenRoutes()
+
+ @Serializable
+ data object Done : SignupScreenRoutes()
+ }참고: kotlinx.serialization에서 sealed interface도 지원되지만, 현재 프로젝트의 타입 안전 네비게이션 확장과의 호환성은 Routes 계층 상속 여부에 달려 있을 가능성이 큽니다. 위 형태면 navigate(...) 제네릭 제약을 충족합니다.
검증을 위해 다음 스크립트를 실행하여 CommonRoutes 선언, Routes 인터페이스, 그리고 navigate 확장 함수 시그니처 및 호출 위치를 확인합니다.
🏁 Script executed:
#!/bin/bash
set -e
echo "=== CommonRoutes 선언 확인 ==="
rg -nP '\bsealed (class|interface) CommonRoutes\b' --type kt
echo "=== Routes 인터페이스 확인 ==="
rg -nP '\binterface\s+Routes\b' --type kt
echo "=== navigate 확장 함수 시그니처 확인 ==="
rg -nP 'fun\s+\w+\.navigate<.*>\(' --type kt
echo "=== AuthNavigationExtensions.kt 파일 위치 확인 ==="
rg -l 'AuthNavigationExtensions.kt'
AUTH_FILE=$(rg -l 'AuthNavigationExtensions.kt')
if [ -n "$AUTH_FILE" ]; then
echo "=== $AUTH_FILE 내 navigate 호출 위치 ==="
rg -n 'navigate' -n -C2 "$AUTH_FILE"
fiLength of output: 161
검증을 위해 CommonRoutes 선언, Routes 인터페이스, navigate 확장 함수 시그니처 및 호출부를 다시 조회합니다. 아래 스크립트를 실행해주세요.
🏁 Script executed:
#!/bin/bash
set -e
echo "=== CommonRoutes.kt 파일 위치 찾기 ==="
COMMON_ROUTES_FILE=$(fd CommonRoutes.kt || find . -type f -name CommonRoutes.kt)
echo "$COMMON_ROUTES_FILE"
if [ -n "$COMMON_ROUTES_FILE" ]; then
echo "=== $COMMON_ROUTES_FILE 내용 (1~50행) ==="
sed -n '1,50p' "$COMMON_ROUTES_FILE"
fi
echo "=== Routes 인터페이스 정의 검색 ==="
rg -nP '\binterface\s+Routes\b' -g '*.kt'
echo "=== navigate 확장 함수 정의 검색 ==="
rg -nP 'fun\s+\w+\.navigate<.*>\(' -g '*.kt' -n -C2
echo "=== AuthNavigationExtensions.kt 위치 찾기 ==="
AUTH_FILE=$(fd AuthNavigationExtensions.kt || find . -type f -name AuthNavigationExtensions.kt)
echo "$AUTH_FILE"
if [ -n "$AUTH_FILE" ]; then
echo "=== $AUTH_FILE 내 navigate 호출 위치 (±2줄) ==="
rg -n -C2 'navigate' "$AUTH_FILE"
fiLength of output: 1670
SignupScreenRoutes를 sealed class로 전환해 CommonRoutes 계층에 포함하세요
현재 SignupScreenRoutes는 sealed interface로 선언되어 CommonRoutes(→Routes) 하위 타입이 아니므로
navigate(CommonRoutes.SignupScreenRoutes.Genre) 호출 시 컴파일 오류가 발생합니다.
수정 위치
- app/src/main/java/com/texthip/thip/ui/navigator/routes/CommonRoutes.kt:
sealed interface SignupScreenRoutes선언부
제안된 변경:
@@ -20,13 +20,17 @@ sealed class CommonRoutes : Routes() {
- @Serializable
- sealed interface SignupScreenRoutes {
- @Serializable
- data object Nickname : SignupScreenRoutes
-
- @Serializable
- data object Genre : SignupScreenRoutes
-
- @Serializable
- data object Tutorial : SignupScreenRoutes
-
- @Serializable
- data object Done : SignupScreenRoutes
- }
+ @Serializable
+ sealed class SignupScreenRoutes : CommonRoutes() {
+ @Serializable
+ data object Nickname : SignupScreenRoutes()
+
+ @Serializable
+ data object Genre : SignupScreenRoutes()
+
+ @Serializable
+ data object Tutorial : SignupScreenRoutes()
+
+ @Serializable
+ data object Done : SignupScreenRoutes()
+ }📝 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.
| sealed interface SignupScreenRoutes { | |
| @Serializable | |
| data object Nickname : SignupScreenRoutes | |
| @Serializable | |
| data object SignupFlow : CommonRoutes() | |
| @Serializable | |
| data object Genre : SignupScreenRoutes | |
| @Serializable | |
| data object Tutorial : SignupScreenRoutes | |
| @Serializable | |
| data object Done : SignupScreenRoutes | |
| } | |
| @Serializable | |
| sealed class SignupScreenRoutes : CommonRoutes() { | |
| @Serializable | |
| data object Nickname : SignupScreenRoutes() | |
| @Serializable | |
| data object Genre : SignupScreenRoutes() | |
| @Serializable | |
| data object Tutorial : SignupScreenRoutes() | |
| @Serializable | |
| data object Done : SignupScreenRoutes() | |
| } |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/navigator/routes/CommonRoutes.kt around
lines 20–33, convert the current sealed interface SignupScreenRoutes into a
sealed class that is a subtype of the CommonRoutes hierarchy so it can be used
as CommonRoutes.SignupScreenRoutes; change the declaration to a sealed class
(e.g., sealed class SignupScreenRoutes : CommonRoutes) and replace the inner
data object declarations with @Serializable object singletons that extend the
sealed class (e.g., @Serializable object Nickname : SignupScreenRoutes()),
ensuring all signup route entries are concrete subclasses of the new sealed
class.
| fun SignupDoneScreen( | ||
| userInfo: SignupUserInfo, | ||
| onNavigateToMain: () -> Unit | ||
| ) { |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
시그니처 변경(네비 콜백 추가): 호출부 전수 반영 확인 필요.
onNavigateToMain이 필수 파라미터로 추가되었습니다. 모든 컴포저블/내비 그래프에서 새로운 시그니처로 업데이트되었는지 확인해 주세요.
점검 스크립트:
🏁 Script executed:
#!/bin/bash
# SignupDoneScreen 호출부 확인
rg -nP -C2 --type=kt '\bSignupDoneScreen\s*\('Length of output: 75
아래 스크립트로 .kt 파일 전체를 대상으로 호출부를 다시 확인해 보시겠어요?
🏁 Script executed:
#!/bin/bash
# SignupDoneScreen 호출부 확인: 모든 .kt 파일 대상으로 검색
rg -n --glob '*.kt' 'SignupDoneScreen(' -C2 .Length of output: 132
다음 스크립트로 .kt 파일 전체에서 SignupDoneScreen( 호출부를 리터럴 매치 모드로 다시 확인해 주세요.
🏁 Script executed:
#!/bin/bash
# SignupDoneScreen 호출부 확인: 모든 .kt 파일 대상으로 리터럴 검색
rg -n --glob '*.kt' -F 'SignupDoneScreen(' -C2 .Length of output: 1602
onNavigateToMain 파라미터 누락 호출부 확인 필요
SignupDoneScreen 시그니처에 onNavigateToMain이 필수로 추가되었으므로, 모든 호출부에서 해당 파라미터를 전달해야 합니다. 현재 Preview용 호출부에 누락된 것이 확인되었습니다.
- app/src/main/java/com/texthip/thip/ui/signin/screen/SignupDoneScreen.kt:
•private fun SignupDoneScreenPrev()내부 호출부(143줄)
기존수정안SignupDoneScreen( userInfo = SignupUserInfo( nickname = "JJUYAA", … ) )
SignupDoneScreen( userInfo = SignupUserInfo( nickname = "JJUYAA", … ), onNavigateToMain = {} // 임시 Lambda 전달 )
위 위치 외에도 프로젝트 내에 남아있는 다른 호출부가 없는지 한 번 더 점검 부탁드립니다.
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/signin/screen/SignupDoneScreen.kt
around lines 38 to 41, the SignupDoneScreen signature now requires an
onNavigateToMain parameter but the Preview caller (private fun
SignupDoneScreenPrev at ~line 143) does not pass it; update that Preview call to
include a temporary lambda (onNavigateToMain = {}) and scan the project for
other SignupDoneScreen callers to add the required parameter where missing.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)
169-193: observeForever로 인한 누수/중복 콜백 위험 – StateFlow 수집으로 교체 필요Compose 수명 주기와 무관한 observeForever 사용은 화면이 사라져도 옵저버가 유지되어 누수 및 중복 업데이트를 유발할 수 있습니다. savedStateHandle.getStateFlow(...) + collectLatest로 대체해 주세요. 동일 패턴이 위의 deleted_feed_id 블록(라인 115-124)에도 존재하므로 함께 교체를 권장합니다.
아래 diff로 본 블록(라인 169-193)을 교체해 주세요:
- LaunchedEffect(Unit) { //커스텀객체 타입 인식오류 -> 직렬화가 아닌 잘게 쪼개어 전달 - navController.currentBackStackEntry?.savedStateHandle?.let { handle -> - handle.getLiveData<Long>("updated_feed_id").observeForever { feedId -> - if (feedId != null) { - val isLiked = handle.get<Boolean>("updated_feed_isLiked") ?: false - val likeCount = handle.get<Int>("updated_feed_likeCount") ?: 0 - val isSaved = handle.get<Boolean>("updated_feed_isSaved") ?: false - - val result = FeedStateUpdateResult( - feedId = feedId, - isLiked = isLiked, - likeCount = likeCount, - isSaved = isSaved - ) - - feedViewModel.updateFeedStateFromResult(result) - - handle.remove<Long>("updated_feed_id") - handle.remove<Boolean>("updated_feed_isLiked") - handle.remove<Int>("updated_feed_likeCount") - handle.remove<Boolean>("updated_feed_isSaved") - } - } - } - } + LaunchedEffect(navController) { // 커스텀 객체 직렬화 회피: primitive 분해 전달 + val handle = navController.currentBackStackEntry?.savedStateHandle ?: return@LaunchedEffect + handle.getStateFlow<Long?>("updated_feed_id", null).collectLatest { feedId -> + if (feedId != null) { + val isLiked = handle.get<Boolean>("updated_feed_isLiked") ?: false + val likeCount = handle.get<Int>("updated_feed_likeCount") ?: 0 + val isSaved = handle.get<Boolean>("updated_feed_isSaved") ?: false + + val result = FeedStateUpdateResult( + feedId = feedId, + isLiked = isLiked, + likeCount = likeCount, + isSaved = isSaved + ) + feedViewModel.updateFeedStateFromResult(result) + + handle.remove<Long>("updated_feed_id") + handle.remove<Boolean>("updated_feed_isLiked") + handle.remove<Int>("updated_feed_likeCount") + handle.remove<Boolean>("updated_feed_isSaved") + } + } + }파일 상단에 아래 import가 필요합니다(외부 변경):
// 추가 import import kotlinx.coroutines.flow.collectLatest또한, deleted_feed_id 관찰(라인 115-124)도 동일 방식으로 교체해 주세요(외부 변경 예시):
LaunchedEffect(navController) { val handle = navController.currentBackStackEntry?.savedStateHandle ?: return@LaunchedEffect handle.getStateFlow<Long?>("deleted_feed_id", null).collectLatest { deletedId -> if (deletedId != null) { feedViewModel.removeDeletedFeed(deletedId) handle.remove<Long>("deleted_feed_id") } } }app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (1)
166-169: OthersFeedCard에 북마크 콜백 전달 완료호출부에서 onBookmarkClick(feed.feedId) 전달로 무동작 위험을 해소했습니다. 과거 지적 사항을 잘 반영했습니다.
🧹 Nitpick comments (7)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (2)
55-55: FeedStateUpdateResult import의 패키지 위치 재고 권장현재 mock 패키지에서 가져오고 있는데, 실사용(프로덕션) 화면에서 사용 중이라면 mock 이름/패키지는 혼동을 줄 수 있습니다. 별도 model/domain/ui 패키지로 이동하거나 명확한 위치로 재정렬을 고려해 주세요.
219-219: 상단바 왼쪽 아이콘 → 사용자 검색 이동 연결 LGTMUI 기대 동작과 맞습니다. 접근성 측면에서 탐색 의도를 더 잘 드러내려면 검색 아이콘으로의 리소스 교체도 고려할 수 있습니다(선택).
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (2)
82-87: 토스트 자동 숨김 처리 적절 (취소-재시작 안전)LaunchedEffect(uiState.showToast)로 2초 후 숨김 처리: showToast 값 변경 시 이전 코루틴이 취소되고 재시작되어 중복 실행이 방지됩니다. UX 개선 여지가 있다면 페이드 인/아웃 애니메이션(AnimatedVisibility) 추가를 고려할 수 있습니다.
183-196: 토스트 오버레이 배치 LGTM — 클릭 차단 고려는 선택TopCenter 정렬 + zIndex(1f)로 시각적 우선순위 확보가 잘 되어 있습니다. 표시 중 하위 UI 클릭을 허용할지 정책에 따라, 명시적으로 pointerInput로 이벤트를 소비하거나, 반대로 클릭을 통과시키는 처리를 추가할 수 있습니다(선택).
app/src/main/java/com/texthip/thip/ui/feed/component/OthersFeedCard.kt (3)
45-50: 중복 clickable 제거로 의도 명확화Column과 내부 Box 모두 onContentClick을 처리하고 있어 중복입니다. 컨텐츠 영역(Box) 쪽에만 클릭을 남기고 바깥 Column의 clickable은 제거하는 편이 낫습니다.
- Column( - modifier = modifier - .fillMaxWidth() - .padding(horizontal = 20.dp) - .clickable { onContentClick() } - ) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + ) { @@ - Box( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp) - .clickable { onContentClick() } - ) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp) + .clickable { onContentClick() } + ) {Also applies to: 57-62
56-87: 텍스트 말줄임 처리 + “더보기” 시각 표시 UX 개선 여지시각적 오버플로우 시 ic_text_more를 표시하는 접근은 좋습니다. 추가로 아이콘 터치 시 전체 보기로 이동하거나, onContentClick으로 위임하는 인터랙션을 부여하면 사용성이 개선됩니다(선택).
125-143: 프리뷰 보강은 충분 — 필요 시 북마크/좋아요 콜백도 표시 확인용 로그 추가 고려현재 기본 파라미터로 동작은 문제없습니다. 상호작용 미리보기 검증을 위해 onLikeClick/onBookmarkClick에 로그/스낵바를 연결하는 것도 도움이 됩니다(선택).
📜 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 (11)
app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/component/OthersFeedCard.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt(8 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt(1 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(3 hunks)app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt(1 hunks)app/src/main/res/values/strings.xml(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt
- app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt
- app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt
- app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt
- app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt
- app/src/main/res/values/strings.xml
- app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt
🧰 Additional context used
🧬 Code Graph Analysis (2)
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/feed/component/OthersFeedCard.kt (1)
app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
ThipTheme(41-69)
🔇 Additional comments (5)
app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt (1)
29-30: 새 FeedRoutes.SearchPeople 라우트 추가 적절Sealed class 내 @serializable data object로 정의한 점이 Navigation + Kotlinx Serialization 흐름에 잘 맞습니다. 네이밍도 일관적이라 추후 확장에 유리합니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)
76-76: 검색 화면 네비게이션 콜백 추가 LGTMonNavigateToSearchPeople 기본값을 제공해 호출부 영향 최소화했고, 상단 바 왼쪽 아이콘과의 연결도 자연스럽습니다. 네비게이션 그래프의 SearchPeople 경로와 일치하는지만 최종 확인해 주세요.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (2)
58-65: 북마크/팔로우 토글 로직 연결 잘 됨
- 북마크: viewModel.changeFeedSave(feedId) 연결 OK
- 팔로우 토글: UI에서 문자열을 생성하고 ViewModel에 전달해 안드로이드 의존성을 분리한 점이 적절합니다.
75-78: 컴포저블 파라미터 확장 일관성 확보onBookmarkClick, onToggleFollow, onHideToast 추가로 상호작용 표면이 명확해졌습니다. 호출부(네비게이션/프리뷰) 업데이트까지 반영되어 있어 역호환성 문제 없습니다.
app/src/main/java/com/texthip/thip/ui/feed/component/OthersFeedCard.kt (1)
108-117: ActionBarButton에 isSaved/onBookmarkClick 전달 LGTM좋아요/댓글/저장 상태와 액션이 일관되게 연결되어 있습니다. ActionBarButton의 최근 API 변경(잠금 아이콘 boolean 제어)과의 시그니처 호환성만 최종 확인해 주세요.
| var isTextTruncated by remember { mutableStateOf(false) } | ||
|
|
There was a problem hiding this comment.
컴파일 오류: remember/mutableStateOf 및 getValue/setValue import 누락
var isTextTruncated by remember { mutableStateOf(false) } 사용에 필요한 runtime import가 빠졌습니다. 아래 import를 추가하지 않으면 컴파일되지 않습니다.
외부 변경(파일 상단 import 추가):
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.mutableStateOf🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/component/OthersFeedCard.kt around
lines 43-44, the delegated state declaration "var isTextTruncated by remember {
mutableStateOf(false) }" is missing required Compose runtime imports; add
imports for androidx.compose.runtime.getValue,
androidx.compose.runtime.setValue, androidx.compose.runtime.remember, and
androidx.compose.runtime.mutableStateOf at the top of the file so the delegated
property and remember/mutableStateOf calls compile.
➕ 이슈 링크
🔎 작업 내용
📸 스크린샷
새롭게 만든 ui 영상 첨부합니다.
신규 유저로 회원가입 시 튜토리얼 -> SignupDone 화면 -> Feed 화면
bandicam.2025-08-20.06-18-44-286.mp4
😢 해결하지 못한 과제
닉네임 변경 시 warningmessage를 띄우는 부분에서 서버 응답 코드를 파싱하여 메시지를 띄우도록 처리했는데, 간헐적으로 오류가 발생합니다 (서버에서 baseresponse 형태의 응답이 오지 않고 400 error만 던져주는 경우가 있음) 이부분은 다시 한 번 수정해볼게욥
📢 리뷰어들에게
Summary by CodeRabbit
New Features
Enhancements
Chores