Skip to content

[UI] 데이터 없는 화면, 사용자 찾기 화면 구현#75

Merged
JJUYAAA merged 22 commits intoTHIP-TextHip:developfrom
JJUYAAA:ui/#74-empty_data_screen
Aug 12, 2025
Merged

[UI] 데이터 없는 화면, 사용자 찾기 화면 구현#75
JJUYAAA merged 22 commits intoTHIP-TextHip:developfrom
JJUYAAA:ui/#74-empty_data_screen

Conversation

@JJUYAAA
Copy link
Member

@JJUYAAA JJUYAAA commented Aug 9, 2025

➕ 이슈 링크


🔎 작업 내용

데이터 없는 화면

  • 유저 구독리스트
  • 내피드 - 작성된 글
  • 피드 -내가 구독하는 유저
  • 마이페이지- 저장피드
  • 마이페이지 - 책 피드
  • 마이페이지 - 반응
  • 다른 사람 피드

새로 추가된 screen

  • 사용자 찾기 화면
  • 다른 사용자 피드 화면

📸 스크린샷

데이터 없는 화면
image
image
image
image
image
image

새로 추가된 screen

bandicam.2025-08-09.14-01-44-672.mp4
image

😢 해결하지 못한 과제


📢 리뷰어들에게

캡쳐, 화면녹화를 듀얼모니터에서 해서 그런가 올리고 보니 비율이 이상하게 캡쳐된 것들이 보이네용.. 양해 부탁드려욧.. 실제로는 좌우 padding 값 이상 없습니닷!

Summary by CodeRabbit

  • 신규 기능

    • 사람 검색 화면, 최근 검색어 목록, 검색 결과 및 빈 결과 UI가 추가되었습니다.
    • 다른 사용자의 피드와 프로필 정보를 보여주는 화면이 추가되었습니다.
    • 저장된 책/피드가 없을 때의 빈 상태 안내 UI가 추가되었습니다.
    • 반응(좋아요/댓글) 목록에 빈 상태 안내가 추가되었습니다.
    • 최근 검색어에서 항목 삭제 기능이 추가되었습니다.
    • 구독자 수(띱 수) 클릭 시 동작이 가능해졌습니다.
    • 구독자 프로필 이미지 표시가 팔로워 리스트가 없을 때 조건부로 변경되었습니다.
    • 저장된 책/피드 리스트 UI가 개선되고, ViewModel 연결 방식이 변경되었습니다.
  • 기능 개선

    • 팔로워/구독자 수 및 팔로워 미리보기 UI가 개선되어, 리스트가 비어 있을 때 안내 메시지가 표시됩니다.
    • 저장된 책/피드 리스트의 상태 관리가 개선되어 빈 데이터 상태를 별도 ViewModel로 처리합니다.
  • 버그 수정

    • 일부 텍스트가 하드코딩에서 문자열 리소스를 사용하도록 변경되었습니다.
  • 문서 및 스타일

    • 다양한 빈 상태 안내 및 사용자 안내 문구가 추가 및 수정되었습니다.
  • 테스트 및 프리뷰

    • 주요 UI 컴포넌트 및 화면에 프리뷰가 추가되어 디자인 확인이 용이해졌습니다.

@coderabbitai
Copy link

coderabbitai bot commented Aug 9, 2025

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

📥 Commits

Reviewing files that changed from the base of the PR and between 5b508b1 and 19374f1.

