Skip to content

[Feat] 피드 나머지 api 연동#105

Merged
JJUYAAA merged 27 commits intoTHIP-TextHip:developfrom
JJUYAAA:feat/#96-feeds_api
Aug 19, 2025
Merged

[Feat] 피드 나머지 api 연동#105
JJUYAAA merged 27 commits intoTHIP-TextHip:developfrom
JJUYAAA:feat/#96-feeds_api

Conversation

@JJUYAAA
Copy link
Member

@JJUYAAA JJUYAAA commented Aug 18, 2025

➕ 이슈 링크


🔎 작업 내용

  • 피드 삭제 api 연동
  • 피드 좋아요 상태 변경 api 연동
  • 피드 저장 상태 변경 api 연동
  • 피드 자세히 보기 화면 피드 하단 아이콘 적용

📸 스크린샷

image image image
bandicam.2025-08-19.00-51-46-874.mp4

😢 해결하지 못한 과제

없습니다

📢 리뷰어들에게

피드 스크린에서 사용자 찾기 클릭 로직도 수정해서 추가하겠습니다!
swagger를 보니 feedId가 0으로 되어있어 변경했습니다.


Summary by CodeRabbit

  • New Features

    • 피드 좋아요·저장 토글 기능 추가 및 즉시 반영(낙관적 업데이트) 지원
    • 피드 삭제 기능 추가(댓글 화면에서 삭제 가능, 이전 화면에 삭제 결과 전달)
    • 액션 버튼에 잠금(락) 아이콘 옵션 추가
  • Improvements

    • 피드 ID를 Long으로 통일하여 대규모 ID 호환성 개선
    • 목록·댓글·작성·검색 등에서 좋아요/저장 액션을 UI에 직접 연결해 반응성 향상
    • 네비게이션 연동 강화로 삭제/뒤로 이동 흐름 안정화

@coderabbitai
Copy link

coderabbitai bot commented Aug 18, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

피드 삭제·좋아요·저장 API 연동을 위해 요청/응답 모델, 서비스·레포지토리 엔드포인트 및 유스케이스·뷰모델을 추가하고 UI/네비게이션을 Long ID로 일관되게 변경했다. 낙관적 UI 업데이트와 삭제 후 뒤로가기 상태 전파가 포함된다.

Changes

Cohort / File(s) Summary
Feed request/response models
app/.../data/model/feed/request/FeedLikeRequest.kt, app/.../data/model/feed/request/FeedSaveRequest.kt, app/.../data/model/feed/response/FeedLikeResponse.kt, app/.../data/model/feed/response/FeedSaveResponse.kt
좋아요/저장 요청·응답용 직렬화 데이터 클래스 추가 (요청: type: Boolean, 응답: feedId: Long, isLiked/isSaved: Boolean).
Service layer
app/.../data/service/FeedService.kt
DELETE /feeds/{feedId}, POST /feeds/{feedId}/likes, POST /feeds/{feedId}/saved 엔드포인트 추가; getFeedDetail/updateFeed의 feedId 타입 Int→Long 변경.
Repository layer
app/.../data/repository/FeedRepository.kt
getFeedDetail/updateFeed의 feedId 타입 Long으로 변경 및 deleteFeed/changeFeedLike/changeFeedSave 메서드 추가(새 모델 사용, runCatching 패턴 유지).
Use cases
app/.../ui/feed/usecase/ChangeFeedLikeUseCase.kt, .../ChangeFeedSaveUseCase.kt, .../DeleteFeedUseCase.kt
좋아요/저장/삭제 유스케이스 추가(FeedRepository 위임).
ViewModels: Feed
app/.../ui/feed/viewmodel/FeedViewModel.kt, .../FeedOthersViewModel.kt, .../FeedDetailViewModel.kt
Long ID 적용, changeFeedLike/changeFeedSave 낙관적 업데이트 추가, 삭제 흐름(deleteFeed) 및 isDeleting/deleteSuccess 상태 추가.
ViewModels: Write
app/.../ui/feed/viewmodel/FeedWriteViewModel.kt, .../FeedWriteUiState.kt
편집/생성 콜백과 편집 feedId 타입 Int→Long으로 변경(연쇄 호출도 Long 사용).
UI screens/components
app/.../ui/feed/screen/FeedScreen.kt, .../FeedCommentScreen.kt, .../FeedOthersScreen.kt, .../component/MyFeedCard.kt, .../mypage/component/SavedFeedCard.kt, .../search/screen/SearchBookDetailScreen.kt
화면 파라미터·콜백의 feedId를 Long으로 통일, 좋아요/저장 클릭을 ViewModel에 연결, 프리뷰 더미 ID를 Long으로 변경.
Navigation
app/.../ui/navigator/routes/FeedRoutes.kt, .../navigations/FeedNavigation.kt, .../extensions/FeedNavigationExtensions.kt
Comment 라우트의 feedId를 Long으로 변경, savedStateHandle get/remove를 Long으로 처리; FeedScreen/FeedCommentScreen에 navController 전달.
Mocks/MyPage
app/.../ui/mypage/mock/FeedItem.kt, .../viewmodel/SavedFeedViewModel.kt
FeedItem.id 타입을 Int→Long으로 변경, toggleBookmark/toggleLike 파라미터를 Long으로 변경.
Search ViewModel mapping
app/.../ui/search/viewmodel/BookDetailViewModel.kt
RelatedFeedItem → FeedItem 변환에서 id를 toLong()으로 매핑.
Common button
app/.../ui/common/buttons/ActionBarButton.kt
저장 아이콘 대체용 isLockIcon: Boolean = false 파라미터 추가 및 동작 분기 추가(기본 호환성 유지).

Sequence Diagram(s)

sequenceDiagram
  participant UI as UI (FeedScreen/Others)
  participant VM as FeedViewModel
  participant UC as ChangeFeedLikeUseCase
  participant Repo as FeedRepository
  participant Svc as FeedService
  participant API as Backend API

  UI->>VM: onLikeClick(feedId)
  VM->>VM: 낙관적 토글(isLiked/likeCount)
  VM->>UC: invoke(feedId, newLikeStatus)
  UC->>Repo: changeFeedLike(feedId, newLikeStatus)
  Repo->>Svc: POST /feeds/{feedId}/likes {type: Boolean}
  Svc->>API: Request
  API-->>Svc: BaseResponse<FeedLikeResponse>
  Svc-->>Repo: FeedLikeResponse
  Repo-->>UC: Result
  UC-->>VM: Result
  alt 실패
    VM->>VM: UI 상태 롤백
  end
Loading
sequenceDiagram
  participant UI as UI (FeedScreen)
  participant VM as FeedViewModel
  participant UC as ChangeFeedSaveUseCase
  participant Repo as FeedRepository
  participant Svc as FeedService
  participant API as Backend API

  UI->>VM: onBookmarkClick(feedId)
  VM->>VM: 낙관적 토글(isSaved)
  VM->>UC: invoke(feedId, newSaveStatus)
  UC->>Repo: changeFeedSave(feedId, newSaveStatus)
  Repo->>Svc: POST /feeds/{feedId}/saved {type: Boolean}
  Svc->>API: Request
  API-->>Svc: BaseResponse<FeedSaveResponse>
  Svc-->>Repo: FeedSaveResponse
  Repo-->>UC: Result
  UC-->>VM: Result
  alt 실패
    VM->>VM: UI 상태 롤백
  end
