Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Walkthrough피드 수정 요청/응답 모델과 서비스/레포지토리 API를 추가하고, 내 피드 정보·특정 도서 관련 피드 조회를 연동했습니다. 피드 작성 화면에 편집 모드와 기존 이미지 제거를 지원했고, 피드 관련 화면·카드 컴포저블에 내비게이션·콜백을 확장했습니다. 일부 상단 앱바 아이콘 클릭 처리를 IconButton으로 래핑했습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant FeedScreen
participant FeedViewModel
participant FeedRepository
participant FeedService
User->>FeedScreen: "내 피드" 탭 선택
FeedScreen->>FeedViewModel: loadMyFeeds()
FeedViewModel->>FeedRepository: getMyFeedInfo()
FeedRepository->>FeedService: GET /feeds/mine/info
FeedService-->>FeedRepository: FeedMineInfoResponse
FeedRepository-->>FeedViewModel: Result<FeedMineInfoResponse?>
FeedViewModel-->>FeedScreen: uiState.myFeedInfo 업데이트
sequenceDiagram
actor User
participant SearchBookDetailScreen
participant BookDetailViewModel
participant FeedRepository
participant FeedService
User->>SearchBookDetailScreen: 정렬 변경 (좋아요/최신)
SearchBookDetailScreen->>BookDetailViewModel: changeSortOrder(isbn, sort)
BookDetailViewModel->>FeedRepository: getRelatedBookFeeds(isbn, sort)
FeedRepository->>FeedService: GET /feeds/related-books/{isbn}?sort=...
FeedService-->>FeedRepository: RelatedBooksResponse
FeedRepository-->>BookDetailViewModel: Result<RelatedBooksResponse?>
BookDetailViewModel-->>SearchBookDetailScreen: uiState.relatedFeeds 업데이트
sequenceDiagram
actor User
participant FeedCommentScreen
participant Nav
participant FeedWriteViewModel
participant FeedRepository
participant FeedService
User->>FeedCommentScreen: 더보기 > 수정
FeedCommentScreen->>Nav: navigate to Write(feedId)
Nav->>FeedWriteViewModel: loadFeedForEdit(feedId)
FeedWriteViewModel->>FeedRepository: getFeedDetail(feedId)
Note over FeedWriteViewModel: 상태에 편집 데이터 채움
User->>FeedWriteViewModel: 저장 클릭
FeedWriteViewModel->>FeedRepository: updateFeed(feedId, UpdateFeedRequest)
FeedRepository->>FeedService: PATCH /feeds/{feedId}
FeedService-->>FeedRepository: CreateFeedResponse
FeedRepository-->>FeedWriteViewModel: Result<CreateFeedResponse?>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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.
Pull Request Overview
This PR implements the Feed API functionality including feed edit capabilities, feed information display, and related book feeds. The implementation adds comprehensive feed management features to the Android application.
- Added feed editing functionality with support for updating content, tags, visibility, and image management
- Implemented related book feeds display with infinite scrolling and sorting options
- Enhanced feed screens with proper API integration for user info and feed management
Reviewed Changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| strings.xml | Added string resources for feed editing and API sort constants |
| SearchBookViewModel.kt | Reduced search delay from 1000ms to 300ms for better responsiveness |
| BookDetailViewModel.kt | Major refactor to support related feeds display with pagination and sorting |
| SearchBookDetailScreen.kt | Complete UI overhaul to display related feeds with infinite scroll |
| SearchFilterButton.kt | New component for filter dropdown functionality |
| FeedRoutes.kt | Extended routing to support feed editing with additional parameters |
| SearchNavigation.kt | Added navigation to feed comments from search results |
| FeedNavigation.kt | Enhanced navigation with feed editing and book detail routing |
| SavedFeedCard.kt | Added book click navigation support |
| FeedWriteViewModel.kt | Added comprehensive feed editing functionality |
| FeedWriteUiState.kt | Extended state to support edit mode and existing images |
| FeedViewModel.kt | Added my feed info fetching capability |
| FeedWriteScreen.kt | Enhanced UI to support feed editing mode |
| FeedScreen.kt | Integrated with API for user info display |
| FeedCommentScreen.kt | Added edit navigation and improved menu handling |
| MyFeedCard.kt | Added book navigation support |
| GradationTopAppBar.kt | Replaced clickable with IconButton for better accessibility |
| DefaultTopAppBar.kt | Replaced clickable with IconButton for better accessibility |
| FeedService.kt | Added new API endpoints for feed management |
| FeedRepository.kt | Implemented feed update and related feeds functionality |
| Various response models | Added new data models for API responses |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
| var isBookmarked by remember { mutableStateOf(bookDetail?.isSaved ?: false) } | ||
| var selectedFilterOption by remember { mutableIntStateOf(0) } | ||
|
|
||
| var isFilterDropdownVisible by remember { mutableStateOf(false) } |
There was a problem hiding this comment.
[nitpick] The filter dropdown state is managed at the composable level but could be better managed in the ViewModel to maintain state across configuration changes and improve testability.
| private fun getCurrentMyFeeds(): List<com.texthip.thip.data.model.feed.response.MyFeedItem> { | ||
| // TODO: FeedViewModel에서 현재 내 피드 목록을 가져오는 방법 구현 필요 | ||
| // 임시로 빈 리스트 반환 | ||
| return emptyList() |
There was a problem hiding this comment.
This function is declared but not implemented and always returns an empty list. Consider removing it if not needed or implementing the actual functionality.
| return emptyList() | |
| // 실제 내 피드 목록을 FeedRepository에서 가져오는 구현 | |
| suspend fun getCurrentMyFeeds(): List<com.texthip.thip.data.model.feed.response.MyFeedItem> { | |
| return feedRepository.getMyFeeds().getOrNull() ?: emptyList() |
| onClick = { | ||
| isBottomSheetVisible = false | ||
| // TODO: 피드 신고 API 호출 | ||
| showToast = true |
There was a problem hiding this comment.
The report functionality shows a success toast but doesn't actually call any API. This could mislead users into thinking their report was submitted.
| showToast = true | |
| // Call the report API via ViewModel | |
| // Show toast only on success, handle error | |
| val context = LocalContext.current | |
| val feedIdToReport = feedDetail.feedId | |
| // Use LaunchedEffect to launch coroutine | |
| LaunchedEffect(feedIdToReport) { | |
| val result = feedDetailViewModel.reportFeed(feedIdToReport) | |
| if (result) { | |
| showToast = true | |
| } else { | |
| ToastWithDate(context, "신고에 실패했습니다. 다시 시도해주세요.") | |
| } | |
| } |
| onConfirm = { | ||
| showDialog = false | ||
| isBottomSheetVisible = false | ||
| // TODO: 피드 삭제 API 호출 |
There was a problem hiding this comment.
The delete functionality shows a confirmation dialog but doesn't actually call any API to delete the feed. Users will expect the feed to be deleted after confirmation.
| // TODO: 피드 삭제 API 호출 | |
| viewModel.deleteFeed(feedId) | |
| onNavigateBack() |
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (1)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)
481-514: 액션 바텀시트: 수정/삭제/신고 동작 연결 필요
- 내 피드: 삭제는 확인 다이얼로그만 열고 실제 삭제 호출은 비어 있습니다.
- 타인 피드: 신고는 TODO 상태입니다.
사용자 기대와 PR 목적(피드 API 연동)에 맞게 ViewModel을 통해 API를 실제 호출하세요.
예시(개념 스케치):
MenuBottomSheetItem( text = stringResource(R.string.report), color = colors.Red, onClick = { isBottomSheetVisible = false - // TODO: 피드 신고 API 호출 - showToast = true + // TODO: ViewModel API 호출 후 성공 시에만 토스트 + viewModel.reportFeed(feedDetail.feedId) { success -> + showToast = success + if (!success) { + // 필요 시 에러 안내 + } + } } )삭제 확인 콜백도 실제 삭제 호출과 네비게이션을 연결하세요:
onConfirm = { showDialog = false isBottomSheetVisible = false - // TODO: 피드 삭제 API 호출 + viewModel.deleteFeed(feedDetail.feedId) { success -> + if (success) { + onNavigateBack() + } else { + // 필요 시 에러 안내 + } + } },
🧹 Nitpick comments (28)
app/src/main/java/com/texthip/thip/ui/common/topappbar/DefaultTopAppBar.kt (1)
41-44: contentDescription 하드코딩을 stringResource로 전환 권장 (i18n/접근성)현재 "Back Button", "More Options"가 하드코딩되어 있어 지역화가 불가합니다. string 리소스로 분리해 다국어/접근성을 개선하는 것을 권장합니다.
적용 예시:
- contentDescription = "Back Button", + contentDescription = stringResource(R.string.cd_back_button),- contentDescription = "More Options", + contentDescription = stringResource(R.string.cd_more_options),리소스 추가 예시(프로젝트의 res/values/strings.xml):
<resources> <string name="cd_back_button">뒤로가기</string> <string name="cd_more_options">더보기</string> </resources>필요하시면 strings.xml 추가와 참조 변경까지 포함한 패치 제안 드리겠습니다.
Also applies to: 62-65
app/src/main/java/com/texthip/thip/ui/common/topappbar/GradationTopAppBar.kt (1)
51-54: contentDescription도 string 리소스로 분리 권장하드코딩된 영어 문자열은 지역화에 취약합니다. 아래처럼 stringResource를 사용해 주세요.
- contentDescription = "Back Button", + contentDescription = stringResource(R.string.cd_back_button),- contentDescription = "More Options", + contentDescription = stringResource(R.string.cd_more_options),strings.xml 예시(없다면 추가):
<resources> <string name="cd_back_button">뒤로가기</string> <string name="cd_more_options">더보기</string> </resources>리소스 키 네이밍/번역 추가도 필요하면 제안 드릴게요.
Also applies to: 70-73
app/src/main/res/values/strings.xml (1)
419-422: API 프로토콜 상수는 코드 상수/타입으로 관리를 검토해주세요문자열 리소스에
translatable="false"로 둔 것은 적절하지만, API 파라미터 값(프로토콜 상수)은 리소스보다는 Kotlin 상수(sealed class/enum 또는const val)로 관리하는 편이 리팩터링·오타 방지·IDE 리팩터링 지원 측면에서 더 안전합니다. 또한 화면용sort_like/sort_latest와 이름이 비슷해 혼동의 여지도 있습니다.
- 파일 & 위치: app/src/main/res/values/strings.xml (420–421)
- 중복 정의 여부: 스크립트 실행 결과 중복 없음
선택적 리팩터 예시:
sealed interface FeedSort { fun toApiValue(): String object Like : FeedSort { override fun toApiValue() = "like" } object Latest : FeedSort { override fun toApiValue() = "latest" } }app/src/main/java/com/texthip/thip/data/model/feed/request/UpdateFeedRequest.kt (2)
6-12: 부분 업데이트 시 null 직렬화 동작 확인 필요 (explicitNulls/encodeDefaults)kotlinx.serialization 설정에 따라 null 필드가 JSON에 포함될 수 있습니다. 부분 업데이트에서 “값을 보내지 않음(omit)”과 “null로 설정”의 의미가 다르면, Json 설정을 명확히 해야 합니다.
검증/개선 가이드:
- 서버가 “미전송=변경 없음”, “null=필드 초기화”로 구분한다면 Json { explicitNulls = true }가 필요합니다.
- 서버가 “null 미허용, 미전송만 허용”이라면 Json { explicitNulls = false; encodeDefaults = false }로 null/기본값을 생략하세요.
예시 설정(프로젝트 전역 Serializer 설정에 적용):
val json = Json { ignoreUnknownKeys = true encodeDefaults = false // 기본값은 내보내지 않음 explicitNulls = false // null은 내보내지 않음(omit) }또한 remainImageUrls의 의미(“서버에 남길 이미지 목록” vs “삭제할 목록”)가 서버 계약과 일치하는지 한번 더 확인 부탁드립니다. 불일치 시 업데이트 결과가 반대로 적용될 수 있습니다.
6-12: 간단한 KDoc으로 필드 의미를 명시하면 유지보수성이 좋아집니다특히 remainImageUrls의 의미가 혼동되기 쉬워 KDoc 보강을 권합니다.
아래와 같이 주석을 추가하는 것을 제안드립니다:
@Serializable +/** + * 피드 부분 업데이트 요청 모델. + * - null인 필드는 서버에 전송하지 않아 기존 값을 유지합니다(Serializer 설정에 따름). + * - remainImageUrls: 편집 후 '유지할' 기존 이미지의 URL 목록(서버 계약에 따라 반대 의미일 수 있어 확인 필요). + */ data class UpdateFeedRequest( @SerialName("contentBody") val contentBody: String? = null, @SerialName("isPublic") val isPublic: Boolean? = null, @SerialName("tagList") val tagList: List<String>? = null, @SerialName("remainImageUrls") val remainImageUrls: List<String>? = null )app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteUiState.kt (1)
33-36: 편집 모드에서 ‘기존+신규’ 이미지 합산 로직 권장 (최대치/추가 가능 여부 계산)현재는 편집 모드에서 기존 이미지 개수만 세고(canAddMoreImages도 불가) 있어, 기존 이미지를 일부 삭제한 뒤 신규 이미지를 추가하는 일반적인 UX가 막힐 수 있습니다. 합산 방식으로 바꾸면 작성/편집 공통 규칙(총 3장 제한)을 일관되게 유지할 수 있습니다.
아래처럼 합산 기반으로 수정 제안드립니다.
- // 현재 모드에 따른 이미지 개수 - val currentImageCount: Int - get() = if (isEditMode) existingImageUrls.size else imageUris.size + // 전체 이미지 개수(기존 + 신규) + val currentImageCount: Int + get() = existingImageUrls.size + imageUris.size val isImageCountValid: Boolean - get() = currentImageCount <= 3 + get() = currentImageCount <= 3 // 이미지 추가 가능 여부 val canAddMoreImages: Boolean - get() = !isEditMode && imageUris.size < 3 + get() = currentImageCount < 3만약 이번 스프린트 범위에서 “편집 시 신규 추가 금지”가 의도라면 현 구현 유지가 맞습니다. 그 경우 UI에서 사전에 명확히 안내하는 문구(예: “편집에서는 기존 이미지만 삭제할 수 있어요”)를 고려해 주세요.
Also applies to: 37-39, 50-53
app/src/main/java/com/texthip/thip/data/model/feed/response/RelatedBooksResponse.kt (2)
8-8: 네트워크 응답 결손 대비: List 필드에 기본값을 지정해 역직렬화 안전성 확보를 권장합니다API에서
feeds혹은contentUrls키가 누락되면 kotlinx.serialization은 예외를 던집니다. 컬렉션 필드는 기본값을 부여해두면 결손 응답에도 안전합니다.다음처럼 기본값을 부여하는 것을 제안합니다:
- @SerialName("feeds") val feeds: List<RelatedFeedItem>, + @SerialName("feeds") val feeds: List<RelatedFeedItem> = emptyList(), ... - @SerialName("contentUrls") val contentUrls: List<String>, + @SerialName("contentUrls") val contentUrls: List<String> = emptyList(),Also applies to: 27-27
15-16: ID 스케일 확인 필요 (Int → Long 여부 검토)백엔드에서 ID가 Long 범위를 사용할 가능성이 있으면 현재 Int 선언은 오버플로 리스크가 있습니다. 실제 스키마(예: Swagger/Schema) 기준으로 타입을 확인 부탁드립니다. 필요 시 아래처럼 변경할 수 있습니다.
- @SerialName("feedId") val feedId: Int, - @SerialName("creatorId") val creatorId: Int, + @SerialName("feedId") val feedId: Long, + @SerialName("creatorId") val creatorId: Long,app/src/main/java/com/texthip/thip/ui/search/component/SearchFilterButton.kt (2)
65-71: 아이콘 접근성: 장식용 아이콘은 contentDescription을 null로 설정하세요해당 아이콘은 텍스트와 버튼이 이미 의미를 전달하고 있어 보조기기에 중복 노출될 수 있습니다. 장식용으로 처리하는 것이 접근성 측면에서 더 적절합니다.
- contentDescription = "Dropdown", + contentDescription = null,선택적으로, 버튼 자체에 의미 있는 설명을 부여하려면
Row에semantics를 추가하거나SearchFilterButton에contentDescription파라미터를 노출하는 방법도 있습니다.
51-52: 터치 타깃 권고 크기(48dp) 충족 검토머티리얼 가이드라인의 최소 터치 영역 48dp를 고려하면 36dp는 다소 작습니다. 여유가 있다면 48dp로 상향을 권장합니다.
- .height(36.dp) + .height(48.dp)app/src/main/java/com/texthip/thip/ui/search/viewmodel/SearchBookViewModel.kt (1)
55-57: 라이브 검색 디바운스 300ms로 단축 — 서버 레이트리밋/트래픽 영향 확인 권장UX 측면에서는 반응성이 좋아지지만, 300ms는 트래픽이 증가할 수 있습니다. 서버 레이트리밋/쿼터, 약한 네트워크 환경에서의 요청 중복/취소 처리 영향을 한 번 점검해 주세요.
리뷰 제안:
- 디바운스 값을 상수로 추출해 A/B 조정이 쉽게 만드세요.
- 최소 입력 길이(n≥2 등) 조건을 두면 불필요한 요청을 줄일 수 있습니다.
예시(파일 내 추가/수정 필요):
// 클래스 내부 어딘가에 상수 정의 companion object { private const val LIVE_SEARCH_DEBOUNCE_MS = 300L }- delay(300) + delay(LIVE_SEARCH_DEBOUNCE_MS)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (3)
76-79: 탭 전환 시 내 피드 정보 동시 로드 좋습니다. refresh 경로에서도 동기화 고려 바랍니다탭 인덱스 1 선택 시 fetchMyFeedInfo() 호출은 적절합니다. 추가로 당 탭에서 당겨서 새로고침(refreshMyFeeds) 시에도 최신화가 필요하다면 동일 호출을 포함하는 것을 권장합니다.
예시(참고용, 기존 함수 외부 수정 제안):
// refreshMyFeeds() 마지막 onSuccess 블록 직후 등 적절한 위치에서 호출 fetchMyFeedInfo()
281-287: 에러 상태 처리는 문제 없으나, 성공 시 이전 에러 클리어 고려onFailure에서 error를 채우고 있으므로, 직전 에러가 남지 않도록 성공 시 error = null로 명시 클리어를 권장합니다.
다음과 같이 수정하면 일관성이 좋아집니다:
- updateState { - it.copy( - isLoading = false, - error = exception.message - ) - } + updateState { + it.copy( + isLoading = false, + error = exception.message + ) + }(주: 본 제안은 fetchRecentWriters 실패 분기에서의 패턴 유지에 대한 코멘트입니다. 성공 분기에서 error를 null로 명시하는 패턴도 일관성 측면에서 고려해 주세요.)
291-305: fetchMyFeedInfo 성공 시 이전 에러를 지워주면 UX가 더 안정적입니다현재 성공 시 error를 건드리지 않아 과거 오류 메시지가 남을 수 있습니다. 성공 시 error = null 세팅을 권장합니다.
.onSuccess { data -> updateState { - it.copy(myFeedInfo = data) + it.copy( + myFeedInfo = data, + error = null + ) } }app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt (1)
21-24: nullable 리스트 기본값을 emptyList로 설정해 NPE·분기 감소editTagList, editContentUrls는 기본값을 emptyList()로 두면 사용처의 null 분기를 줄일 수 있습니다(타입은 그대로 nullable 유지 가능).
- val editTagList: List<String>? = null, - val editContentUrls: List<String>? = null + val editTagList: List<String>? = emptyList(), + val editContentUrls: List<String>? = emptyList()app/src/main/java/com/texthip/thip/data/service/FeedService.kt (1)
56-63: 정렬 파라미터는 enum으로 타입 세이프티를 확보하는 것을 권장sort를 String? 대신 제한된 값의 enum으로 노출하면 오입력 방지와 유지보수성이 좋아집니다.
- suspend fun getRelatedBookFeeds( - @Path("isbn") isbn: String, - @Query("sort") sort: String? = null, - @Query("cursor") cursor: String? = null - ): BaseResponse<RelatedBooksResponse> + suspend fun getRelatedBookFeeds( + @Path("isbn") isbn: String, + @Query("sort") sort: RelatedBookSort? = null, + @Query("cursor") cursor: String? = null + ): BaseResponse<RelatedBooksResponse>참고: enum 정의 예시(파일 외부 추가)
enum class RelatedBookSort { RECENT, POPULAR; override fun toString(): String = name.lowercase() // 서버 기대값에 맞춰 변환 }app/src/main/java/com/texthip/thip/ui/feed/screen/FeedWriteScreen.kt (2)
188-203: 수정 모드에서 이미지 추가 버튼 클릭 비활성화 조건 보강주석은 “수정 모드에서는 비활성화”라고 되어 있지만, 실제 클릭 가능 여부는 uiState.canAddMoreImages만으로 제어되고 있습니다. ViewModel에서 addImages가 수정 모드에선 early return 하므로, 버튼은 눌리지만 아무 동작이 없어 UX 혼란이 생길 수 있습니다. isEditMode까지 함께 체크하도록 보강하세요.
적용 제안:
- .let { - if (uiState.canAddMoreImages) it.clickable { - imagePickerLauncher.launch("image/*") - } else it // 클릭 비활성화 - }, + .let { + if (!uiState.isEditMode && uiState.canAddMoreImages) { + it.clickable { imagePickerLauncher.launch("image/*") } + } else it // 수정 모드 또는 제한 수량 도달 시 비활성화 + },
213-235: 기존 이미지 LazyRow에 안정 키(Key) 부여현재 size 기반 인덱스로 items를 구성하고 있어 삭제 시 컴포저블 재활용으로 인한 깜빡임/매칭 오류 가능성이 있습니다. URL을 key로 부여하는 itemsIndexed 형태로 교체하세요.
- items(uiState.existingImageUrls.size) { index -> - Box(modifier = Modifier.size(80.dp)) { - AsyncImage( - model = uiState.existingImageUrls[index], + itemsIndexed( + items = uiState.existingImageUrls, + key = { _, url -> url } + ) { index, url -> + Box(modifier = Modifier.size(80.dp)) { + AsyncImage( + model = url, contentDescription = null, modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop ) IconButton( onClick = { onRemoveExistingImage(index) }, modifier = Modifier .align(Alignment.TopEnd) .size(24.dp) ) { Icon( painter = painterResource(id = R.drawable.ic_x), contentDescription = null, tint = colors.White ) } } }app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (2)
40-40: 사용되지 않는 LocalContext 제거LocalContext를 import하고 context를 선언했으나 사용되지 않습니다. 정리해 빌드 경고를 줄이세요.
-import androidx.compose.ui.platform.LocalContext ... - val context = LocalContext.currentAlso applies to: 84-84
468-477: 신고 토스트 표시 로직(LGTM) + 후속 보완 제안토스트 오버레이와 3초 자동 숨김 처리는 직관적입니다. 다만 실제 신고 API 호출과 성공/실패 분기 처리가 아직 TODO이니, 성공 시에만 토스트를 띄우고 실패 시 에러 안내를 권장합니다.
원하시면 FeedDetailViewModel에 reportFeed(feedId: Int): Boolean 추가 및 호출부까지 구현 PR 보조하겠습니다.
app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt (1)
178-197: 부분 업데이트 요청 모델로의 수정 반영(LGTM) + 로깅 개선 제안UpdateFeedRequest로 부분 업데이트를 지원한 점 좋습니다. 한편, uriToMultipartBodyPart의 예외 처리에서 printStackTrace 대신 일관된 로깅 유틸을 쓰는 것을 권장합니다(운영 환경에서의 가시성 향상).
추가로 createFeed에서 withContext(Dispatchers.IO)로 파일 변환을 감싼 점과 finally에서 임시 파일 정리까지 포함한 리소스 관리가 깔끔합니다.
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (2)
64-77: 카테고리 인덱스 기본값 -1 유지 권장카테고리 로딩 전에도 기본값 0을 주면, 빈 카테고리에서 선택된 것으로 간주되어 이후 파생 계산(availableTags 등)에서 인덱스 오류/의도치 않은 UI 표시가 발생할 수 있습니다. 찾지 못한 경우 -1을 유지하세요.
- }.let { if (it == -1) 0 else it } + }.let { if (it == -1) -1 else it }
119-155: 수동 편집 데이터 주입(setEditData)에서도 동일한 카테고리 인덱스 보정 필요loadFeedForEdit와 동일하게 -1 기본값을 유지하도록 보정하는 것이 안전합니다.
- }.let { if (it == -1) 0 else it } + }.let { if (it == -1) -1 else it }app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt (1)
525-535: 정렬 드롭다운 선택 시 자동 닫힘 처리옵션 선택 후 isFilterDropdownVisible을 false로 바꿔 드롭다운을 닫는 것이 자연스러운 UX입니다.
- onOptionSelected = { option -> - onSortChange(option) - }, + onOptionSelected = { option -> + onSortChange(option) + isFilterDropdownVisible = false + },app/src/main/java/com/texthip/thip/ui/search/viewmodel/BookDetailViewModel.kt (3)
20-35: UI 상태 모델 확장 방향 좋음. 다만 feedItems 계산은 매 접근마다 O(n) 재계산됨
- feedItems를 게터에서 매번 map하는 구조는 recomposition 시 반복 비용이 발생할 수 있습니다.
- 또한 UI에서 사용하는 FeedItem 타입이
ui.mypage.mock.FeedItem에 의존하는 것은 모듈 간 결합을 키웁니다.두 가지 개선 제안:
- 선택 1: UI에 필요한 모델만 노출하도록 BookDetailUiState에서 relatedFeeds 대신 feedItems만 보유하고, 로드/페이지네이션 성공 시에만 map하여 저장.
- 선택 2: FeedItem을 공용(ui-common) 패키지로 이동해 mock 의존 제거.
79-104: 관련 피드 로딩 시작/성공 시 feedError 초기화 필요에러 이후 재시도에서 이전 에러 메시지가 남아 있을 수 있습니다. 시작 시와 성공 시에 feedError를 null로 정리하는 편이 안전합니다.
아래처럼 최소한 시작/성공 시 초기화를 권장합니다.
- updateState { it.copy(isLoadingFeeds = true) } + updateState { it.copy(isLoadingFeeds = true, feedError = null) } @@ - it.copy( + it.copy( relatedFeeds = response?.feeds ?: emptyList(), nextCursor = response?.nextCursor, isLast = response?.isLast ?: true, isLoadingFeeds = false, - currentSort = sort + currentSort = sort, + feedError = null )
79-104: 정렬 변경 시 응답 역전(race) 가능성 안내연속된 정렬 변경이 빠르게 발생하면 이전 요청 응답이 나중에 도착하여 최신 정렬 결과를 덮어쓸 수 있습니다. 간단히 마지막 요청의 sort/cursor 토큰을 상태로 보관해 응답 적용 시 검증하거나, Job을 보관하여 이전 요청을 취소하는 방식(마지막 요청만 유효) 도입을 권장합니다.
원하시면 관련 코드를 추가한 버전을 제안드리겠습니다. 또한 저장/정렬 변경 시 동시 요청 취소가 필요한지 제품 요구사항을 확인해 주세요.
Also applies to: 140-144
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)
87-99: 무한 스크롤 트리거의 키에 아이템 개수 포함 권장현재 remember 키가
canLoadMoreCurrentTab,isLoadingMore만 포함합니다. 리스트 아이템 수 변화만 있을 때 트리거 갱신이 지연될 수 있어 size를 키에 포함하는 것이 안전합니다.- val shouldLoadMore by remember(feedUiState.canLoadMoreCurrentTab, feedUiState.isLoadingMore) { + val shouldLoadMore by remember( + feedUiState.canLoadMoreCurrentTab, + feedUiState.isLoadingMore, + feedUiState.currentTabFeeds.size + ) { derivedStateOf { val layoutInfo = listState.layoutInfo val totalItems = layoutInfo.totalItemsCount val lastVisibleIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0 feedUiState.canLoadMoreCurrentTab && !feedUiState.isLoadingMore && feedUiState.currentTabFeeds.isNotEmpty() && totalItems > 0 && lastVisibleIndex >= totalItems - 3 } }
📜 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 (25)
app/src/main/java/com/texthip/thip/data/model/feed/request/UpdateFeedRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/feed/response/FeedDetailResponse.kt(1 hunks)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/RelatedBooksResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/users/response/UsersMyFollowingsRecentFeedsResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt(2 hunks)app/src/main/java/com/texthip/thip/data/service/FeedService.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/common/topappbar/DefaultTopAppBar.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/common/topappbar/GradationTopAppBar.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/feed/component/MyFeedCard.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt(12 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedWriteScreen.kt(8 hunks)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteUiState.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/SearchNavigation.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/search/component/SearchFilterButton.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt(12 hunks)app/src/main/java/com/texthip/thip/ui/search/viewmodel/BookDetailViewModel.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/search/viewmodel/SearchBookViewModel.kt(1 hunks)app/src/main/res/values/strings.xml(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (2)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (1)
updateState(34-36)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (1)
updateState(28-30)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (2)
app/src/main/java/com/texthip/thip/ui/common/modal/ToastWithDate.kt (1)
ToastWithDate(24-60)app/src/main/java/com/texthip/thip/ui/common/bottomsheet/MenuBottomSheet.kt (1)
MenuBottomSheet(22-59)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (3)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (1)
updateState(59-61)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (1)
updateState(28-30)app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt (1)
updateFeed(179-196)
app/src/main/java/com/texthip/thip/ui/search/viewmodel/BookDetailViewModel.kt (2)
app/src/main/java/com/texthip/thip/ui/group/makeroom/viewmodel/GroupMakeRoomViewModel.kt (1)
updateState(41-43)app/src/main/java/com/texthip/thip/ui/group/room/viewmodel/GroupRoomRecruitViewModel.kt (1)
updateState(28-30)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (4)
app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt (1)
AuthorHeader(34-121)app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt (1)
hexToColor(6-13)app/src/main/java/com/texthip/thip/ui/feed/component/FeedSubscribelistBar.kt (1)
FeedSubscribeBarlist(35-101)app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
ThipTheme(41-69)
🔇 Additional comments (43)
app/src/main/java/com/texthip/thip/ui/common/topappbar/DefaultTopAppBar.kt (2)
8-8: IconButton 도입 import 추가 적절IconButton 사용을 위한 import 추가가 적절합니다. 기존 clickable 대비 접근성(역할/포커스/리플)과 터치 타깃 일관성이 좋아집니다.
36-45: Icon을 IconButton으로 감싼 전환은 접근성·터치 타깃 면에서 바람직
- 클릭 처리를 Icon에서 분리해 IconButton에 위임한 점이 좋습니다.
- 56dp 바 높이에서 IconButton 기본 최소 터치 영역(48dp)을 확보할 수 있고, TalkBack/스크린리더 역할 정보도 자동 부여됩니다.
Also applies to: 57-66
app/src/main/java/com/texthip/thip/ui/common/topappbar/GradationTopAppBar.kt (2)
10-10: IconButton 도입 import 추가 적절IconButton 사용으로 상단바 버튼의 접근성과 상호작용 일관성이 향상됩니다.
46-55: IconButton 래핑 전환 좋습니다
- 좌/우 아이콘 모두 IconButton으로 감싸 정렬(.align)만 상위에 두고 클릭을 버튼에 위임한 점이 깔끔합니다.
- 기존 동작을 유지하면서 머티리얼 상호작용(리플/롤)과 포커스 처리가 표준화됩니다.
Also applies to: 65-74
app/src/main/res/values/strings.xml (1)
342-342: edit_feed_title 추가 적절 — UI 맥락에 잘 맞습니다편집 모드 타이틀 리소스 추가는 명확하고 기존 edit_feed와 역할이 분리되어 있어 읽기 좋습니다. 사용처에서도 혼선이 줄어들 것으로 보입니다.
app/src/main/java/com/texthip/thip/data/model/feed/response/FeedDetailResponse.kt (1)
18-19: bookImageUrl / isPublic 필드 추가 적절 — 역호환도 안전합니다옵셔널로 추가되어 구버전 응답에서도 파싱 실패가 없고, 신규 서버 필드를 자연스럽게 수용합니다. 도메인·UI 확장에도 필요한 정보라 👍
Also applies to: 26-26
app/src/main/java/com/texthip/thip/data/model/feed/response/FeedMineInfoResponse.kt (1)
6-17: 신규 Mine Info 모델 정의 깔끔합니다필수/옵셔널 구분이 적절하고, 컬렉션 타입도 널이 아닌 기본값으로 받아 안전합니다. 서비스/레포지토리 연동에도 문제 없어 보입니다.
app/src/main/java/com/texthip/thip/data/model/feed/response/RelatedBooksResponse.kt (1)
6-11: 모델 구조 전반 LGTM필드와 @SerialName 매핑이 명확하고, UI/레포지토리에서 쓰기 적절한 형태로 잘 정의되어 있습니다.
Also applies to: 13-32
app/src/main/java/com/texthip/thip/ui/search/component/SearchFilterButton.kt (1)
88-99: 오버레이/드롭다운 레이어링 방식 LGTM바깥 터치로 닫기 처리와
AnimatedVisibility전환이 명확하게 분리되어 있고, 드롭다운이 오버레이 위에 렌더링되도록 순서가 잘 잡혀 있습니다.Also applies to: 100-141
app/src/main/java/com/texthip/thip/ui/navigator/navigations/SearchNavigation.kt (2)
49-51: 피드 댓글 화면으로의 내비게이션 연결 LGTM
onFeedClick을 통해navigateToFeedComment(feedId)로 자연스럽게 연결되었습니다. 뒤로가기 흐름도 기존 패턴과 일관됩니다.
9-9: feedId 파라미터(Int) 타입 일치 확인
- 확장 함수:
fun NavHostController.navigateToFeedComment(feedId: Int)- 경로 정의:
@Serializable data class Comment(val feedId: Int)- 사용처(SearchNavigation.kt, FeedNavigation.kt) 모두
feedId: Int를 전달하므로 타입이 일치합니다.app/src/main/java/com/texthip/thip/data/model/users/response/UsersMyFollowingsRecentFeedsResponse.kt (1)
7-8: 역직렬화 키 호환성 확인 필요
DTO 필드명이recentWriters→myFollowingUsers로 변경되었으므로, 실제 백엔드 JSON 키가 일치하는지 꼭 확인하세요. 불일치 시(recentWriters키가 그대로인 경우)@SerialName("recentWriters")를 사용해 매핑을 유지해야 역직렬화 오류를 방지할 수 있습니다.영향받는 파일:
- app/src/main/java/com/texthip/thip/data/model/users/response/UsersMyFollowingsRecentFeedsResponse.kt
예시(diff):
import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName @Serializable data class UsersMyFollowingsRecentFeedsResponse( - val myFollowingUsers: List<RecentWriterList> + @SerialName("recentWriters") + val myFollowingUsers: List<RecentWriterList> )만약 서버도 키를
myFollowingUsers로 변경했다면, 현재 코드로 동작하지만 장기적인 안정성을 위해 명시적인@SerialName("myFollowingUsers")추가를 권장합니다.app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (2)
66-67: ActionBookButton에 onBookClick 정상 연결 확인의도대로 onBookClick이 연결되었습니다. 기본값이 빈 람다라서 기존 호출부도 깨지지 않습니다.
39-41: SavedFeedCard 호출부에서 trailing-lambda 사용 미발견스크립트 검사를 통해
SavedFeedCard(호출부에서 마지막 인자를 람다로 넘기는 패턴을 찾지 못했습니다.
그러나 기존 소비 코드에 trailing-lambda 사용 가능성을 완전히 배제할 수 없으므로, 호환성 보장을 위해 아래 중 하나를 권장드립니다:
- onCommentClick을 마지막 파라미터로 위치를 변경
- 구버전 시그니처(기존 순서) 오버로드 추가
필요하다면 호출부를 수동으로 한번 더 검토해 주십시오.
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (3)
6-6: FeedMineInfoResponse import 추가 적절새 UI 상태 필드 의존성을 정상적으로 도입했습니다.
22-22: UI 상태에 myFeedInfo 추가 OK헤더/소개 영역 등의 표시를 위한 상태 확장으로 적절합니다. 기본값 null로 두어 초기 로딩 전 접근 안정성도 확보되었습니다.
272-279: recent writers 소스 필드 변경 확인 필요data?.recentWriters → data?.myFollowingUsers로 변경되었습니다. 레포/모델이 동일하게 갱신되었는지, 그리고 UI에서 기대 타입(RecentWriterList)의 스키마가 그대로 유지되는지 확인해 주세요.
app/src/main/java/com/texthip/thip/ui/feed/component/MyFeedCard.kt (2)
48-49: ActionBookButton.onClick 연결 확인의도대로 onBookClick이 정상 연결되었습니다.
34-36: trailing-lambda 호출 미발견: 파라미터 순서 변경은 선택사항입니다현재 코드베이스 전반에서
MyFeedCard를 trailing-lambda 형태로 호출한 곳이 없어,onContentClick과onBookClick의 순서를 바꾸지 않아도 문제가 발생하지 않습니다.
필요시 아래와 같이 순서를 바꾸해도 되지만, 필수 변경은 아닙니다.• 파라미터 선언 (선택적 리팩터):
- onContentClick: () -> Unit = {}, - onBookClick: () -> Unit = {} + onBookClick: () -> Unit = {}, + onContentClick: () -> Unit = {}app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt (1)
14-24: Write 라우트 페이로드 확장 적절수정 모드(feedId 및 편집 관련 필드)까지 포괄하도록 확장된 시그니처는 요구사항과 일치합니다. 모든 필드를 기본값으로 두어 직렬화/역직렬화 호환성 측면에서도 안전합니다.
app/src/main/java/com/texthip/thip/data/service/FeedService.kt (3)
9-20: 신규 모델·어노테이션 import 추가 OK새 엔드포인트에 필요한 타입/어노테이션이 정확히 추가되었습니다.
52-55: 내 피드 정보 조회 엔드포인트 추가 적절UI에서 ‘내 피드’ 헤더 정보를 구성하기 위한 데이터 취득 경로로 자연스럽습니다.
80-86: updateFeed 응답 타입이 CreateFeedResponse인 점 확인 필요PATCH 응답이 생성 응답 스키마(CreateFeedResponse)와 동일한지 백엔드 계약을 확인해 주세요. 별도 UpdateFeedResponse가 존재한다면 해당 타입으로 교체하는 것이 명확합니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedWriteScreen.kt (3)
73-81: 생성/수정 흐름 통합(LGTM)createOrUpdateFeed로 라우팅한 결정 적절합니다. 성공/실패 콜백도 UI로 올바르게 전달되고 있습니다.
133-140: 편집/신규 타이틀 전환(LGTM)isEditMode에 따른 타이틀 전환 적용 잘 되었습니다.
261-271: 사진 카운트 계산 변경(LGTM)currentImageCount 사용으로 기존+신규 이미지를 정확히 반영하는 점 좋습니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)
146-167: 배경 블러 처리(LGTM)바텀시트/다이얼로그 표시 시 배경 블러 처리로 포커스가 명확합니다.
app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt (2)
153-159: 내 피드 정보 API 추가(LGTM)핸들러 체인(handleBaseResponse → getOrThrow)과 Result 래핑 일관성 좋습니다.
160-170: 관련 도서 피드 조회 API 추가(LGTM)sort/cursor 파라미터 옵션화 및 Result 래핑 적절합니다.
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (5)
85-98: 편집 모드 상태 세팅(LGTM)기존 이미지 URL, 공개 여부 반전(isPrivate), 편집 모드 플래그/ID 설정까지 일관성 있게 세팅되어 있습니다.
291-296: 수정 모드에서 이미지 추가 차단(LGTM)isEditMode에서 addImages 조기 반환으로 서버 API 제약과 UI 정책을 맞춘 점 좋습니다.
313-319: 기존 이미지 제거 동작(LGTM)인덱스 검증 후 상태 업데이트 방식 깔끔합니다. remainImageUrls는 updateFeed 호출 시 반영되어 서버와 동기화됩니다.
355-363: 생성/수정 라우팅(LGTM)편집 여부/ID에 따라 update vs create 분기 처리 적절합니다.
418-459: 피드 수정 API 연동(LGTM)
- 폼 검증 → 로딩 플래그 → repository.updateFeed 호출 → 성공 시 feedId 반환 → 로딩 해제의 플로우가 견고합니다.
- remainImageUrls로 서버에 유지 이미지를 명시하는 설계도 좋습니다.
app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt (2)
217-236: 헤더 이미지 플레이스홀더/에러 처리(LGTM)fallback/error 리소스 지정으로 빈/에러 이미지 상황 대비가 되어 있습니다.
445-470: 피드 아이템 키 지정(LGTM)itemsIndexed에 key 지정으로 리스트 업데이트 안정성이 확보되어 있습니다.
app/src/main/java/com/texthip/thip/ui/search/viewmodel/BookDetailViewModel.kt (1)
40-49: 상태 갱신 헬퍼 도입 굿updateState 캡슐화로 가독성과 일관성이 좋아졌습니다. 다른 ViewModel들과 컨벤션도 맞습니다.
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (1)
41-43: 도서 상세 화면 네비게이션 연동 LGTMFeedScreen에 onNavigateToBookDetail을 전달하는 연결이 자연스럽고, 확장 메서드 사용도 일관적입니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (5)
67-71: FeedScreen 파라미터 변경 LGTM도서 상세로의 네비게이션 콜백 추가가 자연스럽고 호출부와도 일관됩니다.
215-235: 헤더 바인딩 안전 처리 LGTM
- myFeedInfo의 닉네임/별칭/색상에 대한 안전 기본값 처리와 hexToColor fallback이 적절합니다.
- FeedMineInfoResponse 연동 방향도 자연스럽습니다.
352-354: 전체 피드 카드 onBookClick 연동 LGTM도서 상세 이동 콜백 연결이 명확합니다.
395-398: 프리뷰 시그니처 갱신 LGTM새 파라미터 반영이 정확합니다.
Also applies to: 406-409
286-295: ISBN은 Non-null String으로 선언되어 있습니다
MyFeedResponse.kt의MyFeedItem데이터 클래스에서@SerialName("isbn") val isbn: String으로 정의되어 있어,myFeed.isbn이null일 가능성이 없습니다.
따라서onNavigateToBookDetail(myFeed.isbn)호출은 안전하며 별도의 null 검사나 빌드/런타임 이슈 대응이 필요하지 않습니다.Likely an incorrect or invalid review comment.
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt
Show resolved
Hide resolved
| .padding( | ||
| top = with(density) { (filterButtonPosition.y - 36).toDp() }, | ||
| end = 20.dp // 기존 FilterButton과 동일하게 화면 오른쪽에서 20dp | ||
| ) |
There was a problem hiding this comment.
px/dp 단위 혼용으로 인한 드롭다운 위치 오차 (밀도 의존 버그)
filterButtonPosition.y는 px 단위인데 36을 그대로 빼고 toDp()로 변환하고 있습니다. 이 경우 mdpi 외 단말에서 드롭다운이 어긋납니다. px→dp 변환을 먼저 한 다음 dp끼리 연산해야 합니다.
다음과 같이 수정해 주세요:
- top = with(density) { (filterButtonPosition.y - 36).toDp() },
+ top = with(density) { filterButtonPosition.y.toDp() - 36.dp },추가로, 화면 상단 근처에서 음수 padding이 되지 않도록 보정하는 것도 권장합니다:
- top = with(density) { filterButtonPosition.y.toDp() - 36.dp },
+ top = (with(density) { filterButtonPosition.y.toDp() - 36.dp }).coerceAtLeast(0.dp),🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/search/component/SearchFilterButton.kt
around lines 111 to 114, the code subtracts a raw px constant from
filterButtonPosition.y (which is in px) then calls toDp(), causing
density-dependent misplacement; convert the pixel value to dp first and perform
dp arithmetic (e.g., filterButtonPosition.y.toDp() - 36.dp) and clamp the result
to a non-negative value so padding cannot become negative (use max(0.dp, ...));
keep the end = 20.dp unchanged.
| onSortChange = { sortType -> | ||
| val apiSortType = if (sortType == "인기순") "like" else "latest" | ||
| viewModel.changeSortOrder(isbn, apiSortType) | ||
| }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
정렬 타입 매핑에 하드코딩된 문자열 제거
"인기순" 같은 하드코딩은 다국어/문구 변경 시 깨집니다. UI에서 선택한 option 문자열과 filterOptions를 이용하거나 enum/상수로 매핑하세요.
예시:
- onSortChange = { sortType ->
- val apiSortType = if (sortType == "인기순") "like" else "latest"
- viewModel.changeSortOrder(isbn, apiSortType)
- },
+ onSortChange = { sortType ->
+ val likeLabel = stringResource(R.string.sort_like)
+ val apiSortType = if (sortType == likeLabel) "like" else "latest"
+ viewModel.changeSortOrder(isbn, apiSortType)
+ },📝 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.
| onSortChange = { sortType -> | |
| val apiSortType = if (sortType == "인기순") "like" else "latest" | |
| viewModel.changeSortOrder(isbn, apiSortType) | |
| }, | |
| onSortChange = { sortType -> | |
| val likeLabel = stringResource(R.string.sort_like) | |
| val apiSortType = if (sortType == likeLabel) "like" else "latest" | |
| viewModel.changeSortOrder(isbn, apiSortType) | |
| }, |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt
around lines 102–105, remove the hardcoded comparison to the Korean label "인기순"
and map UI selection to API sort keys via a stable identifier (use the option's
key/value from filterOptions or an enum/constant map) instead: derive
apiSortType from the selected option's key or a lookup map (e.g., enum or
Map<String, String> that maps option keys/ids to "like"/"latest"), then call
viewModel.changeSortOrder(isbn, apiSortType); do not rely on display strings for
logic so localization or text changes won’t break behavior.
| LaunchedEffect(shouldLoadMore) { | ||
| if (shouldLoadMore && uiState?.isLoadingMore == false && uiState.isLast == false) { | ||
| onLoadMore() | ||
| } | ||
| } |
There was a problem hiding this comment.
널 안정성 버그: uiState.isLast 접근 시 NPE/컴파일 오류
uiState가 nullable인데 isLast에는 안전 호출이 빠져 있습니다. 컴파일 오류입니다. 아래처럼 수정하세요.
- if (shouldLoadMore && uiState?.isLoadingMore == false && uiState.isLast == false) {
+ if (shouldLoadMore && uiState?.isLoadingMore == false && uiState?.isLast == false) {
onLoadMore()
}🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt
around lines 155–159, uiState is nullable but the code accesses uiState.isLast
without a safe call causing a compile/NPE risk; fix by guarding uiState before
accessing isLast (either use a null-check early in the LaunchedEffect or use a
safe-call like uiState?.isLast) so the condition becomes fully null-safe (e.g.,
ensure both isLoadingMore and isLast are accessed via uiState? or bail out when
uiState is null) and keep the onLoadMore() invocation unchanged.
app/src/main/java/com/texthip/thip/ui/search/viewmodel/BookDetailViewModel.kt
Show resolved
Hide resolved
| updateState { it.copy(isSaving = true, error = null) } | ||
|
|
||
| bookRepository.saveBook(isbn, type) | ||
| .onSuccess { saveResponse -> | ||
| saveResponse?.let { | ||
| saveResponse?.let { it -> | ||
| // 책 상세 정보의 isSaved 상태 업데이트 | ||
| val updatedBookDetail = _uiState.value.bookDetail?.copy(isSaved = it.isSaved) | ||
| _uiState.value = _uiState.value.copy( | ||
| bookDetail = updatedBookDetail, | ||
| isSaving = false, | ||
| error = null | ||
| ) | ||
| val updatedBookDetail = | ||
| _uiState.value.bookDetail?.copy(isSaved = it.isSaved) | ||
| updateState { | ||
| it.copy( | ||
| bookDetail = updatedBookDetail, | ||
| isSaving = false, | ||
| error = null | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| .onFailure { exception -> | ||
| _uiState.value = _uiState.value.copy( | ||
| isSaving = false, | ||
| error = exception.message ?: stringResourceProvider.getString(R.string.error_book_save_failed) | ||
| ) | ||
| updateState { | ||
| it.copy( | ||
| isSaving = false, | ||
| error = exception.message | ||
| ?: stringResourceProvider.getString(R.string.error_book_save_failed) | ||
| ) | ||
| } |
There was a problem hiding this comment.
saveBook 성공 응답 null 시 isSaving 해제 누락 및 가독성 저하(it 중첩)
- onSuccess에서 응답이 null이면 isSaving=false가 되지 않아 로딩이 지속될 수 있습니다.
let { it -> }와updateState { it -> }의 it 중첩은 가독성을 해칩니다.
아래와 같이 null 응답 처리 및 변수명 개선을 제안합니다.
- bookRepository.saveBook(isbn, type)
- .onSuccess { saveResponse ->
- saveResponse?.let { it ->
- // 책 상세 정보의 isSaved 상태 업데이트
- val updatedBookDetail =
- _uiState.value.bookDetail?.copy(isSaved = it.isSaved)
- updateState {
- it.copy(
- bookDetail = updatedBookDetail,
- isSaving = false,
- error = null
- )
- }
- }
- }
+ bookRepository.saveBook(isbn, type)
+ .onSuccess { response ->
+ if (response == null) {
+ updateState {
+ it.copy(
+ isSaving = false,
+ error = stringResourceProvider.getString(R.string.error_book_save_failed)
+ )
+ }
+ return@onSuccess
+ }
+ val updatedBookDetail = _uiState.value.bookDetail?.copy(isSaved = response.isSaved)
+ updateState { state ->
+ state.copy(
+ bookDetail = updatedBookDetail,
+ isSaving = false,
+ error = null
+ )
+ }
+ }🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/search/viewmodel/BookDetailViewModel.kt
around lines 148–172, the onSuccess branch fails to clear isSaving when the
saveResponse is null and the nested use of `it` (let { it -> } and updateState {
it -> }) reduces readability; fix by treating null saveResponse explicitly: call
updateState to set isSaving = false and error = null when saveResponse is null,
and for the non-null case compute an updatedBookDetail then call updateState to
set bookDetail and isSaving = false; also rename lambda parameters to avoid
shadowing (e.g., use saveResponse or result and prevState) so there are no
nested `it` usages.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
app/src/main/java/com/texthip/thip/ui/search/viewmodel/BookDetailViewModel.kt (2)
119-127: 이전 스냅샷(currentState) 사용으로 최신 상태 덮어쓰기 가능성updateState 클로저 내부에서 currentState.relatedFeeds를 사용하면, 동시 호출/지연으로 최신 state.relatedFeeds를 덮어쓸 수 있습니다. 클로저 인자(state)를 사용하세요.
- updateState { state -> - state.copy( - relatedFeeds = currentState.relatedFeeds + (response?.feeds - ?: emptyList()), - nextCursor = response?.nextCursor, - isLast = response?.isLast ?: true, - isLoadingMore = false - ) - } + updateState { state -> + state.copy( + relatedFeeds = state.relatedFeeds + (response?.feeds ?: emptyList()), + nextCursor = response?.nextCursor, + isLast = response?.isLast ?: true, + isLoadingMore = false, + feedError = null + ) + }
151-163: saveBook 응답 null 시 isSaving 해제 누락(무한 로딩) 및 it 중첩 가독성 저하응답이 null이면 onSuccess 블록이 아무 것도 하지 않아 isSaving=false가 되지 않습니다. 또한 it 중첩은 가독성을 해칩니다. null 처리 및 변수명을 명확히 해주세요.
- .onSuccess { saveResponse -> - saveResponse?.let { it -> - // 책 상세 정보의 isSaved 상태 업데이트 - val updatedBookDetail = - _uiState.value.bookDetail?.copy(isSaved = it.isSaved) - updateState { - it.copy( - bookDetail = updatedBookDetail, - isSaving = false, - error = null - ) - } - } - } + .onSuccess { response -> + if (response == null) { + updateState { state -> + state.copy( + isSaving = false, + error = stringResourceProvider.getString(R.string.error_book_save_failed) + ) + } + return@onSuccess + } + // 책 상세 정보의 isSaved 상태 업데이트 + val updatedBookDetail = _uiState.value.bookDetail?.copy(isSaved = response.isSaved) + updateState { state -> + state.copy( + bookDetail = updatedBookDetail, + isSaving = false, + error = null + ) + } + }
🧹 Nitpick comments (11)
app/src/main/java/com/texthip/thip/ui/search/viewmodel/BookDetailViewModel.kt (9)
30-31: 매직 스트링 제거: 정렬 값 상수화 권장"like" 문자열 리터럴은 오타/변경에 취약합니다. 파일 상단 또는 파일 스코프에 정렬 상수를 두고 사용하도록 변경해 주세요.
다음과 같이 기본 정렬을 상수로 분리하고 사용을 교체하는 것을 제안합니다.
- val currentSort: String = "like", + val currentSort: String = DEFAULT_SORT,추가 코드(파일 내 다른 위치에 추가):
// 파일 상단(패키지 선언 아래) 또는 ViewModel 하단에 배치 private const val DEFAULT_SORT = "like"
47-49: StateFlow 원자적 갱신 확보: update 확장 함수 사용동시 갱신 가능성이 있는 경우를 대비해 MutableStateFlow.update 확장 함수를 사용하는 것이 안전합니다. 현재 구현도 메인 스레드 사용 시 문제 없겠지만 update를 사용하면 더 견고합니다.
- private fun updateState(update: (BookDetailUiState) -> BookDetailUiState) { - _uiState.value = update(_uiState.value) - } + private fun updateState(update: (BookDetailUiState) -> BookDetailUiState) { + _uiState.update(update) + }추가 import:
import kotlinx.coroutines.flow.update
65-66: 기본 정렬값도 상수 사용으로 통일loadRelatedFeeds 호출 시에도 상수를 사용해 일관성 유지하세요.
- loadRelatedFeeds(isbn, "like") + loadRelatedFeeds(isbn, DEFAULT_SORT)
79-80: 함수 기본인자도 상수로 교체서명 기본값의 매직 스트링 제거를 권장합니다.
- fun loadRelatedFeeds(isbn: String, sort: String = "like") { + fun loadRelatedFeeds(isbn: String, sort: String = DEFAULT_SORT) {
81-82: 관련 피드 로딩 시 에러 상태 초기화 및 실패 기본 메시지 제공
- 로딩 시작 시 이전 feedError를 null로 초기화해 잔존 에러 표시를 방지하세요.
- 실패 시 exception.message가 null인 경우를 대비해 사용자 친화적인 기본 에러 문자열을 설정하세요.
- updateState { it.copy(isLoadingFeeds = true) } + updateState { it.copy(isLoadingFeeds = true, feedError = null) }실패 처리의 예시(리소스 키는 프로젝트에서 사용 중인 공통 에러 문자열로 교체 필요):
// 제안: 실패 시 updateState { it.copy( isLoadingFeeds = false, feedError = exception.message ?: stringResourceProvider.getString(R.string.error_common_load_failed) ) }필요 시 strings.xml에 error_common_load_failed(예: "목록을 불러오지 못했습니다.") 추가를 권장합니다.
Also applies to: 95-101
85-93: 성공 시 feedError를 null로 리셋이전 실패로 설정된 feedError가 성공 후에도 남지 않도록 성공 브랜치에서 명시적으로 feedError를 null로 설정하세요.
updateState { it.copy( relatedFeeds = response?.feeds ?: emptyList(), nextCursor = response?.nextCursor, isLast = response?.isLast ?: true, isLoadingFeeds = false, - currentSort = sort + currentSort = sort, + feedError = null ) }
106-117: 페이지네이션 중복 호출 레이스 윈도우 축소isLoadingMore 플래그를 코루틴 런치 이전에 즉시 올리지 않아, 빠른 연속 호출 시 두 번째 호출도 진입할 수 있는 작은 레이스 윈도우가 있습니다. 플래그를 먼저 올리고 필요한 인자(sort/cursor)를 스냅샷으로 캡처한 후 네트워크를 시작하세요.
fun loadMoreFeeds(isbn: String) { - val currentState = _uiState.value - if (currentState.isLoadingMore || currentState.isLast || currentState.nextCursor == null) return - - viewModelScope.launch { - updateState { it.copy(isLoadingMore = true) } - - feedRepository.getRelatedBookFeeds( - isbn, - currentState.currentSort, - currentState.nextCursor - ) + val snapshot = _uiState.value + if (snapshot.isLoadingMore || snapshot.isLast || snapshot.nextCursor == null) return + + // 중복 호출 방지: 플래그를 즉시 올립니다. + updateState { it.copy(isLoadingMore = true) } + + val sort = snapshot.currentSort + val cursor = snapshot.nextCursor + viewModelScope.launch { + feedRepository.getRelatedBookFeeds( + isbn, + sort, + cursor + )
130-135: 페이지네이션 실패 시 기본 에러 메시지 제공exception.message가 null일 수 있습니다. 사용자 메시지 일관성을 위해 feedError에 기본 문자열 리소스를 제공하세요.
예시(리소스 키는 프로젝트 기준에 맞게 조정):
updateState { state -> state.copy( isLoadingMore = false, feedError = exception.message ?: stringResourceProvider.getString(R.string.error_common_load_failed) ) }
178-195: toFeedItem 매핑 필드명 확인 요청FeedItem의 생성자 파라미터명이 authName인지 authorName인지 확인 부탁드립니다. 오타일 경우 컴파일 에러 또는 UI 불일치가 발생할 수 있습니다. 또한 null 가능 필드가 있다면 기본값 처리(빈 문자열, 0 등)도 고려하세요.
필요하시면 FeedItem 정의에 맞춘 안전한 매핑(널 병합 연산자 활용)으로 패치 제안 드리겠습니다.
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (2)
60-87: 조건 분기 가독성 개선 제안(when + 지역 변수 도입)동일
route필드 접근을 반복하는 if-else 체인을when과 지역 변수로 정리하면 가독성이 좋아집니다. 동작 변화 없이 리팩터 가능합니다.- LaunchedEffect(route) { - if ( - route.feedId != null && - route.editContentBody != null && - route.isbn != null && - route.bookTitle != null && - route.bookAuthor != null - ) { - viewModel.setEditData( - feedId = route.feedId, - isbn = route.isbn, - bookTitle = route.bookTitle, - bookAuthor = route.bookAuthor, - bookImageUrl = route.bookImageUrl ?: "", - contentBody = route.editContentBody, - isPublic = route.editIsPublic ?: true, - tagList = route.editTagList ?: emptyList() - ) - } else if (route.feedId != null) { - // 수정 모드: 기존 방식 (API 호출) - viewModel.loadFeedForEdit(route.feedId) - } else if (route.isbn != null && - route.bookTitle != null && - route.bookAuthor != null && - route.bookImageUrl != null && - route.recordContent != null - ) { - // 새 글 작성 모드: 기록장에서 온 데이터 설정 - viewModel.setPinnedRecord( - isbn = route.isbn, - bookTitle = route.bookTitle, - bookAuthor = route.bookAuthor, - bookImageUrl = route.bookImageUrl, - recordContent = route.recordContent - ) - } - } + LaunchedEffect(route) { + val feedId = route.feedId + val editBody = route.editContentBody + val isbn = route.isbn + val title = route.bookTitle + val author = route.bookAuthor + val imageUrl = route.bookImageUrl + val record = route.recordContent + + when { + feedId != null && editBody != null && isbn != null && title != null && author != null -> { + viewModel.setEditData( + feedId = feedId, + isbn = isbn, + bookTitle = title, + bookAuthor = author, + bookImageUrl = imageUrl ?: "", + contentBody = editBody, + isPublic = route.editIsPublic ?: true, + tagList = route.editTagList ?: emptyList() + ) + } + feedId != null -> { + // 수정 모드: 기존 방식 (API 호출) + viewModel.loadFeedForEdit(feedId) + } + isbn != null && title != null && author != null && imageUrl != null && record != null -> { + // 새 글 작성 모드: 기록장에서 온 데이터 설정 + viewModel.setPinnedRecord( + isbn = isbn, + bookTitle = title, + bookAuthor = author, + bookImageUrl = imageUrl, + recordContent = record + ) + } + } + }
118-121: Comment 라우트 파싱은 toRoute로 일관화 추천상단에서
toRoute<FeedRoutes.Write>()를 사용하듯, 이 부분도toRoute<FeedRoutes.Comment>()로 통일하면 보일러플레이트가 줄고 타입 안전성이 올라갑니다.- val route = backStackEntry.arguments?.let { - FeedRoutes.Comment(it.getInt("feedId")) - } ?: return@composable + val route = backStackEntry.toRoute<FeedRoutes.Comment>()
📜 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/navigator/navigations/FeedNavigation.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt(12 hunks)app/src/main/java/com/texthip/thip/ui/search/viewmodel/BookDetailViewModel.kt(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt
🔇 Additional comments (5)
app/src/main/java/com/texthip/thip/ui/search/viewmodel/BookDetailViewModel.kt (1)
37-45: 전반적 구조와 DI, 상태 관리 패턴은 깔끔합니다Hilt 주입, UI 상태 단일 소스(MutableStateFlow)와 updateState 헬퍼로의 일관된 갱신 패턴 모두 좋습니다. 이후 제안들은 세부 개선사항입니다.
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (4)
18-18: 신규 BookDetail 내비게이션 임포트 — OK탐색 익스텐션 임포트가 적절히 추가되었습니다.
61-77: 수정 모드 널 안전성 강화(!! 제거) — 이전 리뷰 피드백 반영 확인
isbn/bookTitle/bookAuthor까지 널 체크를 조건에 포함해!!를 제거한 점 좋습니다. 런타임 크래시 리스크가 사라졌습니다.
101-106: 피드 생성 결과 전달 및 복귀 플로우 — OK
getBackStackEntry(MainTabRoutes.Feed)에 결과를 저장 후popBackStack으로 자연스럽게 복귀하는 패턴이 일관되고 안전합니다.
127-129: 댓글 화면 → 수정 화면 내비게이션 연결 — OK
FeedRoutes.Write(feedId = feedId)로 진입하면 Write 화면의 분기 로직과 잘 맞습니다.
| onNavigateToBookDetail = { isbn -> | ||
| navController.navigateToBookDetail(isbn) | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
FeedScreen에 도서 상세 진입 콜백 연결 — OK. 다른 호출부/시그니처 검증 권장
FeedScreen에 onNavigateToBookDetail 콜백을 안전하게 연결했습니다. 나머지 호출부/정의와 시그니처가 일치하는지 빠르게 점검해 주세요.
다음 스크립트로 FeedScreen/FeedCommentScreen 시그니처와 호출부를 확인할 수 있습니다.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== FeedScreen 시그니처에 onNavigateToBookDetail 포함 여부 =="
rg -nP --type=kt -C3 '@Composable\s+fun\s+FeedScreen\s*\(' | rg -n 'onNavigateToBookDetail' || true
echo
echo "== FeedScreen 호출부들이 onNavigateToBookDetail을 전달하는지 =="
rg -nP --type=kt -C2 '\bFeedScreen\s*\(' | rg -n 'onNavigateToBookDetail' || true
echo
echo "== FeedCommentScreen 시그니처에 onNavigateToFeedEdit 포함 여부 =="
rg -nP --type=kt -C3 '@Composable\s+fun\s+FeedCommentScreen\s*\(' | rg -n 'onNavigateToFeedEdit' || trueLength of output: 751
다음과 같이 --type=kt 대신 -g '*.kt' 패턴으로 .kt 파일을 대상 검색하도록 스크립트를 수정했습니다. 실행 후 결과를 공유해 주세요.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== FeedScreen 시그니처에 onNavigateToBookDetail 포함 여부 =="
rg -nP -g '*.kt' -C3 '@Composable\s+fun\s+FeedScreen\s*\(' | rg -n 'onNavigateToBookDetail' || true
echo
echo "== FeedScreen 호출부들이 onNavigateToBookDetail을 전달하는지 =="
rg -nP -g '*.kt' -C2 '\bFeedScreen\s*\(' | rg -n 'onNavigateToBookDetail' || true
echo
echo "== FeedCommentScreen 시그니처에 onNavigateToFeedEdit 포함 여부 =="
rg -nP -g '*.kt' -C3 '@Composable\s+fun\s+FeedCommentScreen\s*\(' | rg -n 'onNavigateToFeedEdit' || trueLength of output: 887
FeedScreen/FeedCommentScreen 시그니처에 콜백 파라미터 누락 — 시그니처 수정 필요
FeedNavigation.kt에서 아래 콜백을 전달하고 있지만, 실제 컴포저블 함수 시그니처에는 포함되어 있지 않아 컴파일 오류가 발생합니다. 해당 시그니처를 수정해 주세요.
-
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt
기존@Composable fun FeedScreen( /* … */ ) { /* … */ }
수정
@Composable fun FeedScreen( /* … */, onNavigateToBookDetail: (String) -> Unit, ) { /* … */ }
-
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt
기존@Composable fun FeedCommentScreen( /* … */ ) { /* … */ }
수정
@Composable fun FeedCommentScreen( /* … */, onNavigateToFeedEdit: (String) -> Unit, ) { /* … */ }
수정 후 다시 컴파일 및 네비게이션 흐름이 정상 동작하는지 확인 부탁드립니다.
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt
around lines 41-43, FeedNavigation passes callbacks that are missing from the
composable signatures, causing compile errors; update the composable
declarations to accept the callbacks: modify FeedScreen in
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt to add the
parameter onNavigateToBookDetail: (String) -> Unit, and modify FeedCommentScreen
in app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt to add
the parameter onNavigateToFeedEdit: (String) -> Unit, then propagate these
parameters through any intermediate calls/usages inside those files and update
any call sites to supply the callbacks, rebuild and verify navigation flows
compile and work correctly.
➕ 이슈 링크
🔎 작업 내용
📸 스크린샷
😢 해결하지 못한 과제
[] 방 검색은 아직 구현하지 못했습니다
📢 리뷰어들에게
Summary by CodeRabbit
New Features
Improvements