Skip to content

[REFACTOR] QA 나머지 수정#129

Merged
Nico1eKim merged 6 commits intoTHIP-TextHip:developfrom
rbqks529:refactor/#127_QA_5
Sep 5, 2025
Merged

[REFACTOR] QA 나머지 수정#129
Nico1eKim merged 6 commits intoTHIP-TextHip:developfrom
rbqks529:refactor/#127_QA_5

Conversation

@rbqks529
Copy link
Collaborator

@rbqks529 rbqks529 commented Sep 4, 2025

➕ 이슈 링크


🔎 작업 내용

  • 피드 더보기 위치 수정
  • 고객센터 링크 수정
  • 장르칩 UI수정
  • 책선택 카드 UI수정
  • 책선택 로직 수정
  • 피드 자세히 보기 태그 2줄로 가능하게 수정

📸 스크린샷


😢 해결하지 못한 과제

  • [] TASK


📢 리뷰어들에게

  • 참고해야 할 사항들을 적어주세요

Summary by CodeRabbit

  • 신기능

    • 피드 작성 시 선택한 책 정보가 자동으로 미리 채워집니다.
    • 책 카드에서 원격 이미지 로딩을 지원하고 변경 버튼 표시를 제어할 수 있습니다.
  • 개선

    • 장르 칩이 작은 화면에서 최대 2줄로 자동 줄바꿈됩니다.
    • 댓글의 태그 칩이 최대 2줄까지 줄바꿈됩니다.
    • 피드·저장된 피드 본문을 공백줄을 제외해 자연스럽게 줄단위 말줄임하고 필요 시 더보기 아이콘을 표시합니다.
    • 저자명은 한 줄 말줄임 처리되며 일부 화면의 가로 여백을 조정했습니다.
  • 기타

    • 고객센터 링크를 업데이트했고, 일부 상세 화면에서 우측 버튼(동작)이 제거되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented Sep 4, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

여러 컴포저블의 레이아웃·이미지·텍스트 처리 로직을 수정하고, FeedWriteViewModel에 도서 사전선택 메서드를 추가했으며, SearchBookDetailScreen 시그니처를 단순화하고 고객센터 URL을 교체했습니다.

Changes

Cohort / File(s) Summary
텍스트 트렁케이션 개선
app/src/main/java/com/texthip/thip/ui/feed/component/MyFeedCard.kt, app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt
비어있지 않은 행 기준으로 텍스트를 계산·잘라내는 processedText 도입, maxLines 제거, 잘림 판정 방식 변경 및 “…더보기” 표시 추가
도서 선택 흐름·CardInputBook 확장
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt, app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt, app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt, app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt
FeedWriteViewModel에 setPreselectedBookForFeed 추가, 네비게이션 호출을 해당 API로 교체, 선택 도서 UI를 CardInputBook으로 통합, CardInputBook에 imageUrlshowChangeButton 추가(AsyncImage 사용)
검색 상세 화면 파라미터 변경
app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt, app/src/main/java/com/texthip/thip/ui/navigator/navigations/SearchNavigation.kt
SearchBookDetailScreenonRightClick 파라미터 제거 및 호출부에서 제거된 파라미터 반영
칩/레이아웃 반응형 조정
app/src/main/java/com/texthip/thip/ui/common/buttons/GenreChipRow.kt, app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt, app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupDeadlineRoomSection.kt
GenreChipRow가 화면 폭 기준으로 1열/2열 배치, FeedCommentScreen의 태그를 FlowRow로 래핑(maxLines=2), GroupDeadlineRoomSection의 패딩 구조 변경
마이페이지 상수 변경
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt
고객센터 URL 상수 값 교체
프리뷰·imports·경미한 리팩터링
app/src/main/java/com/texthip/thip/ui/common/buttons/GenreChipRow.kt, 기타 파일들
GenreChipRow 프리뷰 추가/이름 변경, 필요한 import 추가/정리, 일부 컴포저블 내부 클릭/레이아웃 스페이서 조정

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Search as SearchBookDetailScreen
  participant Nav as SearchNavigation
  participant FeedVM as FeedWriteViewModel
  participant Write as FeedWriteScreen

  User->>Search: "피드 작성" 선택
  Search-->>Nav: onWriteFeedClick(bookDetail)
  Nav->>FeedVM: setPreselectedBookForFeed(isbn, title, author, imageUrl)
  FeedVM-->>FeedVM: selectedBook 업데이트, isBookPreselected=true
  Nav->>Write: 피드 작성 화면으로 이동
  Write-->>User: 사전선택 도서 표시