📒 Files selected for processing (4)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (4 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/component/MypageSaveBook.kt (0 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedFeedViewModel.kt (3 hunks)
  • app/src/main/res/values/strings.xml (3 hunks)
 ___________________________________________
< Crossing the tensor streams to find bugs. >
 -------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ

Walkthrough

여러 Compose UI 컴포넌트와 화면에 "데이터 없음" 상태를 처리하는 뷰와 메시지가 추가되었습니다. 기존 북마크/피드 저장 화면, 검색 및 피드 화면 등에서 데이터가 없을 때 보여줄 UI와 문자열 리소스가 대거 보강되었으며, 관련 ViewModel과 미리보기(Preview)도 추가 및 수정되었습니다.

Changes

Cohort / File(s) Change Summary
AuthorHeader 인터랙션 개선
app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt
AuthorHeader에 thipNum 클릭 콜백(onThipNumClick) 파라미터 추가, 해당 영역을 클릭 가능하게 UI 구조 변경 및 Preview 반영
피드 구독/구독자 바 UI 조건 렌더링
app/src/main/java/com/texthip/thip/ui/feed/component/FeedSubscribelistBar.kt,
app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt
구독자/구독 리스트가 비어있을 때 별도 Empty UI를 조건부 렌더링, 이미지/아이콘 조건부 노출, Preview 추가
사람 검색 및 결과/최근검색 UI 추가
app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt,
app/src/main/java/com/texthip/thip/ui/feed/component/PeopleRecentSearch.kt,
app/src/main/java/com/texthip/thip/ui/feed/component/SearchPeopleEmptyResult.kt,
app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt
사람 검색 결과/빈 결과/최근 검색어 컴포넌트 및 화면 추가, 각종 Preview 제공, 검색 상태별 분기 렌더링 구현
피드 및 타인 피드 화면 Empty State 추가
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt,
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt
피드가 없을 때의 메시지 및 UI 추가, Preview에 빈 데이터 상태 반영
마이페이지 저장/반응/빈 상태 UI 리팩토링
app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt,
app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt,
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageReactionScreen.kt,
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt
저장된 피드/책/반응 리스트에 Empty UI 추가, ViewModel 파라미터화, Preview에 빈 상태 반영, 기존 MypageSaveBook/Feed 파일 삭제
마이페이지 저장 ViewModel 확장/빈 상태 지원
app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedBookViewModel.kt,
app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedFeedViewModel.kt
ViewModel을 open 클래스로 변경, 빈 상태 서브클래스(EmptySavedBookViewModel, EmptySavedFeedViewModel) 추가, StateFlow 네이밍 및 override 구조 변경
문자열 리소스 추가 및 수정
app/src/main/res/values/strings.xml
빈 상태 및 안내 메시지, 검색 관련 신규 string 리소스 다수 추가 및 일부 텍스트 수정

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant ViewModel

    User->>UI: 화면 진입/검색/탭 전환
    UI->>ViewModel: 데이터 요청 (피드/책/반응/검색 등)
    ViewModel-->>UI: 데이터 목록 반환
    alt 데이터 없음
        UI->>User: "데이터 없음" 메시지 및 Empty UI 표시
    else 데이터 존재
        UI->>User: 데이터 리스트 렌더링
    end
    User->>UI: (필요시) 인터랙션 (ex. thipNum 클릭, 북마크, 검색어 삭제)
    UI->>ViewModel: 상태 변경 요청 (옵션)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20분

Assessment against linked issues

Objective Addressed Explanation
홈/피드 → 데이터 없는 페이지 생성 (#74)
마이페이지 → 데이터 없는 페이지 생성 (#74)

Assessment against linked issues: Out-of-scope changes

해당 이슈(#74)의 "데이터 없는 페이지 생성" 목적과 직접 관련 없는 변경사항은 발견되지 않았습니다.

Possibly related PRs

Suggested labels

✅ OK merge

Suggested reviewers

  • rbqks529

Poem

🐰
데이터가 없을 땐 걱정 마세요,
빈 화면도 예쁘게 꾸며드려요!
메시지와 프리뷰, 뷰모델도 준비,
토끼가 만든 UI는 언제나 든든히.
클릭도, 검색도, 비어 있어도 OK—
오늘도 토끼는 코드를 채워요!

Tip

You can disable sequence diagrams in the walkthrough.

Disable the reviews.sequence_diagrams setting in your project's settings in CodeRabbit to disable sequence diagrams in the walkthrough.

✨ 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: 19

🔭 Outside diff range comments (2)
app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedFeedViewModel.kt (1)

9-112: 하위 클래스에서 상태 플로우가 분리되어 토글이 반영되지 않는 결함(Shadowing)

EmptySavedFeedViewModel이 별도의 _feeds를 정의하고 feeds만 override해, 상위의 toggleBookmark/Like가 상위 _feeds만 갱신합니다. 결과적으로 EmptySavedFeedViewModel의 feeds에는 토글 변화가 반영되지 않습니다.

상위에 단일 가변 플로우를 두고 이를 override 가능하게 열어주는 방식으로 정리하세요.

수정 예:

-open class SavedFeedViewModel: ViewModel() {
-    private val _feeds = MutableStateFlow(
+open class SavedFeedViewModel : ViewModel() {
+    protected open val mutableFeeds: MutableStateFlow<List<FeedItem>> = MutableStateFlow(
         listOf(
           ... 기존 목데이터 ...
         )
     )
-
-    open val feeds: StateFlow<List<FeedItem>> = _feeds
+    open val feeds: StateFlow<List<FeedItem>> = mutableFeeds
 
     fun toggleBookmark(id: Int) {
-        _feeds.value = _feeds.value.map {
+        mutableFeeds.value = mutableFeeds.value.map {
             if (it.id == id) it.copy(isSaved = !it.isSaved) else it
         }
     }
 
     fun toggleLike(id: Int) {
-        _feeds.value = _feeds.value.map {
+        mutableFeeds.value = mutableFeeds.value.map {
             if (it.id == id) it.copy(isLiked = !it.isLiked) else it
         }
     }
 }
 
-class EmptySavedFeedViewModel : SavedFeedViewModel() {
-
-    private val _feeds = MutableStateFlow<List<FeedItem>>(emptyList())
-
-    override val feeds: StateFlow<List<FeedItem>> = _feeds
-
-}
+class EmptySavedFeedViewModel : SavedFeedViewModel() {
+    override val mutableFeeds: MutableStateFlow<List<FeedItem>> =
+        MutableStateFlow(emptyList())
+}

이렇게 하면 상위 토글 로직이 언제나 동일한 플로우 인스턴스를 갱신하므로, 서브클래스에서도 일관되게 반영됩니다.

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

181-201: onContentClick TODO 처리 및 lastIndex 기준 리스트 불일치 개선

  • onContentClick가 비어 있어 내비게이션이 동작하지 않습니다. 주석대로 댓글 화면으로 이동하도록 연결하세요.
  • Divider 조건에서 feeds.lastIndex를 사용하고 있는데, 루프는 feedStateList를 순회하므로 기준 리스트가 다릅니다. 일관성 있게 feedStateList.lastIndex를 사용하는 것이 안전합니다.
 MyFeedCard(
     feedItem = feed,
     onLikeClick = {
         val updated = feed.copy(
             isLiked = !feed.isLiked,
             likeCount = if (feed.isLiked) feed.likeCount - 1 else feed.likeCount + 1
         )
         feedStateList[index] = updated
     },
-    onContentClick = {} //TODO FeedCommentScreen으로
+    onContentClick = {
+        // TODO: 실제 라우트로 교체
+        navController?.navigate("feedComment/${feed.id}")
+    }
 )
 Spacer(modifier = Modifier.height(40.dp))
-if (index != feeds.lastIndex) {
+if (index != feedStateList.lastIndex) {
     HorizontalDivider(
         color = colors.DarkGrey02,
         thickness = 10.dp
     )
 }

내비게이션 라우트 문자열은 프로젝트의 NavGraph 규칙에 맞게 조정이 필요합니다. 원하시면 해당 화면 연결까지 PR에 반영할 수 있도록 도와드릴게요.

🧹 Nitpick comments (23)
app/src/main/res/values/strings.xml (2)

303-306: 피드 빈 상태 문구 추가는 적절합니다

문구 톤과 스타일은 기존 리소스와 자연스럽습니다. 필요 시 마침표/감탄사 사용 일관성만 한 번 더 점검 부탁드립니다.


333-336: 검색 관련 문자열 리소스 추가 적절

새로 추가된 사용자 검색/빈 결과 문구는 명확하고 UI 구성요소와의 연결도 자연스럽습니다. SearchPeopleEmptyResult에서 이 리소스를 직접 사용하면 중복/하드코딩을 줄일 수 있습니다.

app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt (1)

102-118: 접근성 및 터치 타겟 개선 제안(선택)

thipNum 영역이 클릭 가능해진 만큼 최소 터치 영역(예: padding)과 semantics(role 등)을 부여하면 접근성이 좋아집니다. 아이콘에도 설명이 필요하면 contentDescription을 고려하세요.

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

165-179: 빈 상태 레이아웃: 고정 dp 대신 부모 높이 기반 정렬 권장

.top padding 244.dp는 기기 해상도/상태바/탭 높이에 따라 어긋날 수 있습니다. LazyColumn 아이템에서는 fillParentMaxSize를 활용해 중앙 정렬로 표현하는 편이 더 견고합니다.

- Box(
-     modifier = Modifier
-         .fillMaxWidth()
-         .padding(top = 244.dp),
-     contentAlignment = Alignment.TopCenter
- ) {
+ Box(
+     modifier = Modifier
+         .fillParentMaxSize(),
+     contentAlignment = Alignment.Center
+ ) {
     Text(
         text = stringResource(R.string.create_feed),
         style = typography.smalltitle_sb600_s18_h24,
-        color = colors.White
+        color = colors.White
     )
 }

참고: fillParentMaxSize는 LazyColumn 아이템 내부에서 빈 상태를 스크린 중앙에 배치할 때 유용합니다.


280-289: Preview 중복 ThipTheme 제거

Preview 내부에서 ThipTheme를 중첩 적용하고 있습니다. 불필요한 중첩으로 인한 부작용(예: 상태바 사이드 이펙트) 가능성이 있으니 하나로 통일하세요.

- ThipTheme {
-     FeedScreen(
-         nickname = "ThipUser01",
-         userRole = "문학 칭호",
-         selectedTabIndex = 1,
-         feeds = mockFeeds,
-         totalFeedCount = mockFeeds.size,
-         followerProfileImageUrls = mockFollowerImages
-     )
- }
+ FeedScreen(
+     nickname = "ThipUser01",
+     userRole = "문학 칭호",
+     selectedTabIndex = 1,
+     feeds = mockFeeds,
+     totalFeedCount = mockFeeds.size,
+     followerProfileImageUrls = mockFollowerImages
+ )

300-309: 두 번째 Preview도 ThipTheme 중첩 해제 권장

위와 동일하게 중첩된 ThipTheme를 제거하세요.

- ThipTheme {
-     FeedScreen(
-         nickname = "ThipUser01",
-         userRole = "문학 칭호",
-         selectedTabIndex = 1,
-         feeds = mockFeeds,
-         totalFeedCount = mockFeeds.size,
-         followerProfileImageUrls = mockFollowerImages
-     )
- }
+ FeedScreen(
+     nickname = "ThipUser01",
+     userRole = "문학 칭호",
+     selectedTabIndex = 1,
+     feeds = mockFeeds,
+     totalFeedCount = mockFeeds.size,
+     followerProfileImageUrls = mockFollowerImages
+ )
app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt (1)

34-46: 아이템 액션에 콜백 사용 및 패딩 제안

  • ViewModel 호출을 콜백으로 교체하세요.
  • 가독성과 스크롤 여백을 위해 contentPadding 추가를 고려할 수 있습니다.
- LazyColumn {
+ LazyColumn(
+     contentPadding = androidx.compose.foundation.layout.PaddingValues(bottom = 16.dp)
+ ) {
     items(feedList, key = { it.id }) { feed ->
         val profileImagePainter = feed.userProfileImage?.let { painterResource(it) }

         SavedFeedCard(
             feedItem = feed,
             profileImage = profileImagePainter,
-            onBookmarkClick = { viewModel.toggleBookmark(feed.id) },
-            onLikeClick = { viewModel.toggleLike(feed.id) }
+            onBookmarkClick = { onToggleBookmark(feed.id) },
+            onLikeClick = { onToggleLike(feed.id) }
         )
     }
 }
app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt (2)

95-102: 이미지 로딩 품질 개선(플레이스홀더/에러 처리)

네트워크 이미지에 대한 placeHolder/error를 지정하면 깜빡임과 실패 시 UX를 개선할 수 있습니다.

AsyncImage(
    model = profile.profileImageUrl,
    contentDescription = null,
    modifier = Modifier
        .size(36.dp)
        .clip(CircleShape)
        .background(Color.LightGray),
    placeholder = painterResource(R.drawable.ic_profile_placeholder),
    error = painterResource(R.drawable.ic_profile_placeholder)
)

ic_profile_placeholder가 없다면 추가 생성이 필요합니다.


114-119: 접근성 보완(역할/라벨)

우측 셰브론 아이콘은 장식용이므로 contentDescription = null 유지가 맞습니다. 다만 전체 Row가 클릭 가능하므로 Row에 역할/라벨을 부여하면 스크린리더 접근성이 향상됩니다.

Row(
    modifier = Modifier
        .fillMaxWidth()
        .height(58.dp)
        .clickable { onClick() }
        .semantics { role = Role.Button; contentDescription = stringResource(R.string.my_subscription) },
    verticalAlignment = Alignment.CenterVertically
) { ... }
app/src/main/java/com/texthip/thip/ui/feed/component/PeopleRecentSearch.kt (1)

53-60: 중복 키워드 제거(선택 사항)

같은 키워드가 중복 저장될 수 있다면 distinct()로 중복을 제거해 가독성을 높일 수 있습니다.

- recentSearches.take(9).forEach { keyword ->
+ recentSearches.distinct().take(9).forEach { keyword ->
app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt (1)

40-47: Divider를 Spacer+background 대신 HorizontalDivider로 교체하여 일관성과 접근성을 높이세요.

다른 화면(FeedOthersScreen)에서도 HorizontalDivider를 사용 중입니다. 동일한 컴포넌트를 사용하면 코드 가독성과 스타일 일관성이 좋아집니다.

-                Spacer(
-                    modifier = Modifier
-                        .padding(vertical = 12.dp, horizontal = 20.dp)
-                        .fillMaxWidth()
-                        .height(1.dp)
-                        .background(colors.DarkGrey02)
-                )
+                androidx.compose.material3.HorizontalDivider(
+                    modifier = Modifier
+                        .padding(vertical = 12.dp, horizontal = 20.dp),
+                    thickness = 1.dp,
+                    color = colors.DarkGrey02
+                )
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (3)

50-54: TopAppBar 뒤로가기 핸들러 구현 누락

onLeftClick = {}는 사용자 경험상 뒤로가기를 기대하게 만듭니다. NavController 등을 연결해 뒤로가기 동작을 구현해주세요. 필요하시면 내비게이션 코드 생성 도와드리겠습니다.


137-174: Preview 내 ThipTheme 중복 감싸기 제거

ThipTheme 안에서 다시 ThipTheme를 감싸 중복 적용되고 있습니다. 불필요한 중첩을 제거하세요.

-        ThipTheme {
-            FeedOthersScreen(
-                nickname = "ThipUser01",
-                userRole = "문학 칭호",
-                feeds = mockFeeds,
-                totalFeedCount = mockFeeds.size,
-                followerProfileImageUrls = mockFollowerImages
-            )
-        }
+        FeedOthersScreen(
+            nickname = "ThipUser01",
+            userRole = "문학 칭호",
+            feeds = mockFeeds,
+            totalFeedCount = mockFeeds.size,
+            followerProfileImageUrls = mockFollowerImages
+        )

176-193: 두 번째 Preview도 ThipTheme 중복 제거

-        ThipTheme {
-            FeedOthersScreen(
-                nickname = "ThipUser01",
-                userRole = "문학 칭호",
-                feeds = mockFeeds,
-                totalFeedCount = mockFeeds.size,
-                followerProfileImageUrls = mockFollowerImages
-            )
-        }
+        FeedOthersScreen(
+            nickname = "ThipUser01",
+            userRole = "문학 칭호",
+            feeds = mockFeeds,
+            totalFeedCount = mockFeeds.size,
+            followerProfileImageUrls = mockFollowerImages
+        )
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageReactionScreen.kt (1)

134-137: 배경색 중복 적용 제거 검토

ThipTheme가 루트에 검은 배경을 깔고 있습니다. 화면에서도 Modifier.background(colors.Black)를 추가로 적용해 중복될 수 있습니다. 테마 일관성을 위해 배경 지정은 하나로 유지하세요.

app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt (4)

45-48: 최근 검색어 초기값 하드코딩 제거

실서비스에서는 임시 키워드(“메롱…”) 대신 빈 목록으로 시작하는 것이 자연스럽습니다. 필요 시 샘플은 Preview에서만 사용하세요.

-    var recentSearches by rememberSaveable {
-        mutableStateOf(listOf("메롱", "메메롱", "메메메롱", "메메메", "메메루메루메루"))
-    }
+    var recentSearches by rememberSaveable { mutableStateOf(emptyList<String>()) }

78-82: 첫 진입 시 키보드 포커스 이동 또는 FocusRequester 제거

focusRequester를 부착했지만 실제로 requestFocus()를 호출하지 않습니다. 초기 포커스를 원하는 경우 아래처럼 호출하고, 아니면 관련 코드 제거를 권장합니다.

     LaunchedEffect(isSearched) {
         if (isSearched) {
             focusManager.clearFocus()
         }
     }
+    LaunchedEffect(Unit) {
+        focusRequester.requestFocus()
+    }

111-116: 최근 검색어 중복 저장 방지(대소문자·공백 무시) 로직 보강

현재는 단순 contains로 대소문자/공백 차이의 중복을 허용합니다.

-                    onSearch = { query ->
-                        if (query.isNotBlank() && !recentSearches.contains(query)) {
-                            recentSearches = (listOf(query) + recentSearches).take(10)
-                        }
+                    onSearch = { query ->
+                        val q = query.trim()
+                        val exists = recentSearches.any { it.equals(q, ignoreCase = true) }
+                        if (q.isNotBlank() && !exists) {
+                            recentSearches = (listOf(q) + recentSearches).take(10)
+                        }
                         isSearched = true
                     }

128-133: 결과 카운트 문자열 리소스의 의미 명확화

group_searched_room_size는 “그룹/방” 문맥의 리소스로 보입니다. 사람 검색에는 별도의 리소스(예: search_people_result_count)를 두어 복잡한 재사용을 피하세요.

리소스 추가와 교체를 제안드립니다. 필요 시 strings.xml 패치도 도와드리겠습니다.

app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt (4)

47-51: Composable 파라미터의 ViewModel 기본값 분리로 테스트/미리보기/DI 스코프 개선 제안

현재 기본값에서 viewModel()을 직접 호출하는 패턴은 동작상 문제는 없지만, DI 스코프(특히 NavGraph별 스코프 또는 Hilt 사용 시)와 미리보기를 분리하기엔 덜 유연합니다. 다음처럼 오버로드로 분리하면 테스트/프리뷰/스코프 제어가 쉬워집니다.

  • NavHost 내에서 화면을 사용할 때 ViewModel 스코프가 의도한 Owner(예: navBackStackEntry)에 묶이는지 확인 부탁드립니다. Hilt 사용 중이면 hiltViewModel()로 대체하는 것도 고려해 주세요.
-@Composable
-fun SavedScreen(
-    feedViewModel: SavedFeedViewModel = viewModel(),
-    bookViewModel: SavedBookViewModel = viewModel()
-) {
+@Composable
+fun SavedScreen(
+    feedViewModel: SavedFeedViewModel,
+    bookViewModel: SavedBookViewModel
+) {
   // ... 본문 동일 ...
 }
+
+// 기본 진입 오버로드: 실제 화면 사용 시에만 ViewModel 획득
+@Composable
+fun SavedScreen() {
+    val feedVm: SavedFeedViewModel = viewModel()
+    val bookVm: SavedBookViewModel = viewModel()
+    SavedScreen(feedVm, bookVm)
+}

70-72: 하드코딩된 너비(160dp/60dp) 제거로 가변 텍스트/현지화 대응력 향상

탭 라벨 길이(현지화)에 따라 잘림/겹침 위험이 있습니다. TabRow는 기본적으로 탭을 균등 분배하므로 고정폭을 제거하는 편이 안전합니다.

-            Box(modifier = Modifier
-                .width(160.dp)
-                .padding(start = 20.dp)) {
+            Box(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(horizontal = 20.dp)
+            ) {
-                        Tab(
-                            modifier = Modifier.width(60.dp),
+                        Tab(
+                            modifier = Modifier,
                             selected = selected,
                             onClick = { selectedTabIndex = index },

추가로 TabRow 자체에 가로 폭을 명시해 균등 분배를 확실히 하려면(선택):

TabRow(
    modifier = Modifier.fillMaxWidth(),
    selectedTabIndex = selectedTabIndex,
    // ...
)

Also applies to: 95-95


126-130: 미리보기도 테마로 감싸 일관된 배경/타이포 확인 권장

다른 Preview(SavedScreenWithoutFeedPrev)는 ThipTheme로 감싸지만, 본 미리보기는 테마가 없어 실제 배경/색 대비 검증이 어렵습니다. 통일을 권장합니다.

 @Preview
 @Composable
 private fun SavedScreenPrev() {
-    SavedScreen(
+    ThipTheme {
+      SavedScreen(
         feedViewModel = SavedFeedViewModel(),
         bookViewModel = SavedBookViewModel()
-    )
+      )
+    }
 }

132-141: 빈 상태 프리뷰(LGTM) — 실제 다크 배경·타이포까지 검증 가능

Empty VM으로 상태 시뮬레이션하고 테마를 적용해 시각적 확인이 용이합니다. 필요 시 북만 비어있는 경우/피드만 비어있는 경우 등 조합 프리뷰도 추가하면 QA가 더 수월합니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 423464d and 5d0cdb6.

⛔ Files ignored due to path filters (1)
  • app/src/main/res/drawable/search_character_image.png is excluded by !**/*.png
📒 Files selected for processing (18)
  • app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt (4 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/component/FeedSubscribelistBar.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt (4 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/component/PeopleRecentSearch.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/component/SearchPeopleEmptyResult.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (4 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/component/MypageSaveBook.kt (0 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/component/MypageSaveFeed.kt (0 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageReactionScreen.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt (5 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedBookViewModel.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedFeedViewModel.kt (3 hunks)
  • app/src/main/res/values/strings.xml (3 hunks)
💤 Files with no reviewable changes (2)
  • app/src/main/java/com/texthip/thip/ui/mypage/component/MypageSaveBook.kt
  • app/src/main/java/com/texthip/thip/ui/mypage/component/MypageSaveFeed.kt
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: rbqks529
PR: THIP-TextHip/THIP-Android#30
File: app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt:108-112
Timestamp: 2025-07-01T07:19:10.981Z
Learning: The user rbqks529 prefers to extract hardcoded UI strings to StringResource files (strings.xml) rather than keeping them inline in Compose components, which is a good practice for maintainability and localization in Android development.
Learnt from: rbqks529
PR: THIP-TextHip/THIP-Android#30
File: app/src/main/java/com/texthip/thip/ui/myPage/groupPage/GroupPageScreen.kt:34-37
Timestamp: 2025-07-01T07:19:01.239Z
Learning: GroupPageScreen의 MyPageViewModel은 임시 ViewModel로, 현재는 프로토타입 단계이므로 오류 처리 등의 추가 기능은 나중에 구현 예정입니다.
📚 Learning: 2025-07-01T07:19:10.981Z
Learnt from: rbqks529
PR: THIP-TextHip/THIP-Android#30
File: app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt:108-112
Timestamp: 2025-07-01T07:19:10.981Z
Learning: The user rbqks529 prefers to extract hardcoded UI strings to StringResource files (strings.xml) rather than keeping them inline in Compose components, which is a good practice for maintainability and localization in Android development.

Applied to files:

  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt
  • app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageReactionScreen.kt
  • app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt
  • app/src/main/res/values/strings.xml
📚 Learning: 2025-07-01T07:19:01.239Z
Learnt from: rbqks529
PR: THIP-TextHip/THIP-Android#30
File: app/src/main/java/com/texthip/thip/ui/myPage/groupPage/GroupPageScreen.kt:34-37
Timestamp: 2025-07-01T07:19:01.239Z
Learning: GroupPageScreen의 MyPageViewModel은 임시 ViewModel로, 현재는 프로토타입 단계이므로 오류 처리 등의 추가 기능은 나중에 구현 예정입니다.

Applied to files:

  • app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt
🧬 Code Graph Analysis (8)
app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt (1)
app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (1)
  • SavedFeedCard (33-134)
app/src/main/java/com/texthip/thip/ui/feed/component/SearchPeopleEmptyResult.kt (1)
app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
  • ThipTheme (41-69)
app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt (1)
app/src/main/java/com/texthip/thip/ui/common/cards/CardBookList.kt (1)
  • CardBookList (37-104)
app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)
app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
  • ThipTheme (41-69)
app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt (1)
app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
  • ThipTheme (41-69)
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageReactionScreen.kt (4)
app/src/main/java/com/texthip/thip/ui/common/topappbar/DefaultTopAppBar.kt (1)
  • DefaultTopAppBar (22-65)
app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt (1)
  • OptionChipButton (30-92)
app/src/main/java/com/texthip/thip/ui/common/cards/CardAlarm.kt (1)
  • CardAlarm (39-148)
app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
  • ThipTheme (41-69)
app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt (5)
app/src/main/java/com/texthip/thip/ui/common/topappbar/DefaultTopAppBar.kt (1)
  • DefaultTopAppBar (22-65)
app/src/main/java/com/texthip/thip/ui/common/forms/SearchBookTextField.kt (1)
  • SearchBookTextField (38-112)
app/src/main/java/com/texthip/thip/ui/feed/component/SearchPeopleEmptyResult.kt (1)
  • SearchPeopleEmptyResult (16-32)
app/src/main/java/com/texthip/thip/ui/feed/component/PeopleRecentSearch.kt (1)
  • PeopleRecentSearch (27-63)
app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
  • ThipTheme (41-69)
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt (3)
app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt (1)
  • FeedContent (26-47)
app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt (1)
  • BookContent (27-49)
app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
  • ThipTheme (41-69)
🔇 Additional comments (8)
app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt (1)

51-70: 빈 상태 UI 구성 적절

문구를 문자열 리소스로 추출하고, 중앙 정렬로 안내 메시지를 제공한 점 좋습니다. 테마 타이포그래피/컬러 일관성도 양호합니다.

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

49-68: 빈 상태 UI 구성 적절

문구/스타일/정렬 모두 일관되고 명확합니다. 다른 Empty 컴포넌트들과 톤 앤 매너가 잘 맞습니다.

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

27-63: 최근 검색 UI 구성 적절

  • 문자열 리소스 사용 및 FlowRow로 2줄 제한, 9개 캡 처리 모두 적절합니다.
  • Chip의 onClose로 개별 삭제 UX도 명확합니다.
app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt (1)

22-38: 중복 정의 없음 확인됨

스크립트 실행 결과 SearchPeopleResult 함수는 LiveSearchPeopleResult.kt 파일에서만 정의되어 있어, 동일 패키지 내 중복 선언으로 인한 빌드 오류는 발생하지 않습니다.

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

11-13: bookList 사용처 점검 완료 – 추가 수정 불필요
books 프로퍼티를 참조하는 호출부가 모두 bookList로 통합되어 있어, 더 이상의 호출부 수정은 필요하지 않습니다.

  • app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt:55
  • app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt:31 (주석 처리된 예시)
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt (3)

3-3: 임포트 변경사항 확인됨 — 추가 코멘트 없음

기능/리팩토링 흐름에 부합하는 임포트 추가로 보이며 별도 이슈는 없습니다.

Also applies to: 18-18, 31-31, 35-35, 37-41


113-115: 상태 수집을 상위(SavedScreen)로 끌어올린 구조, 재사용성/테스트성 향상에 적합

FeedContent/BookContent가 데이터와 ViewModel을 주입받도록 변경되어 컴포저블 재사용과 UI 테스트가 수월해졌습니다. 빈 상태 처리도 하위 컴포넌트에서 일관되게 수행됩니다. 굿.


54-55: collectAsStateWithLifecycle 사용을 위해 필요한 의존성이 프로젝트에 포함되어 있지 않아 보입니다.

수동으로 다음을 확인해주세요:

  • androidx.lifecycle:lifecycle-runtime-compose 의존성이 build.gradle(또는 버전 카탈로그) 파일에 추가되어 있는지

의존성이 없을 경우 아래와 같이 추가해야 합니다:

implementation "androidx.lifecycle:lifecycle-runtime-compose:<latest-version>"

Comment on lines +28 to +38
itemsIndexed(peopleList) { index, user ->
AuthorHeader(
profileImage = user.profileImageUrl,
nickname = user.nickname,
badgeText = user.role,
profileImageSize = 36.dp,
showButton = false,
showThipNum = true,
thipNum = user.subscriberCount,
onThipNumClick = { onThipNumClick(user) }
)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

LazyColumn 아이템에 안정적 key를 지정해 재구성 비용과 스크롤 깜빡임을 줄여주세요.

현재 itemsIndexed(peopleList)에 key가 없어 Diff 계산 시 불필요한 재구성이 발생할 수 있습니다. 고유 식별자가 있다면 그 값을 key로 사용하세요. (닉네임이 유일하지 않을 수 있으므로 가능하면 id 사용 권장)

다음과 같이 적용을 제안드립니다(예: 임시로 nickname 사용):

-        itemsIndexed(peopleList) { index, user ->
+        itemsIndexed(peopleList, key = { _, user -> user.nickname }) { index, user ->

닉네임이 유일하지 않다면 MySubscriptionDataid를 추가한 뒤 해당 id를 key로 사용해주세요.

📝 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
itemsIndexed(peopleList) { index, user ->
AuthorHeader(
profileImage = user.profileImageUrl,
nickname = user.nickname,
badgeText = user.role,
profileImageSize = 36.dp,
showButton = false,
showThipNum = true,
thipNum = user.subscriberCount,
onThipNumClick = { onThipNumClick(user) }
)
itemsIndexed(peopleList, key = { _, user -> user.nickname }) { index, user ->
AuthorHeader(
profileImage = user.profileImageUrl,
nickname = user.nickname,
badgeText = user.role,
profileImageSize = 36.dp,
showButton = false,
showThipNum = true,
thipNum = user.subscriberCount,
onThipNumClick = { onThipNumClick(user) }
)
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt
around lines 28 to 38, the LazyColumn's itemsIndexed call lacks a stable key,
causing unnecessary recompositions and scroll flickering. Fix this by adding a
key parameter to itemsIndexed using a unique identifier for each user,
preferably an id if available; if not, temporarily use the nickname as the key.
If the data model does not have an id, add one to ensure stable keys.

Comment on lines +79 to +86
if (subscriptions.isEmpty()) {
EmptyMySubscriptionBar()
} else {
Row(
modifier = Modifier
.fillMaxWidth()
.height(58.dp)
.clickable { onClick() },
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

빈 구독 바 클릭 액션 누락(기능적 이슈)

subscriptions가 비어 있을 때 EmptyMySubscriptionBar가 자체 clickable { }을 갖지만 onClick을 호출하지 않습니다. 사용자가 빈 상태를 눌러 검색/구독으로 진입할 수 있도록 onClick을 전달하세요.

- if (subscriptions.isEmpty()) {
-     EmptyMySubscriptionBar()
+ if (subscriptions.isEmpty()) {
+     EmptyMySubscriptionBar(onClick = onClick)
 } else {
- @Composable
- private fun EmptyMySubscriptionBar() {
+ @Composable
+ private fun EmptyMySubscriptionBar(onClick: () -> Unit) {
     Box(
         modifier = Modifier
             .fillMaxWidth()
             .height(42.dp)
             .clip(RoundedCornerShape(8.dp))
             .background(colors.DarkGrey02)
-            .clickable { }
+            .clickable { onClick() }
     ) {
         Text(
             modifier = Modifier
                 .align(Alignment.CenterStart)
                 .padding(start = 12.dp),
             text = stringResource(R.string.find_thip_mate),
             color = colors.White,
             style = typography.view_m500_s12_h20
         )

         Icon(
             painter = painterResource(id = R.drawable.search_character_image),
             contentDescription = null,
             tint = Color.Unspecified,
             modifier = Modifier
                 .align(Alignment.BottomEnd)
                 .padding(end = 12.dp)
                 .width(32.dp)
                 .height(42.dp)
         )
     }
 }

Also applies to: 124-154

🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt
around lines 79 to 86 and also lines 124 to 154, the EmptyMySubscriptionBar
component is missing the onClick callback invocation on its clickable modifier.
Modify EmptyMySubscriptionBar to accept the onClick lambda and ensure it is
called when the empty subscription bar is clicked, enabling navigation to
search/subscribe functionality.

Comment on lines +18 to +23
modifier: Modifier= Modifier,
mainText: String
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

전달받은 modifier가 사용되지 않습니다

외부에서 넘긴 modifier를 반영해야 조합성이 살아납니다.

적용 diff 예:

-    Column(
-        modifier = Modifier.fillMaxSize(),
+    Column(
+        modifier = modifier.fillMaxSize(),
         verticalArrangement = Arrangement.Center,
         horizontalAlignment = Alignment.CenterHorizontally
     ) {
📝 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
modifier: Modifier= Modifier,
mainText: String
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
modifier: Modifier= Modifier,
mainText: String
) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/feed/component/SearchPeopleEmptyResult.kt
around lines 18 to 23, the passed-in modifier parameter is not used inside the
Column composable, which breaks composability. Replace the hardcoded
Modifier.fillMaxSize() with modifier.fillMaxSize() to apply the external
modifier properly while still filling the max size.

Comment on lines +146 to +157
Row(modifier = Modifier.padding(start = 20.dp, top = 20.dp, bottom = 20.dp)) {
OptionChipButton(
text = stringResource(R.string.likes),
isFilled = true,
onClick = { isLikesSelected = !isLikesSelected }
)
Spacer(modifier = Modifier.width(12.dp))
OptionChipButton(
text = stringResource(R.string.comments),
isFilled = true,
onClick = { isCommentsSelected = !isCommentsSelected }
)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Chip의 선택 상태를 외부 상태로 제어하도록 isSelected를 지정하세요.

현재 OptionChipButton은 내부 상태도 토글하기 때문에 외부 isLikesSelected와 불일치 위험이 있습니다. 제어형 컴포넌트로 사용하세요.

                 OptionChipButton(
                     text = stringResource(R.string.likes),
                     isFilled = true,
+                    isSelected = isLikesSelected,
                     onClick = { isLikesSelected = !isLikesSelected }
                 )
                 Spacer(modifier = Modifier.width(12.dp))
                 OptionChipButton(
                     text = stringResource(R.string.comments),
                     isFilled = true,
+                    isSelected = isCommentsSelected,
                     onClick = { isCommentsSelected = !isCommentsSelected }
                 )
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageReactionScreen.kt
around lines 146 to 157, the OptionChipButton components toggle their own
internal state, which can cause inconsistency with the external isLikesSelected
and isCommentsSelected states. To fix this, remove internal state toggling
inside OptionChipButton and instead pass the external isLikesSelected and
isCommentsSelected as the isSelected parameter, controlling the selection state
externally. Also, update the onClick handlers to only toggle the external state
variables without changing internal state.

Comment on lines 166 to 176
text = stringResource(R.string.no_saved_reation),
style = typography.smalltitle_sb600_s18_h24,
color = colors.White
)
Text(
text = stringResource(R.string.save_first_reaction),
style = typography.feedcopy_r400_s14_h20,
color = colors.Grey,
modifier = Modifier.padding(top = 8.dp)
)
}
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

문자열 리소스 키 오타 의심: no_saved_reation → no_saved_reaction

리소스 키가 오타로 보입니다. 키와 실제 strings.xml 값을 정정해 주세요. 빌드/번역 관리 시 혼란을 줄입니다.

다음 스크립트로 키 사용처를 확인하고 일괄 수정하세요:


🏁 Script executed:

#!/bin/bash
# 'no_saved_reation' 키 검색
rg -n "no_saved_reation" -S

# strings.xml 내 정의 위치 확인
fd strings.xml | xargs -I{} rg -n "no_saved_reation" {}

Length of output: 405


리소스 키 오타 수정 필요: no_saved_reation → no_saved_reaction

strings.xml과 코드에서 해당 키를 일관되게 수정해 주세요. 그렇지 않으면 빌드나 번역 관리 시 혼란이 발생할 수 있습니다.

  • app/src/main/res/values/strings.xml (133행)
  • app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageReactionScreen.kt (166행)

수정 예시:

--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -133,7 +133,7 @@
-    <string name="no_saved_reation">아직 저장한 반응이 없어요</string>
+    <string name="no_saved_reaction">아직 저장한 반응이 없어요</string>
--- a/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageReactionScreen.kt
+++ b/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageReactionScreen.kt
@@ -166,7 +166,7 @@
-    text = stringResource(R.string.no_saved_reation),
+    text = stringResource(R.string.no_saved_reaction),
📝 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
text = stringResource(R.string.no_saved_reation),
style = typography.smalltitle_sb600_s18_h24,
color = colors.White
)
Text(
text = stringResource(R.string.save_first_reaction),
style = typography.feedcopy_r400_s14_h20,
color = colors.Grey,
modifier = Modifier.padding(top = 8.dp)
)
}
text = stringResource(R.string.no_saved_reaction),
style = typography.smalltitle_sb600_s18_h24,
color = colors.White
)
Text(
text = stringResource(R.string.save_first_reaction),
style = typography.feedcopy_r400_s14_h20,
color = colors.Grey,
modifier = Modifier.padding(top = 8.dp)
)
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageReactionScreen.kt
at line 166 and in app/src/main/res/values/strings.xml at line 133, the resource
key "no_saved_reation" is misspelled. Rename this key to "no_saved_reaction"
consistently in both the strings.xml file and the Kotlin code to prevent build
errors and translation issues.

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

🧹 Nitpick comments (3)
app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedBookViewModel.kt (3)

11-12: 공개 StateFlow 일원화는 잘 반영되었습니다. 다만 books는 굳이 open일 필요가 없습니다.

상태의 단일 소스를 보장하려면 서브클래스가 별도 Flow를 노출하지 못하게 booksfinal 유지가 더 안전합니다. _booksprotected로 둔 현재 구조라면 상태 수정은 서브클래스에서 충분히 가능합니다.

적용 예:

-    open val books: StateFlow<List<BookItem>> = _books
+    val books: StateFlow<List<BookItem>> = _books

14-19: init에서 open 메서드 호출은 지양 권장. Empty ViewModel을 위해 override no-op이 필요한 구조는 설계 신호입니다.

현재 구조는 부모 init에서 loadMockBooks()(open)를 호출하고, EmptySavedBookViewModel이 이를 no-op으로 덮어쓰는 방식입니다. 이는

  • 상속 초기화 순서에 취약(초기화 전 다형성 호출),
  • detekt(EmptyFunctionBlock) 경고 유발의 근본 원인,
  • 서브클래스가 상태 흐름을 분기시킬 여지를 만듭니다.

두 가지 대안 제안:

  • 옵션 A(권장, 변경 영향 최소): “사전 채움 여부”를 생성자 인자로 받습니다. 미리보기에서는 prepopulate = false로 호출해 빈 상태를 만들고, 더 이상 open 메서드 override가 필요 없습니다.
  • 옵션 B(대안): 기본 목업 데이터를 상수/팩토리로 분리하고, 호출 측(Preview/DI)에서 명시적으로 loadMockBooks()를 호출하도록 변경하여 부모 init에서 open 호출을 제거합니다.

옵션 A 예시:

open class SavedBookViewModel(
    prepopulate: Boolean = true
) : ViewModel() {

    protected val _books = MutableStateFlow<List<BookItem>>(emptyList())
    val books: StateFlow<List<BookItem>> = _books

    init {
        if (prepopulate) _books.value = DEFAULT_BOOKS
    }

    companion object {
        private val DEFAULT_BOOKS: List<BookItem> = listOf(
            // 기존 목업 아이템들
        )
    }
}

class EmptySavedBookViewModel : SavedBookViewModel(prepopulate = false)

옵션 B 예시(호출 측으로 위임):

open class SavedBookViewModel : ViewModel() {
    protected val _books = MutableStateFlow<List<BookItem>>(emptyList())
    val books: StateFlow<List<BookItem>> = _books

    // init { loadMockBooks() } 제거
    open fun loadMockBooks() { _books.value = DEFAULT_BOOKS }
}

또한, 현재 목업 리스트는 동일 아이템 블록이 반복됩니다. 간결성을 위해 생성 유틸을 쓰는 편이 유지보수에 유리합니다.

private fun mockBooks(count: Int = 8): List<BookItem> =
    List(count) { idx ->
        BookItem(
            id = idx + 1,
            title = "이기적 유전자",
            author = "리처드 도킨스",
            publisher = "을유문화사",
            imageUrl = null,
            isSaved = true
        )
    }

87-89: StateFlow 갱신은 update {} 사용을 권장합니다.

읽기→쓰기 패턴보다 update가 간결하고 잠재적 경합에도 더 안전합니다.

적용 예:

+import kotlinx.coroutines.flow.update
@@
-        _books.value = _books.value.map {
-            if (it.id == id) it.copy(isSaved = !it.isSaved) else it
-        }
+        _books.update { list ->
+            list.map { if (it.id == id) it.copy(isSaved = !it.isSaved) else it }
+        }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2e07f2 and 5b508b1.

📒 Files selected for processing (7)
  • app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt (4 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageReactionScreen.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt (5 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedBookViewModel.kt (2 hunks)
  • app/src/main/res/values/strings.xml (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • app/src/main/java/com/texthip/thip/ui/feed/screen/FeedOthersScreen.kt
🚧 Files skipped from review as they are similar to previous changes (5)
  • app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt
  • app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt
  • app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt
  • app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageReactionScreen.kt
  • app/src/main/res/values/strings.xml
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: rbqks529
PR: THIP-TextHip/THIP-Android#30
File: app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt:108-112
Timestamp: 2025-07-01T07:19:10.981Z
Learning: The user rbqks529 prefers to extract hardcoded UI strings to StringResource files (strings.xml) rather than keeping them inline in Compose components, which is a good practice for maintainability and localization in Android development.
Learnt from: rbqks529
PR: THIP-TextHip/THIP-Android#30
File: app/src/main/java/com/texthip/thip/ui/myPage/groupPage/GroupPageScreen.kt:34-37
Timestamp: 2025-07-01T07:19:01.239Z
Learning: GroupPageScreen의 MyPageViewModel은 임시 ViewModel로, 현재는 프로토타입 단계이므로 오류 처리 등의 추가 기능은 나중에 구현 예정입니다.
🪛 detekt (1.23.8)
app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedBookViewModel.kt

[warning] 94-94: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)

🔇 Additional comments (1)
app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedBookViewModel.kt (1)

5-5: 별도 조치 불필요

해당 import 변경은 리뷰 포인트 없음으로 스킵합니다.

Comment on lines +93 to 96
class EmptySavedBookViewModel : SavedBookViewModel() {
override fun loadMockBooks() {}

} No newline at end of file
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

detekt(EmptyFunctionBlock) 경고 해결: 최소 @Suppress 또는 주석 추가. 근본적으로는 부모 init 설계 개선이 바람직합니다.

현재 override는 의도적 no-op이므로 제거할 수 없습니다. 경고 억제를 위해 주석 혹은 @Suppress를 추가하세요. 상단 코멘트의 옵션 A/B를 채택하면 이 override 자체가 불필요해집니다.

빠른 수정:

-class EmptySavedBookViewModel : SavedBookViewModel() {
-    override fun loadMockBooks() {}
+class EmptySavedBookViewModel : SavedBookViewModel() {
+    @Suppress("EmptyFunctionBlock")
+    override fun loadMockBooks() { /* no-op for empty preview */ }
 }
📝 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
class EmptySavedBookViewModel : SavedBookViewModel() {
override fun loadMockBooks() {}
}
class EmptySavedBookViewModel : SavedBookViewModel() {
@Suppress("EmptyFunctionBlock")
override fun loadMockBooks() { /* no-op for empty preview */ }
}
🧰 Tools
🪛 detekt (1.23.8)

[warning] 94-94: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)

🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedBookViewModel.kt
around lines 93 to 96, the override of loadMockBooks is an intentional no-op but
triggers a detekt EmptyFunctionBlock warning. To fix this quickly, add either a
@Suppress annotation or a comment explaining the intentional empty override to
suppress the warning. Alternatively, consider refactoring the parent class
initialization to avoid needing this override.

@JJUYAAA JJUYAAA merged commit 223ae10 into THIP-TextHip:develop Aug 12, 2025
1 check was pending
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.

[UI] 데이터 없는 페이지 생성 [(THIP2025-234)]

1 participant