Loading
sequenceDiagram
  participant UI as UI (FeedCommentScreen)
  participant VM as FeedDetailViewModel
  participant UC as DeleteFeedUseCase
  participant Repo as FeedRepository
  participant Svc as FeedService
  participant API as Backend API

  UI->>VM: confirmDelete(feedId)
  VM->>VM: isDeleting=true, error=null
  VM->>UC: invoke(feedId)
  UC->>Repo: deleteFeed(feedId)
  Repo->>Svc: DELETE /feeds/{feedId}
  Svc->>API: Request
  API-->>Svc: BaseResponse<String>
  Svc-->>Repo: Result
  Repo-->>UC: Result
  UC-->>VM: Result
  alt 성공
    VM->>VM: isDeleting=false, deleteSuccess=true
  else 실패
    VM->>VM: isDeleting=false, error=message
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
피드 삭제 API 연동 (#96)
피드 저장 상태 변경 API 연동 (#96)
피드 좋아요 상태 변경 API 연동 (#96)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
feedId 타입 전면 변경 (예: app/src/main/java/com/texthip/thip/data/service/FeedService.kt, 여러 뷰/네비게이션 파일) 이슈 #96은 API 연동(삭제/저장/좋아요)을 요구. ID 타입 전면 변경은 요구사항에 명시되지 않았으나 일관성 목적의 범위 확장임.
UI 프리뷰/목업 ID 변경 (예: app/src/main/java/com/texthip/thip/ui/mypage/mock/FeedItem.kt, SavedFeedCard.kt) 프리뷰/목업 정합성 조정으로, 이슈의 API 연동 목표와 직접적 관련 없음.

Possibly related issues

Possibly related PRs

Suggested labels

✨ feat, 🐹 주연, ✅ OK merge

Suggested reviewers

  • rbqks529
  • Nico1eKim

Poem

깡총깡총 코드 밟고 온 토끼예요 🐇
좋아요 찰칵, 저장도 뽀얗게 찰칵—
삭제는 살며시, 뒤로 한 걸음.
Long으로 길게, ID를 늘렸지요.
당근 하나로 머지 축하해요 🥕✨

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.


📜 Recent 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 4a4786d and 022e0c4.

📒 Files selected for processing (5)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (6 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (10 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (4 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🔭 Outside diff range comments (4)
app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt (1)

14-14: Write.feedId 타입 통일 및 관련 호출부 수정 필요

FeedRoutes.WriteInt? 로 남아 있어, 다른 라우트(Comment, Others) 및 API/내비게이션 호출부에서는 모두 Long 으로 사용 중입니다. 불필요한 toLong()/toInt() 변환을 제거하고, 라우트 직·역직렬화에서도 Long? 타입을 일관되게 써야 합니다.

수정이 필요한 위치:

  • app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt: Write.feedId 타입 변경
  • app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt:
    • route.feedId.toLong() 호출 제거 또는 route.feedId 직접 사용 (69, 80번 줄)
  • app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt:
    • navigateToFeedWrite 에서 feedId?.toInt() 변환 제거 및 Long? 처리 확인
  • (테스트) 라우트 직·역직렬화 포맷과 SavedStateHandle 키("feedId")도 Long 기반으로 올바르게 동작하는지 검증 필요

제안 diff:

--- a/app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt
+++ b/app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt
@@ -12,7 +12,7 @@ sealed class FeedRoutes : Routes() {

     @Serializable
     data class Write(
-        val feedId: Int? = null,  // 수정 모드용 피드 ID
+        val feedId: Long? = null, // 수정 모드용 피드 ID
         val isbn: String? = null,
         val bookTitle: String? = null,
         //...
app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt (1)

155-158: 컴파일 오류 가능성: nullable uiState를 안전 호출 없이 참조하고 있습니다

조건식에서 uiState?.isLoadingMore == false 다음에 !uiState.isLast를 사용하고 있어 NPE/컴파일 오류가 납니다. 아래처럼 null-safe하게 변경해 주세요.

-        if (shouldLoadMore && uiState?.isLoadingMore == false && !uiState.isLast) {
+        if (shouldLoadMore && uiState?.isLoadingMore == false && (uiState?.isLast == false)) {
             onLoadMore()
         }

대안:

  • 가독성을 위해 val isLast = uiState?.isLast ?: true 로컬 변수를 두고 !isLast를 사용하는 것도 좋습니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)

66-71: onNavigateToFeedEdit 파라미터가 Int로 남아 Long 전환과 불일치 (컴파일 실패 가능)

화면 시그니처가 feedId: Long으로 변경되었는데, onNavigateToFeedEdit는 (Int) -> Unit을 그대로 유지하고 있습니다. 아래 호출부에서 feedDetail.feedId(아마 Long)를 그대로 넘기므로 현재 상태로는 타입 불일치로 컴파일 에러가 날 확률이 높습니다. 전체 PR 목표(피드 ID Long 전환)와도 불일치합니다. Long으로 맞춰주세요.

아래 패치로 정합성 맞출 수 있습니다:

 fun FeedCommentScreen(
     modifier: Modifier = Modifier,
-    feedId: Long,
+    feedId: Long,
     onNavigateBack: () -> Unit = {},
-    onNavigateToFeedEdit: (Int) -> Unit = {},
+    onNavigateToFeedEdit: (Long) -> Unit = {},
     feedDetailViewModel: FeedDetailViewModel = hiltViewModel(),
     commentsViewModel: CommentsViewModel = hiltViewModel()
 )
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (1)

69-81: FeedRoutes.Write의 feedId 타입을 Long?으로 통일하고 toLong() 호출 제거

현재 FeedRoutes.Comment에서는 feedId: Long으로 정의되어 있지만, FeedRoutes.WritefeedIdInt?로 되어 있어 FeedNavigation.kt에서 .toLong() 변환이 중복 사용됩니다. 일관성을 위해 다음을 반영해주세요.

수정할 위치:

  • app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt
    • data class Write의 선언을
      data class Write(
          val feedId: Int? = null,  // 수정 모드용 피드 ID
          …
      )
      에서
      data class Write(
          val feedId: Long? = null,  // 수정 모드용 피드 ID
          …
      )
      로 변경
  • app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt
    • route.feedId.toLong() 호출을 route.feedId 로 대체 (null 검사는 기존 분기에서 이미 처리됨)
🧹 Nitpick comments (23)
app/src/main/java/com/texthip/thip/data/model/feed/response/FeedLikeResponse.kt (1)

1-10: 응답 모델 정의 적절함. 낙관적 업데이트 동기화 포인트만 확인해 주세요

  • 계약 상 필드명이 "feedId", "isLiked"라면 현재 정의로 충분합니다.
  • 낙관적 좋아요 토글을 쓰는 경우, 서버가 likeCount(혹은 기타 동기화용 메타)도 내려준다면 모델에 포함해 최종 상태를 재동기화하는 걸 고려해 주세요. 미반환이면 현재 구조로도 무방합니다.
app/src/main/java/com/texthip/thip/data/model/feed/request/FeedSaveRequest.kt (1)

1-9: 요청 필드 의미를 명시하는 KDoc 추가 제안

Boolean 타입의 "type"은 의미가 모호할 수 있습니다. 계약 의미를 KDoc으로 남겨 오해를 줄이는 것을 권장합니다.

다음 주석을 추가해 주세요:

 package com.texthip.thip.data.model.feed.request

 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable

+/**
+ * 서버 계약:
+ * - type = true  -> 저장 ON
+ * - type = false -> 저장 OFF
+ */
 @Serializable
 data class FeedSaveRequest(
     @SerialName("type") val type: Boolean
 )
app/src/main/java/com/texthip/thip/data/model/feed/request/FeedLikeRequest.kt (1)

1-9: 요청 필드 의미를 명시하는 KDoc 추가 제안

FeedSaveRequest와 동일하게 "type" 의미를 문서화하면 가독성이 좋아집니다.

적용 예시는 다음과 같습니다:

 package com.texthip.thip.data.model.feed.request

 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable

+/**
+ * 서버 계약:
+ * - type = true  -> 좋아요 ON
+ * - type = false -> 좋아요 OFF
+ */
 @Serializable
 data class FeedLikeRequest(
     @SerialName("type") val type: Boolean
 )
app/src/main/java/com/texthip/thip/data/service/FeedService.kt (2)

85-90: DELETE 응답 타입을 의미적으로 단순화하는 것을 고려해주세요

서버가 본문 없이 성공 상태만 반환(예: 204 No Content)이면, BaseResponse<String> 대신 BaseResponse<Unit> 사용이 더 명확합니다. 서버 계약이 문자열 메시지를 항상 내려준다면 현행 유지하세요.

다음과 같이 변경 가능:

-    ): BaseResponse<String>
+    ): BaseResponse<Unit>

서버 스펙 상 응답 바디가 없는지(204/200+빈바디) 또는 메시지 문자열을 반환하는지 확인 부탁드립니다.


91-97: 좋아요 상태 변경: 멱등성(HTTP 메서드) 검토 제안

요청 바디에 최종 상태(Boolean)를 전달하는 형태라면 POST보다 PUT/PATCH가 의미상 더 적합합니다. 백엔드 계약이 고정이라면 유지하시되, API 설계 합의 여지가 있다면 다음처럼 고려해볼 수 있습니다.

-    @POST("feeds/{feedId}/likes")
+    @PATCH("feeds/{feedId}/likes")
     suspend fun changeFeedLike(
         @Path("feedId") feedId: Long,
         @Body request: FeedLikeRequest
     ): BaseResponse<FeedLikeResponse>
app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (1)

93-101: 접근성(a11y): 이미지 contentDescription 고려

피드 이미지가 정보 전달에 의미가 있다면 contentDescription 제공을 검토해 주세요. 순수 장식 용도라면 null 유지가 맞습니다.

app/src/main/java/com/texthip/thip/ui/feed/component/MyFeedCard.kt (1)

75-83: 접근성(a11y) 개선 제안

피드 이미지가 의미 있는 콘텐츠라면 contentDescription 제공을 고려해 주세요. 장식 이미지라면 현재처럼 null 유지가 적절합니다.

app/src/main/java/com/texthip/thip/ui/feed/screen/FeedWriteScreen.kt (1)

78-79: 에러 핸들링 누락 — 최소 로깅 또는 사용자 피드백 추가 권장

onError 블록이 비어 있어 실패 시 원인 파악/사용자 피드백이 어렵습니다. 최소한 로깅을 넣고, 가능하면 Snackbar/Toast 등 사용자 피드백을 추가해 주세요.

아래처럼 간단 로깅을 추가해 두면 초기에 도움이 됩니다:

-                onError = { errorMessage ->
-                }
+                onError = { errorMessage ->
+                    android.util.Log.e("FeedWriteScreen", "Failed to create/update feed: $errorMessage")
+                    // TODO: Snackbar/Toast 등 사용자 피드백 추가 고려
+                }
app/src/main/java/com/texthip/thip/ui/feed/usecase/ChangeFeedSaveUseCase.kt (1)

6-11: UseCase 구성 적절 — 단일 책임으로 리포지토리 호출 위임 OK

의존성 주입과 operator invoke 패턴으로 사용성이 좋습니다. 가독성 향상을 위해 간단한 KDoc을 권장합니다.

 class ChangeFeedSaveUseCase @Inject constructor(
     private val feedRepository: FeedRepository
 ) {
-    suspend operator fun invoke(feedId: Long, newSaveStatus: Boolean) =
+    /**
+     * 피드 저장 상태를 변경합니다.
+     * @param feedId 대상 피드 ID
+     * @param newSaveStatus true면 저장, false면 해제
+     */
+    suspend operator fun invoke(feedId: Long, newSaveStatus: Boolean) =
         feedRepository.changeFeedSave(feedId, newSaveStatus)
 }
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (2)

75-78: 불필요한 toLong() 호출 제거 제안

feedId 자체가 Long이므로 toLong()은 중복 변환입니다. 가독성을 위해 제거하는 것이 좋습니다.

     LaunchedEffect(feedId) {
         feedDetailViewModel.loadFeedDetail(feedId)
-        commentsViewModel.initialize(postId = feedId.toLong(), postType = "FEED")
+        commentsViewModel.initialize(postId = feedId, postType = "FEED")
     }

387-400: 삭제 확인 후 후속 처리(내비게이션/피드백) 흐름 확인 필요

onConfirm에서 바로 deleteFeed(feedId)를 호출하지만, 삭제 성공 시 화면을 닫거나 상위로 결과를 전달하는 흐름(토스트, popBackStack, savedStateHandle 등)이 이 화면에서 관찰되고 있지 않습니다. ViewModel에서 성공 이벤트를 내보낸다면, 여기서 구독해 후속 동작을 수행해 주세요.

원한다면 다음과 같이 성공 이벤트(예: feedDetailUiState.deleteSuccess)를 관찰해 뒤로 가기/결과 전달을 수행할 수 있습니다:

LaunchedEffect(feedDetailUiState.deleteSuccess) {
    if (feedDetailUiState.deleteSuccess == true) {
        // 필요 시 결과 전달 로직 추가 (savedStateHandle 등)
        onNavigateBack()
    }
}
app/src/main/java/com/texthip/thip/ui/feed/usecase/DeleteFeedUseCase.kt (1)

10-12: 반환 타입 설계 검토 제안(Result<String?> 대신 도메인 친화 타입)

서버 메시지(String?)를 그대로 Result로 노출하면 상위 계층 결합도가 올라갑니다. 결과 자체가 중요하지 않다면 Result 또는 도메인 이벤트(예: Deleted)로 감추는 것을 고려해 보세요.

app/src/main/java/com/texthip/thip/ui/feed/usecase/ChangeFeedLikeUseCase.kt (1)

9-11: 반환 타입 명시로 인터페이스 안정성 강화 제안

public 유스케이스는 반환 타입을 명시하면 변경 파급을 줄일 수 있습니다. Repository 시그니처가 Result<FeedLikeResponse?>라면 다음처럼 타입을 명시하는 것을 권장합니다.

 package com.texthip.thip.ui.feed.usecase

 import com.texthip.thip.data.repository.FeedRepository
+import com.texthip.thip.data.model.feed.response.FeedLikeResponse
 import javax.inject.Inject

 class ChangeFeedLikeUseCase @Inject constructor(
     private val feedRepository: FeedRepository
 ) {
-    suspend operator fun invoke(feedId: Long, newLikeStatus: Boolean) =
-        feedRepository.changeFeedLike(feedId, newLikeStatus)
+    suspend operator fun invoke(feedId: Long, newLikeStatus: Boolean): Result<FeedLikeResponse?> {
+        return feedRepository.changeFeedLike(feedId, newLikeStatus)
+    }
 }
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (1)

127-129: FeedCommentScreen 쪽 onNavigateToFeedEdit 시그니처(Long)와의 정합성 점검 필요

FeedCommentScreen의 onNavigateToFeedEdit를 Long으로 변경할 예정이므로(앞선 코멘트), 이 람다의 파라미터도 Long을 기대하게 됩니다. 또한 FeedRoutes.Write의 feedId 역시 Long으로 정렬되어야 합니다. 라우트가 아직 Int라면 임시로 toLong() 변환을 추가하세요.

Long으로 정렬된 경우 그대로 두시면 됩니다. 만약 Int라면 임시 패치는 아래와 같습니다:

-            onNavigateToFeedEdit = { feedId ->
-                navController.navigate(FeedRoutes.Write(feedId = feedId))
-            }
+            onNavigateToFeedEdit = { feedId ->
+                navController.navigate(FeedRoutes.Write(feedId = feedId /* .toInt()가 필요하다면 라우트 타입을 Long으로 수정하는 것이 우선입니다 */))
+            }

권장사항: FeedRoutes.Write(feedId: Long)로 통일하고 중간 변환을 제거하세요.

app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedFeedViewModel.kt (2)

93-97: 북마크 토글은 현재 로컬 상태만 변경 — 향후 실제 저장 API 연동 시 일관된 패턴 권장

현재는 로컬 StateFlow만 갱신합니다. 이후 Saved 피드 화면도 실제 저장 상태 변경 API(ChangeFeedSaveUseCase)로 연동한다면, 본 뷰모델에서도 동일한 “낙관적 업데이트 → 실패 시 롤백” 패턴을 적용하는 편이 일관적입니다.

다음과 같이 유스케이스를 주입하고, 실패 시 이전 상태로 복구하는 형태를 고려해 보세요:

-class SavedFeedViewModel: ViewModel() {
+class SavedFeedViewModel(
+    // 예: @Inject 주입
+    private val changeFeedSaveUseCase: ChangeFeedSaveUseCase? = null
+): ViewModel() {
@@
-    fun toggleBookmark(id: Long) {
-        _feeds.value = _feeds.value.map {
-            if (it.id == id) it.copy(isSaved = !it.isSaved) else it
-        }
-    }
+    fun toggleBookmark(id: Long) {
+        val prev = _feeds.value
+        val next = prev.map { if (it.id == id) it.copy(isSaved = !it.isSaved) else it }
+        _feeds.value = next
+        // 실제 API가 연결된 경우에만 호출
+        changeFeedSaveUseCase?.invoke(id, next.first { it.id == id }.isSaved)
+            ?.onFailure { _ ->
+                // 실패 시 롤백
+                _feeds.value = prev
+            }
+    }

99-103: 좋아요 토글도 동일하게 낙관적 업데이트/롤백 패턴 고려

Saved 영역에서 좋아요를 다루지 않는다면 현상 유지로 충분하나, 다룰 계획이라면 북마크와 동일 패턴으로 처리 권장합니다.

app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedOthersViewModel.kt (1)

69-94: 낙관적 업데이트는 적절합니다. 실패 시 사용자에게 피드백 노출을 추가하세요

현재 실패 시 UI 롤백만 수행합니다. FeedOthersUiState에 errorMessage가 이미 있으므로, 실패 원인을 errorMessage에 담아 스낵바 등으로 사용자에게 피드백 제공을 권장합니다.
또한 동일 피드에 대해 연속 탭으로 다중 요청이 중첩되는 경우를 완화(예: in-flight set으로 중복 요청 차단)하면 더 안정적입니다.

다음과 같이 실패 시 에러 메시지 업데이트를 추가해 보세요:

-            changeFeedLikeUseCase(feedId, newLikeStatus)
-                .onFailure {
-                    _uiState.update { it.copy(feeds = currentFeeds) }
-                }
+            changeFeedLikeUseCase(feedId, newLikeStatus)
+                .onFailure { e ->
+                    _uiState.update {
+                        it.copy(
+                            feeds = currentFeeds,
+                            errorMessage = e.message ?: "좋아요 상태 변경에 실패했어요."
+                        )
+                    }
+                }
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)

3-3: 사용하지 않는 import 정리

현재 파일에서 Log는 사용되지 않습니다. 제거를 권장합니다.

다음 변경으로 정리할 수 있습니다:

-import android.util.Log
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (1)

61-78: 삭제 성공 이벤트 소모(consume) 메서드 추가 고려

deleteSuccess는 상태로 남아 재구성 시 재처리될 수 있습니다. 화면에서 한 번 처리 후 false로 되돌리는 consume 메서드를 두면 안정적입니다.

아래와 같이 보조 메서드를 추가해 사용하세요:

fun consumeDeleteSuccess() {
    updateState { it.copy(deleteSuccess = false) }
}
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (2)

312-337: 낙관적 업데이트는 잘 적용 — 실패 시 에러 전달 및 updateState 일관 사용 제안

  • 실패 시 단순 롤백 외에 FeedUiState.error에 메시지를 채워 사용자 피드백을 제공하면 UX가 개선됩니다.
  • 동일 파일 내 다른 곳처럼 updateState를 일관되게 사용하면 코드 가독성이 좋아집니다.

다음과 같이 수정해 보세요:

-            _uiState.update { it.copy(allFeeds = newFeeds) }
+            updateState { it.copy(allFeeds = newFeeds) }
@@
-            changeFeedLikeUseCase(feedId, newLikeStatus)
-                .onFailure {
-                    _uiState.update { it.copy(allFeeds = currentFeeds) }
-                }
+            changeFeedLikeUseCase(feedId, newLikeStatus)
+                .onFailure { e ->
+                    updateState {
+                        it.copy(
+                            allFeeds = currentFeeds,
+                            error = e.message ?: "좋아요 상태 변경에 실패했어요."
+                        )
+                    }
+                }

추가로, 동일 피드에 대한 연속 탭으로 요청이 중첩되는 문제를 방지하려면 in-flight 관리(예: Set로 진행 중인 feedId 추적)도 고려해 보세요.


339-361: 저장 상태 변경도 동일하게 에러 전달/일관 API 사용

위와 동일한 맥락으로 updateState 사용 일관성 및 실패 시 에러 메시지 설정을 권장합니다.

-            updateState { it.copy(allFeeds = newFeeds) }
+            updateState { it.copy(allFeeds = newFeeds) }
@@
-            changeFeedSaveUseCase(feedId, newSaveStatus)
-                .onFailure {
-                    updateState { it.copy(allFeeds = currentFeeds) }
-                }
+            changeFeedSaveUseCase(feedId, newSaveStatus)
+                .onFailure { e ->
+                    updateState {
+                        it.copy(
+                            allFeeds = currentFeeds,
+                            error = e.message ?: "저장 상태 변경에 실패했어요."
+                        )
+                    }
+                }
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (1)

440-442: toLong() 변환이 불필요할 수 있음

updatedFeedId가 이미 Long이면 변환이 불필요합니다. 타입 확인 후 제거를 권장합니다.

제안 diff:

-                    onSuccess(updatedFeedId.toLong())
+                    onSuccess(updatedFeedId)

확인은 위에서 제공한 스크립트(CreateFeedResponse)와 더불어 FeedRepository.updateFeed의 반환 모델 타입을 참고하세요.

app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt (1)

226-231: deleteFeed 반환 타입 단순화 제안
현재 FeedService.deleteFeedBaseResponse<String>을 반환하고, FeedRepository.deleteFeed는 이를 Result<String?>으로 래핑하고 있습니다. 만약 호출부나 도메인 로직에서 서버가 반환하는 문자열 메시지에 전혀 의존하지 않는다면, 반환 타입을 Result<Unit>으로 변경하여 불필요한 메시지 처리를 제거할 수 있습니다.

  • FeedRepository.deleteFeed 서명:
    suspend fun deleteFeed(feedId: Long): Result<String?> =
    suspend fun deleteFeed(feedId: Long): Result<Unit> = runCatching {
        feedService.deleteFeed(feedId)
            .handleBaseResponse()   // 내부 메시지 무시
            .let { }                // 반환값 대신 Unit 사용
    }
  • 호출부(예: ViewModel)의 deleteFeed 처리 로직에서 반환된 문자열 대신 성공 여부만을 판단하도록 수정 필요
  • 서비스 시그니처가 항상 메시지를 포함하는지, 앞으로 메시지가 없어질 가능성은 없는지 팀과 한 번 더 확인 권장
📜 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 359f86a and 12a5163.

📒 Files selected for processing (27)
  • app/src/main/java/com/texthip/thip/data/model/feed/request/FeedLikeRequest.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/data/model/feed/request/FeedSaveRequest.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/data/model/feed/response/FeedLikeResponse.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/data/model/feed/response/FeedSaveResponse.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/data/service/FeedService.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 (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (6 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedWriteScreen.kt (1 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/usecase/DeleteFeedUseCase.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (4 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedOthersViewModel.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (4 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteUiState.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (8 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/mock/FeedItem.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedFeedViewModel.kt (6 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (4 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/search/viewmodel/BookDetailViewModel.kt (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (2)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (1)
  • updateState (64-66)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (1)
  • updateState (35-37)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (2)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (1)
  • updateState (35-37)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (1)
  • updateState (32-34)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (1)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)
  • FeedScreen (65-389)
🔇 Additional comments (37)
app/src/main/java/com/texthip/thip/ui/mypage/mock/FeedItem.kt (1)

4-4: Long ID 전환 잘 적용되었습니다

UI/네비게이션/리포지토리 전반의 Long 이관과 일관성이 맞습니다. 특별한 이슈는 보이지 않습니다.

app/src/main/java/com/texthip/thip/data/model/feed/response/FeedSaveResponse.kt (1)

6-10: 직렬화 응답 모델 정의가 명확하며 Long 마이그레이션과 일치합니다

필드 네이밍(SerialName)과 타입이 일관되고, Boolean 기반의 저장 상태 표현도 적절합니다.

app/src/main/java/com/texthip/thip/data/service/FeedService.kt (3)

101-103: updateFeed의 반환 타입이 CreateFeedResponse인 점 확인 필요

업데이트도 생성과 동일한 응답 스키마를 의도적으로 사용한다면 OK입니다. 아니라면 UpdateFeedResponse(또는 typealias)로 의미를 분리하는 것이 가독성에 좋습니다.

API 문서 기준으로 업데이트 응답 스키마가 생성과 동일한지 확인 부탁드립니다.


105-111: 저장 상태 변경 엔드포인트 경로 확인 요청

POST("feeds/{feedId}/saved") 경로가 서버 계약의 정확한 리소스명(예: saved vs saves vs bookmarks)과 일치하는지 재확인 부탁드립니다. 또한 좋아요와 동일하게 멱등성 측면에서 PATCH 고려 여지도 있습니다.


71-73: feedId 타입(Long) 마이그레이션 영향 범위 재확인 요청

서비스 함수 시그니처를 Long으로 변경하셨습니다. 호출부, Nav-args, ViewModel, UseCase 등에서 feedId 파라미터가 모두 Long으로 일치하는지 다시 한번 확인해 주세요. 아래 명령어로 .kt 파일 전체를 스캔하여 Int 잔여 여부를 점검하실 수 있습니다:

  • rg -n '\bfeedId\s*:\s*Int\b' -g '*.kt'
  • rg -n 'NavType\.\s*IntType' -g '*.kt'
  • rg -n ':\s*Int\s*->' -g '*.kt'
  • rg -n '\.toInt\(\)' -g '*.kt'

위 확인 후에도 누락된 부분이 없으면 이 건은 종료하셔도 좋습니다.

app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (1)

123-123: 프리뷰 데이터의 ID Long 전환이 일관성 있게 반영되었습니다

프로덕션 전반의 Long 마이그레이션과 맞물려 Preview도 안전하게 반영되었습니다.

Also applies to: 139-139

app/src/main/java/com/texthip/thip/ui/feed/component/MyFeedCard.kt (1)

131-131: 프리뷰 데이터의 ID Long 전환이 적절합니다

전역적인 ID 타입 변경과 일치하며, Preview 빌드에도 문제 없습니다.

Also applies to: 147-147

app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteUiState.kt (1)

27-27: editingFeedId의 Long? 변경이 PR 목표와 일치합니다

작성/수정 플로우에서 ID 범위 확장에 대비한 타입 변경으로 합리적입니다.

app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt (1)

10-10: 피드 댓글 route 파라미터를 Long으로 전환한 점 일관성 좋습니다

전반적인 ID Long 마이그레이션과 맞물려 내비게이션 타입 일치가 잘 반영되었습니다.

다른 경로에서 Comment(feedId)를 생성/역직렬화하는 부분(네비게이션 그래프, 인코더/디코더)이 모두 Long 기준으로 동기화되었는지 한 번만 확인 부탁드립니다.

app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt (1)

23-25: navigateToFeedComment(feedId: Long) 전환 LGTM

경로 객체 FeedRoutes.Comment도 Long을 받도록 맞춰져 있어 시그니처 정합성이 확보되었습니다.

  • 기존 Int 기반 호출부가 남아 있지 않은지 검색해 주세요.
  • NavType/직렬화 스키마가 Long을 정상 지원하는지(딥링크 포함)도 점검 부탁드립니다.
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedWriteScreen.kt (1)

65-65: onFeedCreated 콜백을 (Long) -> Unit으로 전환 — OK

생성/수정 완료 후 전달되는 feedId 타입과 일치합니다. 상위 네비게이션/호출부도 Long 시그니처로 동기화되었는지 확인만 부탁드립니다.

  • Feed 생성 후 이동(예: 상세/댓글 화면) 경로가 Long 기반으로 모두 마이그레이션되었는지 확인해 주세요.
app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt (2)

76-77: SearchBookDetailScreen의 onFeedClick(Long) 전환 — 일관성 확보

상위/하위 컴포저블 및 feedItem.id(Long)과 타입이 맞아 떨어집니다.


123-124: 내부 Content 컴포저블의 onFeedClick(Long)도 동기화 — OK

호출 경로 일관성 유지가 잘 되어 있습니다.

app/src/main/java/com/texthip/thip/ui/feed/usecase/DeleteFeedUseCase.kt (1)

6-13: 단일 책임의 얇은 유스케이스 위임, 적절합니다

리포지토리로의 위임이 명확하고 테스트/주입 관점에서도 다루기 좋습니다. 현재 요구사항 범위에서는 충분합니다.

app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (1)

46-50: 좋아요 콜백 전파 및 ViewModel 위임 연동이 깔끔합니다

  • Screen -> Content -> Card로 일관되게 (Long) feedId 콜백을 전파하고 ViewModel에 위임하는 구조가 명확합니다.
  • Preview에서도 새 파라미터 반영 완료되어 컴파일 안전성에 도움 됩니다.

Also applies to: 55-58, 137-141, 189-191

app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (2)

25-31: SavedStateHandle에서 Long으로 결과 수신/소모 처리, 방향성 적절

feedId를 Long으로 통일하는 맥락에 부합하며, get/remove 사용도 문제 없습니다.


118-121: Comment 라우트에서 getLong 사용은 일관성 측면에서 적절

Comment 경로도 Long으로 수신하여 ID 타입 일관성을 유지합니다.

app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedFeedViewModel.kt (1)

13-25: ID를 Long으로 일괄 전환한 점 좋습니다

목데이터의 FeedItem.id를 Long 리터럴(…L)로 전환해 본 PR의 전반적 ID 타입 변경과 일관성이 맞습니다.

Also applies to: 27-40, 42-55, 57-70, 72-84

app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedOthersViewModel.kt (1)

10-10: UseCase 주입으로 책임 분리 OK

ChangeFeedLikeUseCase를 주입해 ViewModel-Repository 사이에 유스케이스 레이어를 둔 점 좋습니다. 관심사 명확하고 테스트 용이성이 올라갑니다.

Also applies to: 29-31

app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (3)

70-73: 피드 댓글 내비게이션/결과 ID를 Long으로 정규화한 점 좋습니다

콜백 및 resultFeedId의 Long 일관화가 본 PR의 타입 변경과 맞습니다.


341-345: 북마크/좋아요 이벤트를 ViewModel 유스케이스로 연결한 부분 좋습니다

낙관적 업데이트 + 실패 롤백이 ViewModel에 구현되어 있어 UI는 단순 위임으로 깔끔합니다.


269-269: toLong() 호출 유지 필요 — AllFeedItem/MyFeedItem의 feedId는 Int
AllFeedItem(data class AllFeedItem)와 MyFeedItem(data class MyFeedItem)의 feedId 프로퍼티가 Int로 선언되어 있어, Long 타입 필드에 할당하기 위해 .toLong() 호출이 필요합니다.

Likely an incorrect or invalid review comment.

app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (3)

18-21: 삭제 진행/완료 상태 플래그 추가 적절

isDeleting, deleteSuccess를 UI 상태에 포함시켜 화면 단에서 로딩/완료/네비게이션 처리를 분리할 수 있어 좋습니다.


25-27: DeleteFeedUseCase 주입 OK

Repository 호출을 UseCase를 통해 감싸 관심사 분리가 잘 되어 있습니다.


36-60: loadFeedDetail feedId 타입 검증 완료 — 모든 호출부에서 Long 전달 중

  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (라인 66): feedId: Long을 그대로 loadFeedDetail(feedId)에 전달
  • 그 외 loadFeedDetail 호출부 없음
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (2)

52-55: 좋아요/저장 UseCase 주입으로 액션 분리 OK

UI 액션이 ViewModel → UseCase → Repository로 분리되어 테스트 용이성과 유지보수성이 좋아졌습니다.


312-337: toLong() 변환 유지 필요

  • 검증 결과 AllFeedItem.feedIdInt 타입이므로 toLong() 호출이 필요합니다.
  • 단순 비교(it.feedId == feedId)로 변경 시 타입 불일치 오류가 발생합니다.

위 제안은 무시해 주세요.

Likely an incorrect or invalid review comment.

app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (5)

65-99: Long 마이그레이션 및 편집 상태 반영 로직 적정 — LGTM

feedId: Long으로의 전환이 리포지토리 API와 일관되며, 로딩/에러 상태 업데이트와 편집 UI 상태 세팅도 적절합니다.


120-156: 편집 데이터 세팅 시그니처 Long으로 통일 — LGTM

setEditDatafeedId: Long 적용이 상위 레이어와 일관되며, 카테고리 인덱스 계산 및 상태 업데이트 흐름도 타당합니다.


356-364: 생성/수정 분기와 콜백 시그니처(Long) 통일 — LGTM

편집 모드 여부와 editingFeedId 존재 여부로 분기하는 구조가 명확합니다. 콜백 타입(Long) 통일도 일관됩니다.


419-460: 업데이트 시그니처 Long으로 전환 및 에러 처리 — LGTM

updateFeed(feedId: Long, ...) 전환과 결과 처리/에러 처리 흐름 모두 기존 패턴과 일관됩니다.


392-396: feedId는 Int 타입이므로 .toLong() 변환을 유지해야 합니다
CreateFeedResponse.feedIdInt로 정의되어 있어, Long이 필요한 onSuccess 호출 시 변환이 필수입니다.

  • 확인 위치:
    • app/src/main/java/com/texthip/thip/data/model/feed/response/CreateFeedResponse.kt: val feedId: Int (라인 9)

Likely an incorrect or invalid review comment.

app/src/main/java/com/texthip/thip/data/repository/FeedRepository.kt (5)

7-19: 신규 요청/응답 모델 import 추가 — LGTM

좋아요/저장 요청·응답과 전체 피드/연관 피드 응답 import가 적절히 추가되었습니다. 마이그레이션 방향과 일치합니다.


176-180: 피드 상세 조회 feedId를 Long으로 변경 — LGTM

서비스 시그니처와의 정합성이 확보되었습니다.


184-200: 피드 수정 feedId Long 전환 — LGTM

요청 모델 구성 및 서비스 호출 흐름이 기존 패턴과 동일해 신뢰할 수 있습니다.


233-239: 좋아요 요청 필드명(type) 검증 필요

현재 FeedLikeRequest.kt@SerialName("type") val type: Boolean로 설정되어 요청 페이로드에 "type": true/false가 전송됩니다.
서버 API 스펙에서 해당 엔드포인트가 type 필드를 사용하는지(liked 또는 isLiked 등 다른 이름이 아닌지) 반드시 확인해주세요. 불일치 시 400/422 에러가 발생할 수 있습니다.

  • app/src/main/java/com/texthip/thip/data/model/feed/request/FeedLikeRequest.kt:8 (@SerialName("type") val type: Boolean)
  • app/src/main/java/com/texthip/thip/data/service/FeedService.kt 내 changeFeedLike 메서드 확인

241-247: 저장 요청 필드명(type) 일치 확인 완료

  • FeedSaveRequest 데이터 클래스에 @SerialName("type") val type: Boolean 으로 정의되어 있어 서버 스펙의 type 필드와 일치함을 확인했습니다.
    추가 수정은 필요하지 않습니다.

val isLoadingCategories: Boolean = false,
val isEditMode: Boolean = false,
val editingFeedId: Int? = null
val editingFeedId: Long? = null
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

연쇄 영향도 점검 스크립트 제공: Int 기반 잔존 호출부/네비게이션 파악

해당 필드 타입 변경에 따라 저장/복원, NavArgs, 콜백 시그니처까지 Long으로 일관 정리되었는지 확인을 권장합니다.


🏁 Script executed:

#!/bin/bash
# editingFeedId가 Int로 남아있는 선언/사용 탐색
rg -nP --type=kt -C2 '\beditingFeedId\s*:\s*Int\??\b'

# feedId 파라미터에 Int 사용 흔적 탐색
rg -nP --type=kt -C2 '\bfeedId\s*:\s*Int\??\b'

# 네비게이션 인자 타입 Long 반영 여부 (IntType 사용 잔존 여부)
rg -nP --type=kt -C2 'NavType\.\s*IntType'
rg -nP --type=kt -C2 'navArgument\(\s*"feedId"\s*\)\s*\{\s*type\s*=\s*NavType\.\s*IntType'

# 콜백 시그니처에서 Int -> Long 전환 확인
rg -nP --type=kt -C2 'onFeedCreated\s*:\s*\(\s*Int\s*\)\s*->'
rg -nP --type=kt -C2 '\(\s*Int\s*\)\s*->\s*Unit'

# Int 변환 사용 잔존 여부
rg -nP --type=kt -C2 '\.toInt\(\)'

Length of output: 583


Int 타입 잔존 여부 점검을 위해 --glob '*.kt' 옵션을 사용해 .kt 파일을 대상으로 재검색합니다. 아래 스크립트를 실행해주세요.


🏁 Script executed:

#!/bin/bash
# editingFeedId가 Int로 남아있는 선언/사용 탐색
rg -nP -C2 '\beditingFeedId\s*:\s*Int\??\b' --glob '*.kt'

# feedId 파라미터에 Int 사용 흔적 탐색
rg -nP -C2 '\bfeedId\s*:\s*Int\??\b' --glob '*.kt'

# 네비게이션 인자 타입 Long 반영 여부 (IntType 사용 잔존 여부)
rg -nP -C2 'NavType\.\s*IntType' --glob '*.kt'
rg -nP -C2 'navArgument\(\s*"feedId"\s*\)\s*\{\s*type\s*=\s*NavType\.\s*IntType' --glob '*.kt'

# 콜백 시그니처에서 Int -> Long 전환 확인
rg -nP -C2 'onFeedCreated\s*:\s*\(\s*Int\s*\)\s*->' --glob '*.kt'
rg -nP -C2 '\(\s*Int\s*\)\s*->\s*Unit' --glob '*.kt'

# Int 변환 사용 잔존 여부
rg -nP -C2 '\.toInt\(\)' --glob '*.kt'

Length of output: 20964


feedId/편집 모드 ID 타입 일관성 검증·수정 필요
editingFeedIdLong?으로 변경하셨다면, 저장·복원(StateSaver), NavArgs, 콜백 시그니처 등 모든 feedId 관련 선언 및 사용부가 Long으로 일관되도록 반드시 정리해야 합니다. 아래 위치에서 여전히 Int 타입이 남아있거나 NavType.IntType을 사용 중이니, Long으로 교체하거나 필요 시 toLong() 변환 로직을 추가해주세요.

  • data/model/feed/response/*.kt
    • RelatedBooksResponse, MyFeedResponse, CreateFeedResponse, AllFeedResponse, FeedDetailResponse 등 val feedId: Int
  • ui/navigator/routes/FeedRoutes.kt
    • data class Write(val feedId: Int? = null)
  • ui/feed/screen/FeedCommentScreen.kt
    • onNavigateToFeedEdit: (Int) -> Unit(Long) -> Unit
  • NavArgs 설정
    • navArgument("feedId"){ type = NavType.IntType }NavType.LongType
  • 기타 feedId 전달·콜백부
    • 네비게이션 호출부, ViewModel 콜백 시그니처 등

위 항목들을 점검·수정해 주시고, 변경 후에도 컴파일 및 런타임 네비게이션이 정상 동작하는지 확인 부탁드립니다.

🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteUiState.kt
around line 27, editingFeedId is now Long? but many related declarations still
use Int; update all feedId-related types and usages to Long for consistency:
change model response properties (RelatedBooksResponse, MyFeedResponse,
CreateFeedResponse, AllFeedResponse, FeedDetailResponse) from Int to Long,
update ui/navigator/routes/FeedRoutes.kt Write data class feedId to Long?,
change FeedCommentScreen callback signatures from (Int)->Unit to (Long)->Unit,
change NavArgs navArgument("feedId"){ type = NavType.IntType } to
NavType.LongType, and update any navigation call sites or ViewModel callbacks to
pass Long (or call toLong() where converting from Int). Also ensure
StateSaver/State restoration and serialization use Long, run a full compile and
validate navigation at runtime.

import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.onFailure
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

컴파일 오류: 잘못된 import 제거 필요

onFailurekotlin.Result의 멤버 확장으로 별도 import가 필요하지 않습니다. import kotlin.onFailure는 존재하지 않는 심볼을 참조하므로 컴파일 에러가 발생합니다. 해당 import를 제거하세요.

-import kotlin.onFailure
📝 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.

Suggested change
import kotlin.onFailure
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt
around line 21, remove the invalid import statement `import kotlin.onFailure`
because it references a non-existent symbol; delete that import line and
recompile to confirm the compile error is resolved.


data class FeedItem(
val id: Int,
val id: Long,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

남아있는 Int 기반 feedId/id 사용 여부 점검 요청

이번 변경과 맞물려 잔존 Int 타입 식별자가 있으면 추후 서버가 32비트 범위를 넘는 ID를 내려줄 때 파싱 실패/범위 초과 이슈가 생길 수 있습니다. 레포 전체에서 Int 타입 선언/캐스팅 흔적을 확인해 주세요.

다음 스크립트로 흔적을 점검할 수 있습니다:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) feedId: Int 선언 검색"
rg -nP --type=kt -C2 '\bfeedId\s*:\s*Int\b'

echo
echo "2) Feed 관련 data class에서 id: Int 선언 검색"
rg -nPU --type=kt -n -C2 '(?s)^\s*data\s+class\s+\w*Feed\w*\s*\([^)]*\bid\s*:\s*Int\b'

echo
echo "3) toLong() 캐스팅 흔적(원천이 Int일 가능성) 검색"
rg -nP --type=kt -C2 '\bfeedId\.toLong\s*\('

Length of output: 143


Int 타입 식별자 잔여 여부를 재확인하기 위해 아래 스크립트를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) 'feedId: Int' 선언 검색"
rg -n --glob '*.kt' -C2 '\bfeedId\s*:\s*Int\b'

echo
echo "2) Feed 관련 data class에서 'id: Int' 선언 검색"
rg -n --glob '*.kt' -C2 'data class \w*Feed\w*.*\bid\s*:\s*Int\b'

echo
echo "3) .toLong() 캐스팅 흔적 검색"
rg -n --glob '*.kt' -C2 '\.toLong\s*\('

Length of output: 3677


Int 기반 feedIdLong 변경 요청
서버에서 32비트 범위(±2,147,483,647)를 넘는 ID를 내려줄 경우 파싱 실패/범위 초과 문제가 발생할 수 있습니다. 아직 Int 타입으로 선언된 feedId를 모두 Long으로 변경해 주세요.

발견된 위치:

  • app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt:14
    val feedId: Int? = null
  • app/src/main/java/com/texthip/thip/data/model/feed/response/RelatedBooksResponse.kt:15
    @SerialName("feedId") val feedId: Int
  • app/src/main/java/com/texthip/thip/data/model/feed/response/CreateFeedResponse.kt:9
    val feedId: Int
  • app/src/main/java/com/texthip/thip/data/model/feed/response/AllFeedResponse.kt:16
    @SerialName("feedId") val feedId: Int
  • app/src/main/java/com/texthip/thip/data/model/feed/response/FeedDetailResponse.kt:8
    @SerialName("feedId") val feedId: Int
  • app/src/main/java/com/texthip/thip/data/model/feed/response/MyFeedResponse.kt:16
    @SerialName("feedId") val feedId: Int

id: Int 선언은 확인되지 않았습니다.

위 위치의 IntLong 변경을 부탁드립니다.

private fun RelatedFeedItem.toFeedItem(): FeedItem {
return FeedItem(
id = this.feedId,
id = this.feedId.toLong(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

toLong() 캐스팅은 임시 대응입니다 — RelatedFeedItem.feedId를 Long으로 이관하세요

feedId의 근원 타입이 Int인 채로 UI에서 Long으로 캐스팅하면, 서버가 64비트 범위의 ID를 내려주는 순간 파싱/범위 문제로 이어질 수 있습니다. 모델 층(RelatedFeedItem)의 feedId를 Long으로 이관한 뒤 이 캐스팅을 제거하는 방향이 안전합니다.

모델 이관 이후 이 파일 변경은 다음과 같습니다:

-        id = this.feedId.toLong(),
+        id = this.feedId,

RelatedFeedItem의 현재 타입을 확인하려면:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "RelatedFeedItem 선언 위치 확인"
rg -nP --type=kt -C3 '\bdata\s+class\s+RelatedFeedItem\b'

echo
echo "RelatedFeedItem.feedId 타입 확인 (Int 잔존 여부)"
rg -nP --type=kt -C2 '\bfeedId\s*:\s*Int\b'

Length of output: 165


모델 이관 상태를 정확히 재확인하기 위해, --type=kt 대신 --glob '*.kt' 옵션을 사용하여 다음 스크립트를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) RelatedFeedItem 데이터 클래스 선언 위치 확인"
rg -nP '\bdata\s+class\s+RelatedFeedItem\b' -n --glob '*.kt'

echo
echo "2) RelatedFeedItem.feedId 타입 확인 (Int 또는 Long)"
rg -nP '\bfeedId\s*:\s*(Int|Long)\b' -n --glob '*.kt'

Length of output: 4622


RelatedFeedItem.feedId를 Long으로 변경하고 toLong() 캐스팅 제거
feedId의 원시 타입이 Int인 상태에서 UI에서 .toLong()으로 변환 사용하는 것은, 서버가 64비트 범위의 ID를 내려줄 때 파싱/범위 손실 문제를 일으킬 수 있습니다. 모델 계층에서 feedIdLong으로 이관한 뒤 UI 코드에서 캐스팅을 제거하세요.

  • app/src/main/java/com/texthip/thip/data/model/feed/response/RelatedBooksResponse.kt
    -    @SerialName("feedId") val feedId: Int,
    +    @SerialName("feedId") val feedId: Long,
  • app/src/main/java/com/texthip/thip/ui/search/viewmodel/BookDetailViewModel.kt (181행)
    -        id = this.feedId.toLong(),
    +        id = this.feedId,
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/feed/response/RelatedBooksResponse.kt
(update the model) and
app/src/main/java/com/texthip/thip/ui/search/viewmodel/BookDetailViewModel.kt
around line 181, change RelatedFeedItem.feedId's type from Int to Long in the
data model and remove the .toLong() cast in the ViewModel; update any
constructors, serializers/deserializers (e.g., Gson/Moshi), parcelable/room
annotations or mapping functions that reference feedId to use Long so JSON
parsing and downstream usages accept 64-bit IDs without overflow, and run/adjust
any call sites or tests that assumed Int accordingly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (2)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (2)

65-74: 컴파일 오류 위험: onNavigateToFeedEdit 파라미터 타입을 Long으로 변경하세요

이 컴포저블은 feedId를 Long으로 사용합니다. 그러나 onNavigateToFeedEdit의 시그니처가 (Int) -> Unit로 남아 있어, 내부에서 onNavigateToFeedEdit(feedDetail.feedId)를 호출할 때 타입 불일치가 발생합니다. 또한 FeedNavigation.kt에서 이 람다를 FeedRoutes.Write(feedId = feedId)로 넘기고 있어, 라우트가 Long을 기대하면 컴파일 에러가 됩니다. 전체적으로 Long으로 일관화하는 것이 안전합니다.

아래처럼 시그니처를 Long으로 변경해 주세요.

 @Composable
 fun FeedCommentScreen(
     navController: NavHostController,
     modifier: Modifier = Modifier,
-    feedId: Long,
+    feedId: Long,
     onNavigateBack: () -> Unit = {},
-    onNavigateToFeedEdit: (Int) -> Unit = {},
+    onNavigateToFeedEdit: (Long) -> Unit = {},
     feedDetailViewModel: FeedDetailViewModel = hiltViewModel(),
     commentsViewModel: CommentsViewModel = hiltViewModel()
 )

438-446: Preview 컴파일 오류: feedId 리터럴을 Long으로 변경 필요

FeedCommentScreen의 feedId가 Long으로 변경되었으므로, Preview에서 1 대신 1L을 전달해야 컴파일됩니다.

         FeedCommentScreen(
-            feedId = 1,
+            feedId = 1L,
             navController = rememberNavController()
         )
🧹 Nitpick comments (2)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)

87-90: 사소하지만 정리: toLong() 호출 불필요

feedId가 이미 Long이므로 중복 변환을 제거해 가독성을 높일 수 있습니다.

 LaunchedEffect(feedId) {
     feedDetailViewModel.loadFeedDetail(feedId)
-    commentsViewModel.initialize(postId = feedId.toLong(), postType = "FEED")
+    commentsViewModel.initialize(postId = feedId, postType = "FEED")
 }
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (1)

119-123: 타입 세이프 라우팅 도입 좋습니다. 주석된 이전 코드 제거 권장

toRoute<FeedRoutes.Comment>() 사용으로 간결하고 안전해졌습니다. 과거 방식 주석은 유지 필요성이 낮으므로 정리하면 가독성이 좋아집니다.

-        /*val route = backStackEntry.arguments?.let {
-            FeedRoutes.Comment(it.getLong("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.

📥 Commits

Reviewing files that changed from the base of the PR and between 12a5163 and 27ba048.

📒 Files selected for processing (4)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (5 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (10 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (4 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (2)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)
  • FeedScreen (67-402)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)
  • FeedCommentScreen (65-436)
🔇 Additional comments (16)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (3)

41-43: NavController 도입과 관련 import 추가 적절

삭제 성공 후 이전 스크린으로 결과 전달 흐름(BackStack SavedStateHandle)과 일관되게 작동할 기반이 마련되었습니다.


78-86: 삭제 성공 후 결과 전달 로직은 적절합니다. 단, 플래그 소모 처리(consume/reset) 확인 필요

deleteSuccess가 true인 상태로 재구성되면, LaunchedEffect가 다시 트리거될 수 있습니다. 뷰모델에서 deleteSuccess를 한 번 사용 후 false로 되돌리는 consume 메서드(또는 Event 래핑)로 재발동을 방지하는지 확인해 주세요.


411-411: 삭제 API 호출 연결 LGTM

확정 삭제 시점에 feedDetailViewModel.deleteFeed(feedId) 호출이 정확히 연결되었습니다.

app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (7)

25-25: SavedState에서 Long으로 결과 수신 일관화 좋습니다

피드 생성 결과 feedId를 Long으로 읽어오는 변경이 전체 Long 마이그레이션과 일치합니다.


28-28: FeedScreen에 NavController 전달 적절

FeedScreen이 SavedStateHandle을 구독해 삭제 결과를 반영할 수 있는 기반이 마련되었습니다.


31-31: 결과 소모 시 remove 사용 적절

결과 소비 후 상태 정리(remove)로 중복 처리를 방지합니다.


69-71: 작성(수정) 경로로의 feedId 변환(toLong) 처리 OK

Edit 인라인 데이터 세팅 경로에서 feedId를 Long으로 맞춰 사용하고 있습니다. 라우트 정의(FeedRoutes.Write)에서 feedId가 Long?이면 toLong()은 불필요할 수 있으니 최종 타입을 한 번 점검해 주세요.


81-82: 수정 모드 로드에서도 Long 변환 일관성 유지

loadFeedForEdit 호출부도 Long으로 일관화되어 있습니다.


125-127: FeedCommentScreen에 NavController 전달 OK

코멘트 화면에서도 삭제 결과를 이전 스크린으로 전달할 수 있게 되어 흐름이 완성됩니다.


130-132: onNavigateToFeedEdit 인자 타입 정합성 확인 필요

FeedCommentScreen의 onNavigateToFeedEdit가 (Int) -> Unit으로 남아 있으면, 여기서 feedId를 FeedRoutes.Write(feedId = feedId)로 넘길 때 라우트가 Long을 기대하는 경우 컴파일 에러가 납니다. FeedCommentScreen 쪽 시그니처를 (Long) -> Unit으로 변경하면 이 람다의 feedId 타입도 Long으로 추론되어 문제가 해소됩니다. 앞 파일의 수정과 함께 적용해 주세요.

app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (6)

45-47: NavController 관련 import 추가 OK

프리뷰에서 rememberNavController()를 사용하도록 기반이 갖춰졌습니다.


72-77: Public API 시그니처(Long 전환, NavController 주입) 정합성 좋습니다

  • onNavigateToFeedComment: (Long) -> Unit
  • resultFeedId: Long?
  • navController: NavHostController

네비게이션 및 결과 전달 체인과 일관됩니다.


282-282: 내 피드 id Long 전환 일관성 좋습니다

MyFeed -> FeedItem 변환 시 id를 Long으로 맞춰 downstream과 타입 일치를 확보했습니다.


331-331: 전체 피드 id Long 전환 일관성 좋습니다

AllFeed -> FeedItem 변환에서도 Long으로 일치합니다.


354-358: 좋아요/저장 상태 변경 핸들러 연결 완료

뷰모델 메서드(changeFeedSave, changeFeedLike)로 연결되어 실제 API 연동 경로가 확보되었습니다.


410-424: 프리뷰에서 NavController 주입 OK

프리뷰 빌드가 원활합니다. (FeedCommentScreen 프리뷰의 feedId는 1L로 변경 필요 — 해당 파일 코멘트 참조)

Comment on lines +103 to +112
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")
}
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

observeForever로 인한 메모리 누수/중복 구독 위험 — SavedStateHandle의 StateFlow로 전환 권장

observeForever는 수동 해제가 없으면 누수를 유발합니다. SavedStateHandle.getStateFlow를 사용해 안전하게 수집하세요.

-    LaunchedEffect(Unit) {
-        navController.currentBackStackEntry?.savedStateHandle?.let { handle ->
-            handle.getLiveData<Long>("deleted_feed_id").observeForever { deletedId ->
-                if (deletedId != null) {
-                    feedViewModel.removeDeletedFeed(deletedId)
-                    handle.remove<Long>("deleted_feed_id")
-                }
-            }
-        }
-    }
+    LaunchedEffect(Unit) {
+        val handle = navController.currentBackStackEntry?.savedStateHandle ?: return@LaunchedEffect
+        handle.getStateFlow<Long?>("deleted_feed_id", null).collect { deletedId ->
+            if (deletedId != null) {
+                feedViewModel.removeDeletedFeed(deletedId)
+                handle.remove<Long>("deleted_feed_id")
+            }
+        }
+    }
📝 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.

Suggested change
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(Unit) {
val handle = navController.currentBackStackEntry?.savedStateHandle
?: return@LaunchedEffect
handle
.getStateFlow<Long?>("deleted_feed_id", null)
.collect { deletedId ->
if (deletedId != null) {
feedViewModel.removeDeletedFeed(deletedId)
handle.remove<Long>("deleted_feed_id")
}
}
}
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt around lines
103–112, replace the observeForever usage (which leaks and causes duplicate
subscriptions) with SavedStateHandle.getStateFlow("deleted_feed_id", /* default
*/ 0L) and collect it inside LaunchedEffect; when you receive a non-default id
call feedViewModel.removeDeletedFeed(deletedId) and then clear the key via
handle.remove("deleted_feed_id") or set it back to the default so the value is
consumed—this uses the StateFlow collection tied to the coroutine lifecycle and
avoids manual observer removal.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (2)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (2)

89-91: feedId 타입 변환 중복 제거 필요

Line 91에서 feedId.toLong()으로 변환하고 있지만, feedId는 이미 Long 타입입니다. 불필요한 변환을 제거해야 합니다.

-        commentsViewModel.initialize(postId = feedId.toLong(), postType = "FEED")
+        commentsViewModel.initialize(postId = feedId, postType = "FEED")

392-393: feedId 타입 불일치 문제

onNavigateToFeedEdit 함수는 Int 타입을 받지만 feedDetail.feedIdInt 타입입니다. 그러나 FeedCommentScreen의 feedId 파라미터가 Long으로 변경되었으므로, onNavigateToFeedEdit 함수의 시그니처도 일관성있게 Long으로 변경해야 합니다.

-    onNavigateToFeedEdit: (Int) -> Unit = {},
+    onNavigateToFeedEdit: (Long) -> Unit = {},

그리고 Line 392에서:

-                        onNavigateToFeedEdit(feedDetail.feedId)
+                        onNavigateToFeedEdit(feedDetail.feedId.toLong())
🧹 Nitpick comments (5)
app/src/main/java/com/texthip/thip/ui/common/buttons/ActionBarButton.kt (3)

33-33: 이름 의도를 더 명확히: isLockIcon → isSaveLocked 제안

현재 플래그는 “아이콘 모양”이 아니라 “저장 기능 잠금 상태”를 의미합니다. API/도메인 용어와 맞춘 isSaveLocked(또는 isLocked)가 의도를 더 잘 전달합니다. 기본값은 동일하게 false 유지가 가능합니다.

적용 예(서명만 발췌):

-    isLockIcon: Boolean = false,
+    isSaveLocked: Boolean = false,

채택 시, 호출부(SavedFeedCard, Preview 등)와 내부 분기(if …)의 식별자도 함께 변경해야 합니다. 필요한 경우 전체 참조를 안전하게 치환하는 스크립트 제공 가능.


91-108: 잠금 상태 a11y 개선: contentDescription 및 비활성(semantics.disabled) 전달

잠금일 때 클릭을 막은 점은 좋습니다. 접근성(스크린리더) 측면에서 상태 전달을 위해 잠금 아이콘에 적절한 contentDescription과 semantics { disabled() }를 함께 제공하는 것을 권장합니다. 또한 북마크 아이콘에도 on/off에 따른 설명을 추가하면 좋습니다.

잠금/북마크 아이콘 블록만 발췌:

-            if (isLockIcon) {
-                Icon(
-                    painter = painterResource(R.drawable.ic_lock),
-                    contentDescription = null,
-                    tint = Color.Unspecified
-                )
-            } else {
-                Icon(
-                    modifier = Modifier.clickable { onBookmarkClick() },
-                    painter = painterResource(
-                        if (isSaved) R.drawable.ic_save_filled else R.drawable.ic_save
-                    ),
-                    contentDescription = null,
-                    tint = Color.Unspecified
-                )
-            }
+            if (isLockIcon) {
+                Icon(
+                    modifier = Modifier.semantics { disabled() },
+                    painter = painterResource(R.drawable.ic_lock),
+                    contentDescription = stringResource(R.string.content_desc_locked),
+                    tint = Color.Unspecified
+                )
+            } else {
+                Icon(
+                    modifier = Modifier.clickable { onBookmarkClick() },
+                    painter = painterResource(
+                        if (isSaved) R.drawable.ic_save_filled else R.drawable.ic_save
+                    ),
+                    contentDescription = stringResource(
+                        if (isSaved) R.string.content_desc_bookmark_on else R.string.content_desc_bookmark_off
+                    ),
+                    tint = Color.Unspecified
+                )
+            }

추가로 필요한 import:

import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.semantics

추가 string 리소스 예시(res/values/strings.xml):

<string name="content_desc_locked">잠금됨</string>
<string name="content_desc_bookmark_on">저장됨</string>
<string name="content_desc_bookmark_off">저장</string>

129-129: Preview 가시성 강화: 잠금/비잠금 두 경우 모두 확인 가능한 추가 프리뷰 권장

현재 프리뷰는 잠금 케이스만 보여줍니다. 북마크 토글 경로(잠금 해제)를 함께 미리보기로 추가해두면 리그레션을 빠르게 감지하는 데 도움이 됩니다.

예시(파일 하단에 추가):

@Preview
@Composable
private fun ActionBarButtonPreview_Unlocked() {
    var isLiked by remember { mutableStateOf(false) }
    var isSaved by remember { mutableStateOf(false) }

    ActionBarButton(
        isLiked = isLiked,
        likeCount = 123,
        commentCount = 45,
        isSaveVisible = true,
        isSaved = isSaved,
        isPinVisible = false,
        onLikeClick = { isLiked = !isLiked },
        onCommentClick = {},
        onBookmarkClick = { isSaved = !isSaved },
        onPinClick = {},
        isLockIcon = false
    )
}
app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (1)

112-112: 잠금 상태 전달 방식 적절함(아이콘 비활성 경로와 정합)

isLockIcon = feedItem.isLocked 매핑으로 저장 아이콘의 비활성 표시 흐름과 일치합니다. 위에서 제안한 이름 개선을 채택한다면 호출부도 함께 변경해 주세요.

호출부 예시:

-            isLockIcon = feedItem.isLocked,
+            isSaveLocked = feedItem.isLocked,
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)

150-163: 조건문 단순화 가능

blur 효과 적용 조건을 더 간결하게 표현할 수 있습니다.

-            modifier = if (isBottomSheetVisible || showDialog) {
-                Modifier
-                    .fillMaxSize()
-                    .blur(5.dp)
-            } else {
-                Modifier.fillMaxSize()
-            }
+            modifier = Modifier
+                .fillMaxSize()
+                .then(
+                    if (isBottomSheetVisible || showDialog) Modifier.blur(5.dp) else Modifier
+                )
📜 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 27ba048 and 4a4786d.

📒 Files selected for processing (4)
  • app/src/main/java/com/texthip/thip/ui/common/buttons/ActionBarButton.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (6 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (4 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (2)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (1)
  • updateState (64-66)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (1)
  • updateState (35-37)
🔇 Additional comments (3)
app/src/main/java/com/texthip/thip/ui/common/buttons/ActionBarButton.kt (1)

124-124: Preview에서 핀 숨김(false) 설정 OK

잠금 케이스를 강조하려는 의도가 명확합니다. 문제 없습니다.

app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt (1)

79-87: 피드 삭제 후 이전 화면에서 deleted_feed_id 처리 로직 확인됨

FeedCommentScreen의 LaunchedEffect에서 저장한 deleted_feed_id
FeedScreen에서 아래와 같이 받아 처리하고 있습니다.

  • FeedCommentScreen.kt (79–87줄):
    savedStateHandle.set("deleted_feed_id", feedId) 호출
  • FeedScreen.kt (105–108줄):
    handle.getLiveData("deleted_feed_id").observeForever { deletedId →
    • feedViewModel.removeDeletedFeed(deletedId)
    • handle.remove("deleted_feed_id")
    }

위 코드로 삭제된 피드 ID를 정상적으로 수신·처리하고 있으므로 추가 확인은 필요 없습니다.

app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (1)

94-94: feedId 타입 변환 검증 완료

FeedDetailResponse.kt의 feedId가 Int로 선언되어 있어 originalFeed.feedId.toLong() 호출이 필요하며, 해당 변환이 올바르게 적용되어 있습니다. 추가 조치 불필요합니다.

Comment on lines +83 to +99
fun changeFeedLike() {
viewModelScope.launch {
val originalFeed = _uiState.value.feedDetail ?: return@launch

val updatedFeed = originalFeed.copy(
isLiked = !originalFeed.isLiked,
likeCount = if (originalFeed.isLiked) originalFeed.likeCount - 1 else originalFeed.likeCount + 1
)
updateState { it.copy(feedDetail = updatedFeed) }

val newLikeStatus = !originalFeed.isLiked
changeFeedLikeUseCase(originalFeed.feedId.toLong(), newLikeStatus)
.onFailure {
updateState { it.copy(feedDetail = originalFeed) }
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

좋아요 상태 변경 시 에러 처리 개선 필요

좋아요 API 호출이 실패했을 때 원래 상태로 롤백은 하지만, 사용자에게 에러 메시지를 표시하지 않습니다. 네트워크 오류 등으로 실패했을 때 사용자가 인지할 수 있도록 에러 상태를 업데이트해야 합니다.

             changeFeedLikeUseCase(originalFeed.feedId.toLong(), newLikeStatus)
-                .onFailure {
-                    updateState { it.copy(feedDetail = originalFeed) }
+                .onFailure { exception ->
+                    updateState { 
+                        it.copy(
+                            feedDetail = originalFeed,
+                            error = "좋아요 상태 변경에 실패했습니다."
+                        )
+                    }
                 }
📝 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.

Suggested change
fun changeFeedLike() {
viewModelScope.launch {
val originalFeed = _uiState.value.feedDetail ?: return@launch
val updatedFeed = originalFeed.copy(
isLiked = !originalFeed.isLiked,
likeCount = if (originalFeed.isLiked) originalFeed.likeCount - 1 else originalFeed.likeCount + 1
)
updateState { it.copy(feedDetail = updatedFeed) }
val newLikeStatus = !originalFeed.isLiked
changeFeedLikeUseCase(originalFeed.feedId.toLong(), newLikeStatus)
.onFailure {
updateState { it.copy(feedDetail = originalFeed) }
}
}
}
fun changeFeedLike() {
viewModelScope.launch {
val originalFeed = _uiState.value.feedDetail ?: return@launch
val updatedFeed = originalFeed.copy(
isLiked = !originalFeed.isLiked,
likeCount = if (originalFeed.isLiked) originalFeed.likeCount - 1 else originalFeed.likeCount + 1
)
updateState { it.copy(feedDetail = updatedFeed) }
val newLikeStatus = !originalFeed.isLiked
changeFeedLikeUseCase(originalFeed.feedId.toLong(), newLikeStatus)
.onFailure { exception ->
updateState {
it.copy(
feedDetail = originalFeed,
error = "좋아요 상태 변경에 실패했습니다."
)
}
}
}
}

Comment on lines +101 to +116
fun changeFeedSave() {
viewModelScope.launch {
val originalFeed = _uiState.value.feedDetail ?: return@launch

val updatedFeed = originalFeed.copy(
isSaved = !originalFeed.isSaved
)
updateState { it.copy(feedDetail = updatedFeed) }

val newSaveStatus = !originalFeed.isSaved
changeFeedSaveUseCase(originalFeed.feedId.toLong(), newSaveStatus)
.onFailure {
updateState { it.copy(feedDetail = originalFeed) }
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

저장(북마크) 상태 변경 시 에러 처리 개선 필요

좋아요와 마찬가지로 저장 API 호출 실패 시에도 사용자에게 에러를 알려야 합니다.

             changeFeedSaveUseCase(originalFeed.feedId.toLong(), newSaveStatus)
-                .onFailure {
-                    updateState { it.copy(feedDetail = originalFeed) }
+                .onFailure { exception ->
+                    updateState { 
+                        it.copy(
+                            feedDetail = originalFeed,
+                            error = "저장 상태 변경에 실패했습니다."
+                        )
+                    }
                 }
📝 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.

Suggested change
fun changeFeedSave() {
viewModelScope.launch {
val originalFeed = _uiState.value.feedDetail ?: return@launch
val updatedFeed = originalFeed.copy(
isSaved = !originalFeed.isSaved
)
updateState { it.copy(feedDetail = updatedFeed) }
val newSaveStatus = !originalFeed.isSaved
changeFeedSaveUseCase(originalFeed.feedId.toLong(), newSaveStatus)
.onFailure {
updateState { it.copy(feedDetail = originalFeed) }
}
}
}
fun changeFeedSave() {
viewModelScope.launch {
val originalFeed = _uiState.value.feedDetail ?: return@launch
val updatedFeed = originalFeed.copy(
isSaved = !originalFeed.isSaved
)
updateState { it.copy(feedDetail = updatedFeed) }
val newSaveStatus = !originalFeed.isSaved
changeFeedSaveUseCase(originalFeed.feedId.toLong(), newSaveStatus)
.onFailure { exception ->
updateState {
it.copy(
feedDetail = originalFeed,
error = "저장 상태 변경에 실패했습니다."
)
}
}
}
}
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt
around lines 101 to 116, the failure branch for changeFeedSave only reverts UI
state but does not notify the user; update the onFailure handler to both revert
to originalFeed and emit a user-facing error (e.g., send a UI event or update an
error field in the state) with the failure message so the user is informed when
the save/bookmark API call fails; ensure you pass the exception or its message
into the event/state and keep reverting the optimistic change as currently
implemented.

private fun SavedFeedCardPrev() {
val feed1 = FeedItem(
id = 1,
id = 1L,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

ID Long 리터럴로의 전환 반영 👍 / 전역 일관성 점검 권장

Preview 데이터의 id = 1L, 2L 변경은 Long 전환과 일치합니다. 실제 코드 전체에서도 FeedItem.id, 관련 DTO/엔드포인트의 feedIdLong으로 일관되는지 한 번에 점검해보는 것을 권장합니다.

다음 스크립트로 주요 지점을 점검할 수 있습니다.

Also applies to: 140-140


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== FeedItem 정의의 id 타입 확인 =="
fd -a 'FeedItem.kt' | while read -r f; do
  echo ">>> $f"
  rg -n -C2 -e 'data\s+class\s+FeedItem' -e '\bid\s*:\s*(Int|Long)\b' "$f" || true
done

echo
echo "== feedId 파라미터 타입 사용 현황 (Int vs Long) =="
rg -nP --type=kt -C2 '\bfeedId\s*:\s*Int\b' || true
rg -nP --type=kt -C2 '\bfeedId\s*:\s*Long\b' || true

echo
echo "== 리터럴 Long 누락 의심 (id = <숫자> 형태에서 L 접미사 없는 경우) =="
# 오탐 가능성 있습니다. 리뷰 보조용으로만 참고하세요.
rg -nP --type=kt -C1 '\bid\s*=\s*\d+(?!L)\b' || true

Length of output: 1076


아래 스크립트로 FeedItem 정의와 feedId 사용, id 리터럴 전환 누락 여부를 전수 검사해 보시기 바랍니다.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== FeedItem 정의에서 id 타입 확인 =="
rg -nP 'data\s+class\s+FeedItem\b' -C2 -g '*.kt'

echo
echo "== feedId 파라미터 타입 사용 현황 =="
rg -nP '\bfeedId\s*:\s*(Int|Long)\b' -C2 -g '*.kt'

echo
echo "== id 리터럴에서 L 접미사 누락 의심 지점 =="
rg -nP '\bid\s*=\s*\d+(?!L)\b' -C1 -g '*.kt'

Length of output: 25773


FeedItem ID Long 반영 확인 및 응답 DTO·리터럴 일관성 수정 필요

SavedFeedCard 에서 id = 1L 로 변경하신 건 적절하나, 전체적으로 feedId/id 타입이 혼용되어 있습니다. 아래 지점을 점검·수정해 주세요.

  • 데이터 모델(응답 DTO)에서 feedId: Int 로 선언된 항목들
    RelatedBooksResponse.RelatedFeedItem (feedId: Int)
    MyFeedResponse.MyFeedItem (feedId: Int)
    AllFeedResponse.AllFeedItem (feedId: Int)
    FeedDetailResponse (feedId: Int)
    CreateFeedResponse (feedId: Int)
    Long 으로 변경하여 API, 서비스, 레포지토리 전반과 타입 일치시킬 것

  • 코드 상의 리터럴에 L 접미사 없는 id = <숫자> (실제 타입이 Long이라면 누락)
    SearchBookGroupScreen.kt
    SavedBookViewModel.kt
    GroupLiveSearchResult.kt
    GroupFilteredSearchResult.kt
    GroupSearchScreen.kt
    id = 1L, 2L … 처럼 명시적 Long 리터럴로 변경

  • 그 외 FeedItem.id, FeedService/@path, FeedRepository, 뷰모델·유스케이스 등에서 모두 Long 사용 일관 확인

위 항목을 반영하면 전체 코드베이스의 ID 타입이 일관되어 예기치 못한 변환 오류를 방지할 수 있습니다.

🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt
around line 124, you changed the literal to id = 1L but the codebase mixes Int
and Long for feed IDs; update the DTOs RelatedBooksResponse.RelatedFeedItem,
MyFeedResponse.MyFeedItem, AllFeedResponse.AllFeedItem, FeedDetailResponse, and
CreateFeedResponse to declare feedId as Long (not Int), change any numeric
literals in SearchBookGroupScreen.kt, SavedBookViewModel.kt,
GroupLiveSearchResult.kt, GroupFilteredSearchResult.kt, GroupSearchScreen.kt,
etc. to use Long literals (1L, 2L ...), and audit FeedItem.id, FeedService
@Path/@RequestParam signatures, FeedRepository methods, viewmodels and use-cases
to accept/return Longs so the API, service, repository and UI layers are
consistent.

@JJUYAAA JJUYAAA merged commit 1da6b52 into THIP-TextHip:develop Aug 19, 2025
1 check was pending
@JJUYAAA JJUYAAA self-assigned this Aug 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API] 피드 API 남은 것 연동 [(THIP2025-295)]

1 participant