Loading
sequenceDiagram
  participant UI as CardInputBook
  participant Coil as AsyncImage
  participant Res as DrawableRes

  UI->>UI: imageUrl 체크
  alt imageUrl 존재
    UI->>Coil: AsyncImage 로드 (fallback/error = imageRes)
    Coil-->>UI: 이미지 렌더링
  else imageUrl 없음
    UI->>Res: imageRes로 기본 이미지 렌더링
  end
  UI->>UI: showChangeButton에 따라 버튼 표시/숨김
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
MyPage의 저장된 Content에서 네비게이션 로직 추가 [#123] SavedFeedCard 텍스트·아이콘 변경은 확인되나 네비게이션 연결 추가 여부는 본 diff에서 명확하지 않음.
사진 더보기 아이콘 수정 [#123] SavedFeedCard 및 MyFeedCard에 “…더보기” 아이콘/표시 로직이 추가됨.
책 조회 API cursor로 수정 / 검색 무한스크롤 수정 [#123] 페이징/리포지토리/API 호출의 cursor 기반 변경은 포함되어 있지 않음.
닉네임 입력/표시 수정 및 좋아요 반영 문제 [#123] 닉네임 입력 로직·좋아요 반영 관련 변경 없음.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
고객센터 URL 상수 변경 (app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt) #123의 명시적 목표와 연관된 작업이 아님—단순 URL 교체로 기능 요구와 무관.
SearchBookDetailScreen의 onRightClick 제거 (…/SearchBookDetailScreen.kt, …/SearchNavigation.kt) #123 요구사항 목록에 해당 시그니처 변경 언급 없음; 네비게이션 API 축소는 범위 외 변경으로 보임.
GenreChipRow의 화면폭 기반 2열 배치 및 프리뷰 추가 (…/ui/common/buttons/GenreChipRow.kt) #123에 칩 레이아웃 반응형 변경 요구 없음—UI 레이아웃 개선으로 범위 외.
Feed 카드의 라인 기반 트렁케이션 로직 추가 (…/MyFeedCard.kt, …/SavedFeedCard.kt) #123의 "사진 더보기 아이콘" 관련은 있으나, 라인 기반 트렁케이션 자체는 이슈 명세에서 요구하지 않음.

Possibly related issues

Possibly related PRs

Suggested labels

✅ OK merge

Suggested reviewers

  • Nico1eKim
  • JJUYAAA

Poem

작은 귀 폴짝, 칩들이 줄지어 앉아
표지 URL로 반짝 떠오르는 한 권의 책 📚
줄 끝에 살포시 놓인 말줄임표와 함께
토끼는 미리 책을 쥐고 글쓰러 hop! 🐇

✨ 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 or @coderabbit 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.

@rbqks529 rbqks529 requested a review from Copilot September 4, 2025 13:01
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors various UI components and logic as part of QA fixes, focusing on improving user experience through UI adjustments and code cleanup.

  • Removes unused onRightClick parameter from search book detail screen and navigation
  • Updates customer service URL and improves feed text truncation logic
  • Enhances book selection cards and genre chip layout for better responsive design

Reviewed Changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
SearchBookDetailScreen.kt Removes unused onRightClick parameter
SearchNavigation.kt Removes onRightClick callback implementation
FeedNavigation.kt Updates book selection logic to use new preselected method
MypageScreen.kt Updates customer service URL
SavedFeedCard.kt Improves text truncation logic for better display
GroupDeadlineRoomSection.kt Adjusts padding for better layout
GroupSelectBook.kt Replaces custom book display with reusable CardInputBook component
FeedWriteViewModel.kt Adds new method for preselecting books in feed
FeedCommentScreen.kt Changes tag layout from Row to FlowRow for multi-line support
MyFeedCard.kt Improves text truncation logic matching SavedFeedCard
CardInputBook.kt Enhances component to support AsyncImage and conditional change button
GenreChipRow.kt Adds responsive layout for small screens

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 55 to 73
val processedText = remember(feedItem.content) {
val lines = feedItem.content.split("\n")
val nonEmptyLines = mutableListOf<Int>() // 실제 텍스트가 있는 줄의 인덱스

lines.forEachIndexed { index, line ->
if (line.trim().isNotEmpty()) {
nonEmptyLines.add(index)
}
}

if (nonEmptyLines.size <= maxTextLines) {
// 실제 텍스트 줄이 제한보다 적으면 전체 표시
feedItem.content
} else {
// 실제 텍스트 줄이 제한을 초과하면, maxTextLines 번째 텍스트 줄까지만 표시
val lastAllowedLineIndex = nonEmptyLines[maxTextLines - 1]
lines.take(lastAllowedLineIndex + 1).joinToString("\n")
}
}
Copy link

Copilot AI Sep 4, 2025

Choose a reason for hiding this comment

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

This text processing logic is duplicated in MyFeedCard.kt. Consider extracting this into a shared utility function to avoid code duplication and improve maintainability.

Copilot uses AI. Check for mistakes.
import androidx.compose.ui.unit.dp
import com.texthip.thip.ui.theme.ThipTheme

@SuppressLint("ConfigurationScreenWidthHeight")
Copy link

Copilot AI Sep 4, 2025

Choose a reason for hiding this comment

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

Using @SuppressLint for ConfigurationScreenWidthHeight should be avoided. Consider using LocalDensity or other Compose-native approaches to handle screen size detection instead of suppressing the lint warning.

Copilot uses AI. Check for mistakes.
val configuration = LocalConfiguration.current
val screenWidthDp = configuration.screenWidthDp

if (screenWidthDp < 360) {
Copy link

Copilot AI Sep 4, 2025

Choose a reason for hiding this comment

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

The hardcoded value 360 should be extracted as a named constant to improve code readability and maintainability.

Copilot uses AI. Check for mistakes.
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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
app/src/main/java/com/texthip/thip/ui/common/buttons/GenreChipRow.kt (1)

24-29: modifier 파라미터를 간격(Spacer)로 재사용하는 API 설계 냄새

modifier는 보통 레이아웃 수식용으로 소비자에게 열어두고, 간격은 별도 파라미터로 받는 것이 안전합니다. 현재 구현은 호출부가 modifier에 다른 연산을 추가할 경우 Spacer에도 전파되어 예기치 않은 배치를 유발할 수 있습니다.

-fun GenreChipRow(
-    modifier: Modifier = Modifier.width(4.dp),
+import androidx.compose.ui.unit.Dp
+
+fun GenreChipRow(
+    modifier: Modifier = Modifier,
+    chipSpacing: Dp = 4.dp,
     genres: List<String>,
     selectedIndex: Int,
     onSelect: (Int) -> Unit,
     horizontalArrangement: Arrangement.Horizontal = Arrangement.Center
 ) {
...
-                    if (idx < 2) {
-                        Spacer(modifier = modifier)
+                    if (idx < 2) {
+                        Spacer(modifier = Modifier.width(chipSpacing))
                     }
...
-                    if (relativeIdx < genres.drop(3).size - 1) {
-                        Spacer(modifier = modifier)
+                    if (relativeIdx < genres.drop(3).size - 1) {
+                        Spacer(modifier = Modifier.width(chipSpacing))
                     }
...
-                if (idx < genres.size - 1) {
-                    Spacer(modifier = modifier)
+                if (idx < genres.size - 1) {
+                    Spacer(modifier = Modifier.width(chipSpacing))
                 }

Also applies to: 57-58, 83-84, 109-110

app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt (1)

80-81: 텍스트 영역이 지나치게 좁습니다 — weight(.1f) → 1f 권장

.weight(.1f)로 인해 제목/저자 텍스트가 버튼에 밀려 과도하게 잘릴 가능성이 큽니다. 일반적으로 가변 폭 콘텐츠에는 1f를 부여해 여유 공간을 충분히 차지하도록 하는 편이 안전합니다.

다음과 같이 수정 제안드립니다.

-        Column(
-            modifier = Modifier.weight(.1f),
+        Column(
+            modifier = Modifier.weight(1f),
             verticalArrangement = Arrangement.Top
         ) {
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (1)

188-190: 사용자가 책을 다시 선택할 때 프리셀렉트 상태를 해제해야 합니다

selectBook은 현재 selectedBook만 갱신하고 isBookPreselected를 건드리지 않습니다. 기록장/상세에서 넘어온 프리셀렉트 상태가 남아 버튼 숨김 등 UI에 영향을 줄 수 있습니다.

-    fun selectBook(book: BookData) {
-        updateState { it.copy(selectedBook = book) }
-    }
+    fun selectBook(book: BookData) {
+        updateState { it.copy(selectedBook = book, isBookPreselected = false) }
+    }
🧹 Nitpick comments (15)
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt (1)

195-200: 외부 URL 열기 시 예외 처리 및 콜백 일관화 제안

브라우저 미설치 등으로 ActivityNotFoundException이 발생할 수 있습니다. 또한 onCustomerServiceClick 파라미터가 선언되어 있으나 미사용입니다. 콜백을 사용하거나 예외 처리를 추가해 안정성을 높여주세요.

다음과 같이 최소 수정으로 예외를 흡수할 수 있습니다:

- onClick = {
-     val intent =
-         Intent(Intent.ACTION_VIEW, URL_CUSTOMER_SERVICE.toUri())
-     context.startActivity(intent)
- }
+ onClick = {
+     runCatching {
+         val intent = Intent(Intent.ACTION_VIEW, URL_CUSTOMER_SERVICE.toUri())
+             .addCategory(Intent.CATEGORY_BROWSABLE)
+         context.startActivity(intent)
+     }.onFailure {
+         // TODO: 사용자에게 브라우저 없음 안내(토스트/다이얼로그)
+     }
+ }

또는, 아래처럼 콜백을 사용해 화면 외부에서 열기 로직을 통일할 수도 있습니다:

- onClick = { /* open url */ }
+ onClick = onCustomerServiceClick
app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupDeadlineRoomSection.kt (1)

124-126: 가로 인셋 상수화로 일관성 확보

제목과 컨텐츠 영역 모두 20.dp를 반복 사용합니다. 상수화하여 차후 조정 비용을 낮추고 일관성을 확보하세요.

변경 예:

@@
-            val horizontalPadding = sideMargin
+            val horizontalPadding = sideMargin
+            val horizontalInset = 20.dp
@@
-                        Text(
+                        Text(
                             text = sectionTitle,
                             style = typography.title_b700_s20_h24,
-                            color = colors.White,
-                            modifier = Modifier.padding(horizontal = 20.dp)
+                            color = colors.White,
+                            modifier = Modifier.padding(horizontal = horizontalInset)
                         )
@@
-                        Column(
+                        Column(
                             verticalArrangement = Arrangement.spacedBy(20.dp),
                             modifier = Modifier
                                 .fillMaxWidth()
                                 .height(584.dp)
-                                .padding(horizontal = 20.dp)
+                                .padding(horizontal = horizontalInset)
                         ) {

Also applies to: 141-142

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

336-343: 태그 2줄 래핑 구현 적합 — 숨겨진 태그에 대한 피드백(더보기/접근성) 고려

FlowRow(maxLines = 2)로 요구사항 충족됩니다. 다만 3줄 이상일 때 초과 태그가 조용히 사라집니다. 사용자가 더 있다는 사실을 인지할 수 있도록 “+N”/“더보기” 칩 추가 또는 보이스오버용 semantics로 “일부 태그가 숨겨졌음” 안내를 고려해주세요.

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

50-69: 대용량 텍스트에서 불필요한 전체 스캔 최소화

nonEmptyLines를 끝까지 채우지 말고 maxTextLines+1에 도달하면 조기 종료하면 비용을 줄일 수 있습니다. 현재 구현도 동작은 맞지만 긴 콘텐츠에서 불필요한 연산이 생깁니다.


135-145: 더보기 아이콘 사이즈/스타일 일관성

SavedFeedCard의 더보기 아이콘과 사이즈/정렬이 다릅니다(여기선 width/height 고정). 동일 컴포넌트 간 UI 일관성을 위해 고정 사이즈 제거 또는 공통 컴포저블로 추출을 권장합니다.

-                Image(
+                Image(
                     painter = painterResource(id = R.drawable.ic_text_more),
                     contentDescription = null,
-                    modifier = Modifier
-                        .align(Alignment.BottomEnd)
-                        .width(80.dp)
-                        .height(24.dp)
+                    modifier = Modifier.align(Alignment.BottomEnd)
                 )
app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (1)

51-73: 중복 로직 공통화 제안

MyFeedCard와 동일한 잘림 로직이 중복됩니다. 재사용 가능한 유틸로 추출하면 유지보수가 쉬워집니다.

예: TextTruncation.kt

package com.texthip.thip.ui.common.text

fun truncateByNonEmptyLines(content: String, maxTextLines: Int): Pair<String, Boolean> {
    val lines = content.split("\n")
    var count = 0
    var lastIdx = -1
    for ((i, line) in lines.withIndex()) {
        if (line.isNotBlank()) {
            count++
            lastIdx = i
            if (count == maxTextLines) break
        }
    }
    if (count <= maxTextLines && lines.drop(lastIdx + 1).none { it.isNotBlank() }) {
        return content to false
    }
    val end = if (lastIdx >= 0) lastIdx + 1 else 0
    return lines.take(end).joinToString("\n") to (content.length != lines.take(end).sumOf { it.length } + end - 1)
}

사용처:

val (processedText, isTextTruncated) = remember(feedItem.content, maxTextLines) {
    truncateByNonEmptyLines(feedItem.content, maxTextLines)
}

원하시면 PR에 포함해 드릴게요.

app/src/main/java/com/texthip/thip/ui/common/buttons/GenreChipRow.kt (2)

30-87: 소형 화면에서 2줄 하드코딩은 스케일에 취약 — FlowRow/Wrap으로 단순화 권장

장르 수가 늘면 두 번째 Row가 넘칩니다. 화면 폭 분기 + take/drop 대신 FlowRow(또는 ExperimentalLayoutApi)로 자동 줄바꿈을 사용하면 유지보수/응답성이 좋아집니다.

예시:

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun GenreChipRow(
    modifier: Modifier = Modifier,
    chipSpacing: Dp = 4.dp,
    genres: List<String>,
    selectedIndex: Int,
    onSelect: (Int) -> Unit,
) {
    FlowRow(
        modifier = modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(chipSpacing, Alignment.CenterHorizontally),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        genres.forEachIndexed { idx, genre ->
            OptionChipButton(
                text = genre,
                isFilled = true,
                isSelected = selectedIndex == idx,
                onClick = { onSelect(if (selectedIndex == idx) -1 else idx) }
            )
        }
    }
}

43-55: clip(RoundedCornerShape) 중복 적용

OptionChipButton 내부에서 shape/border를 이미 처리합니다. 외부에서 clip을 또 적용하면 불필요한 레이어가 생기고 ripple 경계가 이중 관리됩니다. 제거를 권장합니다.

-                    OptionChipButton(
-                        modifier = Modifier
-                            .clip(RoundedCornerShape(20.dp)),
+                    OptionChipButton(
+                        modifier = Modifier,
                         text = genre,
...
-                    OptionChipButton(
-                        modifier = Modifier
-                            .clip(RoundedCornerShape(20.dp)),
+                    OptionChipButton(
+                        modifier = Modifier,
                         text = genre,
...
-                OptionChipButton(
-                    modifier = Modifier
-                        .clip(RoundedCornerShape(20.dp)),
+                OptionChipButton(
+                    modifier = Modifier,
                     text = genre,

Also applies to: 69-81, 95-107

app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt (4)

55-62: 네트워크 이미지 로딩 UX 보완 — placeholder 추가

로딩 중 깜빡임을 줄이려면 placeholder를 지정해 주는 것이 좋습니다. 현재 fallback/error만 설정되어 있어 최초 로딩 시 빈 영역이 보일 수 있습니다.

             AsyncImage(
                 model = imageUrl,
                 contentDescription = null,
                 modifier = Modifier.fillMaxSize(),
                 contentScale = ContentScale.Crop,
+                placeholder = imageRes?.let { painterResource(id = it) },
                 fallback = imageRes?.let { painterResource(id = it) },
                 error = imageRes?.let { painterResource(id = it) }
             )

57-57: 접근성(a11y) 개선 — contentDescription 제공

표지가 정보 전달에 유의미하다면 contentDescription을 제공해 스크린리더 사용성을 높이는 것이 좋습니다. 단순 장식이라면 null 유지도 가능하지만, 책 표지라면 제목 포함 설명을 권장합니다.

새 문자열 리소스 추가 전제로 예시를 제안드립니다.

-                    contentDescription = null,
+                    contentDescription = stringResource(R.string.cd_book_cover_with_title, title),
-                        contentDescription = null,
+                        contentDescription = stringResource(R.string.cd_book_cover_with_title, title),

리소스 예: cd_book_cover_with_title = "%1$s 표지".

원하시면 문자열 리소스 추가 PR 스니펫도 제공하겠습니다.

Also applies to: 68-68


103-107: 버튼 고정 너비 제거로 현지화/가독성 대응

고정 폭(49dp)은 언어에 따라 텍스트가 잘릴 수 있습니다. 콘텐츠 기반 너비로 전환하고 최소 폭만 제한하는 편이 안전합니다.

             OutlinedButton(
                 modifier = Modifier
-                    .size(width = 49.dp, height = 33.dp)
+                    .height(33.dp)
+                    .widthIn(min = 49.dp)
                     .align(Alignment.Bottom),

50-52: 자잘한 스타일링 제안 — 모서리 클리핑으로 일관된 썸네일 표현

썸네일을 카드와 어울리게 보이도록 살짝 라운드를 주는 것을 고려해 보세요.

         Box(
             modifier = Modifier
-                .size(width = 60.dp, height = 80.dp)
+                .size(width = 60.dp, height = 80.dp)
+                .clip(RoundedCornerShape(6.dp))
         ) {

필요 import:

import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.draw.clip
app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt (1)

66-69: i18n: contentDescription 하드코딩 문자열 리소스화

"검색 아이콘" 하드코딩 대신 문자열 리소스를 사용해 현지화 일관성을 유지하세요.

-                Icon(
-                    painter = painterResource(id = R.drawable.ic_search),
-                    contentDescription = "검색 아이콘",
-                    tint = colors.Grey01
-                )
+                Icon(
+                    painter = painterResource(id = R.drawable.ic_search),
+                    contentDescription = stringResource(R.string.cd_search_icon),
+                    tint = colors.Grey01
+                )

리소스 키가 없다면 추가 스니펫 제공 가능합니다.

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

192-205: 중복 제거: 프리셀렉트/핀 기록/수정 데이터에서 BookData 구성 로직 통합

setPreselectedBookForFeed, setPinnedRecord, setEditData, loadFeedForEdit에 동일한 BookData 생성 코드가 반복됩니다. 헬퍼로 추출해 유지보수성을 높이세요.

-    fun setPreselectedBookForFeed(isbn: String, bookTitle: String, bookAuthor: String, bookImageUrl: String) {
-        val preselectedBook = BookData(
-            title = bookTitle,
-            imageUrl = bookImageUrl,
-            author = bookAuthor,
-            isbn = isbn
-        )
-        updateState {
-            it.copy(
-                selectedBook = preselectedBook,
-                isBookPreselected = true
-            )
-        }
-    }
+    fun setPreselectedBookForFeed(isbn: String, bookTitle: String, bookAuthor: String, bookImageUrl: String) {
+        val preselectedBook = buildBookData(isbn, bookTitle, bookAuthor, bookImageUrl)
+        updateState { it.copy(selectedBook = preselectedBook, isBookPreselected = true) }
+    }

헬퍼 추가(파일 내 적절한 위치에):

private fun buildBookData(isbn: String, title: String, author: String, imageUrl: String) =
    BookData(title = title, imageUrl = imageUrl, author = author, isbn = isbn)

그리고 setPinnedRecord/setEditData/loadFeedForEdit에서도 동일 헬퍼 사용 권장.


109-112: 하드코딩 에러 메시지 → 리소스 사용

"피드 정보를 불러올 수 없습니다."는 하드코딩되어 있습니다. 기존처럼 StringResourceProvider를 사용해 i18n 일관성을 유지하세요.

-                            it.copy(
-                                isLoading = false,
-                                errorMessage = "피드 정보를 불러올 수 없습니다."
-                            )
+                            it.copy(
+                                isLoading = false,
+                                errorMessage = stringResourceProvider.getString(R.string.error_feed_detail_load_failed)
+                            )

해당 문자열 리소스가 없다면 키/값 제안드릴게요.

📜 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 0c5d8c6 and 6e9195b.

📒 Files selected for processing (12)
  • app/src/main/java/com/texthip/thip/ui/common/buttons/GenreChipRow.kt (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt (5 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 (3 hunks)
  • app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupDeadlineRoomSection.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/navigations/SearchNavigation.kt (0 hunks)
  • app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt (0 hunks)
💤 Files with no reviewable changes (2)
  • app/src/main/java/com/texthip/thip/ui/navigator/navigations/SearchNavigation.kt
  • app/src/main/java/com/texthip/thip/ui/search/screen/SearchBookDetailScreen.kt
🧰 Additional context used
🧬 Code graph analysis (4)
app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt (1)
app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt (1)
  • CardInputBook (33-113)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedWriteViewModel.kt (3)
app/src/main/java/com/texthip/thip/ui/group/makeroom/viewmodel/GroupMakeRoomViewModel.kt (1)
  • updateState (47-49)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (1)
  • updateState (74-76)
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedDetailViewModel.kt (1)
  • updateState (40-42)
app/src/main/java/com/texthip/thip/ui/common/buttons/GenreChipRow.kt (1)
app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt (1)
  • OptionChipButton (30-93)
app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt (1)
app/src/main/java/com/texthip/thip/ui/common/buttons/OutlinedButton.kt (1)
  • OutlinedButton (30-72)
🔇 Additional comments (8)
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt (1)

45-45: 고객센터 URL 교체 LGTM

새 링크로의 치환 자체는 문제 없습니다. 기존 인텐트 동작과의 호환성도 유지됩니다.

app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupDeadlineRoomSection.kt (1)

116-116: 컨테이너 수평 패딩 제거 적절 — 내부 컴포넌트로 인셋 이관 확인 요청

상위 Box의 가로 패딩 제거는 합리적입니다. 다만 HorizontalPager(contentPadding = PaddingValues(horizontal = 30.dp))cardWidth 계산(양옆 30.dp) + 내부 가로 인셋(20.dp) 조합이 의도한 여백 체계인지 소형 디바이스에서 한번만 확인 부탁드립니다. 과도/이중 인셋이 없는지 UX 검증 필요합니다.

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

150-150: FlowRow 사용을 위한 Opt-in 적절 — Compose 버전 호환성만 재확인

@OptIn(ExperimentalLayoutApi::class) 추가는 타당합니다. 현재 모듈의 Compose 버전이 해당 API(FlowRow의 maxLines)를 지원하는지 build.gradle 의존성 버전을 한 번만 재확인해주세요.

app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt (1)

38-41: API 시그니처 변경 확인 및 호출부 검토
CardInputBookimageUrl, imageRes, showChangeButton, onChangeClick 파라미터가 추가되었습니다. 기본값 대신 의도한 값을 전달해야 하는 호출부를 확인해주세요:

  • app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt (프리뷰, 124행)
  • app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt (78행)
app/src/main/java/com/texthip/thip/ui/group/makeroom/component/GroupSelectBook.kt (1)

78-85: 중복 제거 잘했습니다 — 공용 CardInputBook 활용 👍

선택 도서 표현을 CardInputBook으로 통합해 중복 레이아웃과 이미지 로딩 로직이 깔끔해졌습니다. showChangeButton = !isBookPreselected 처리는 의도에 부합합니다.

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

571-582: 네이밍/정합성 확인 요청 — imageUris 전송 조건

신규 작성에서만 이미지 추가가 가능하고 수정 모드에서는 remainImageUrls만 전달합니다. 서버가 “수정 모드에서 새 이미지 업로드 불가” 정책인지, 그리고 빈 imageUris 전달 시 서버가 문제 없이 처리하는지 확인 바랍니다.

테스트 시나리오:

  • 새 글: 이미지 3장 업로드 → 성공/갯수 제한 동작 확인
  • 수정 글: 기존 2장 유지 + 1장 삭제 → remainImageUrls만 전달되어 정상 반영되는지 확인
app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt (2)

154-159: 신규 API 연계 LGTM

setPreselectedBookForFeed 호출로 프리셀렉트 플로우가 명확해졌습니다. bookImageUrl ?: "" 처리도 NPE 방지에 적절합니다.


110-161: 이전 selectBook(BookData(...)) 호출부 제거 확인
코드베이스 전반에 해당 호출부가 더 이상 존재하지 않음을 확인했습니다.

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

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

49-58: 이전 상태 의존성 문제 해결, 굿

remember(feedItem.content, hasImages)로 재계산 키 보강했고, 잘림 여부를 파생값으로 전환한 흐름도 좋습니다.

🧹 Nitpick comments (2)
app/src/main/java/com/texthip/thip/ui/feed/component/MyFeedCard.kt (2)

50-51: remember 키는 hasImages 대신 maxTextLines 사용 권장

의도 전달과 미래 변경(줄 수 정책 변경) 안전성을 위해 직접 의존하는 파생값을 키로 두는 편이 명확합니다.

-    val processedText = remember(feedItem.content, hasImages) {
+    val processedText = remember(feedItem.content, maxTextLines) {

133-143: 접근성: “더보기” 조작 힌트 노출

이미지는 장식이므로 contentDescription = null 유지해도 되지만, 컨테이너 클릭에 onClickLabel을 부여하면 스크린리더 힌트가 좋아집니다. 리소스가 없다면 추가 부탁드립니다.

예: Box의 clickable에 라벨 추가

  • Modifier.clickable(onClickLabel = stringResource(R.string.read_more)) { onContentClick() }
    리소스 키(R.string.read_more)는 프로젝트 컨벤션에 맞게 조정 필요.
📜 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 6e9195b and cf40ad0.

📒 Files selected for processing (3)
  • app/src/main/java/com/texthip/thip/ui/feed/component/MyFeedCard.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/makeroom/viewmodel/GroupMakeRoomViewModel.kt (0 hunks)
  • app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt (2 hunks)
💤 Files with no reviewable changes (1)
  • app/src/main/java/com/texthip/thip/ui/group/makeroom/viewmodel/GroupMakeRoomViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt

Comment on lines +60 to +67
if (nonEmptyLines.size <= maxTextLines) {
// 실제 텍스트 줄이 제한보다 적으면 전체 표시
feedItem.content
} else {
// 실제 텍스트 줄이 제한을 초과하면, maxTextLines 번째 텍스트 줄까지만 표시
val lastAllowedLineIndex = nonEmptyLines[maxTextLines - 1]
lines.take(lastAllowedLineIndex + 1).joinToString("\n")
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

개행(‘\n’) 기준 자르기는 시각적 줄 수를 반영하지 않아 긴 단락이 통째로 노출됩니다

프리뷰의 feed1(라인 218)의 긴 단일 문단처럼 개행이 없으면 현재 로직은 전체 본문을 그대로 출력해 “…더보기”도 표시되지 않습니다. 실제 UI 줄바꿈(폭 기준)을 반영하려면 TextmaxLinesonTextLayout.hasVisualOverflow를 함께 사용해 시각적 줄수 기반 클램핑을 해야 합니다. 아래처럼 최소 수정으로 보강해 주세요.

적용 diff 1 (잘림 상태 계산 보강):

-    // 잘림 여부는 파생 값으로 계산
-    val isTextTruncated = processedText != feedItem.content
+    // 시각적 줄바꿈까지 고려한 잘림 여부 상태
+    var isTextTruncated by remember(processedText, maxTextLines) {
+        mutableStateOf(processedText != feedItem.content)
+    }

적용 diff 2 (Text에 시각적 클램핑 추가):

             Text(
                 text = processedText,
                 style = typography.feedcopy_r400_s14_h20,
                 color = colors.White,
                 modifier = Modifier
                     .fillMaxWidth()
                     .padding(top = 16.dp),
+                maxLines = maxTextLines,
+                overflow = TextOverflow.Clip,
+                onTextLayout = { result ->
+                    isTextTruncated = result.hasVisualOverflow || (processedText != feedItem.content)
+                }
             )

추가 import:

import androidx.compose.ui.text.style.TextOverflow
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/component/MyFeedCard.kt around
lines 60-67, the current logic slices content by '\n' which doesn't reflect
visual line-wrapping; replace this with a visual-line-aware clamp: keep the
existing newline-based fallback but render the preview Text with maxLines =
maxTextLines and provide onTextLayout = { if (it.hasVisualOverflow) set a flag
to indicate overflow } so you can decide whether to show the full text or the
clamped preview; add TextOverflow import and set overflow =
TextOverflow.Ellipsis (or Clip) on the Text; update the boolean/previewText
selection to prefer the visual-clamped output when onTextLayout reports
overflow, falling back to the newline-sliced string only when needed.

@Nico1eKim Nico1eKim merged commit 60ff42c into THIP-TextHip:develop Sep 5, 2025
1 check passed
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.

3 participants