Skip to content

[UI] 모임 페이지 구현 완료#30

Merged
Nico1eKim merged 33 commits intoTHIP-TextHip:developfrom
rbqks529:UI-#25]
Jul 2, 2025
Merged

[UI] 모임 페이지 구현 완료#30
Nico1eKim merged 33 commits intoTHIP-TextHip:developfrom
rbqks529:UI-#25]

Conversation

@rbqks529
Copy link
Collaborator

@rbqks529 rbqks529 commented Jun 30, 2025

➕ 이슈 링크


🔎 작업 내용

  • 어떤 부분이 구현되었는지 설명해주세요

  • 내 모임방(진행 중, 모집 중)

  • 네비게이션과 연결된 메인 모임 페이지

  • 모임방 페이지(참여, 참여 취소, 모집 마감)

  • 주연이가 부탁한 컴포넌트 수정


📸 스크린샷

**내 모임방 (진행 중)** ![image](https://github.com/user-attachments/assets/e6725eb4-a6ef-4b1d-9fe8-cdfb8829fdde)

내 모임방 (모집 중)
image

네비게이션과 연결된 메인 모임 페이지
image

모임방 페이지(참여, 참여 취소, 모집 마감)
image

컴포넌트 수정
FormTextFieldDefault
image

CardAlarm
image

프로젝트 구조
image


😢 해결하지 못한 과제

  • 모임방 참여 페이지 배경

  • 페이지에서 컴포넌트를 눌렀을 때 click event(화면 연결)

  • CardAlarm에서 ~시간 전과 날짜를 보여주는 부분은 서버에서 어떻게 데이터를 보내주는지에 따라서 많이 바뀔 것 같아서 검토가 필요할듯 합니당


📢 리뷰어들에게

  • 기존의 form이나 버튼 같은 component도 이름을 수정한게 있어서 유심히 봐주시면 감사할거 같습니다~

Summary by CodeRabbit

  • 신규 기능

    • 다양한 그룹/모임방 UI 컴포넌트(카드, 필터, 검색창, 장르 선택, 페이저, 섹션 헤더 등) 및 화면이 추가되었습니다.
    • 그룹/모임방 상세, 목록, 추천, 마이 그룹 등 화면에 대한 미리보기와 필터 기능이 제공됩니다.
    • 그룹/모임방 관련 더미 데이터, 뷰모델, Enum 등이 추가되어 샘플 데이터 기반 UI 확인이 가능합니다.
  • 기능 개선 및 변경

    • 기존 카드, 버튼, 입력 필드 등 공통 컴포넌트에 선택 상태, 배지, 제한, 명칭 등 다양한 옵션이 추가·수정되었습니다.
    • 일부 컴포넌트 및 함수명이 일관된 네이밍으로 변경되었습니다.
    • 저자, 출판사, 도서 소개 등의 텍스트가 문자열 리소스로 변경되어 다국어 지원이 강화되었습니다.
  • 문서화 및 문자열

    • 카드, 그룹, 모임방 관련 다양한 문자열 리소스가 추가 및 일부 삭제되어 UI의 다국어 지원 및 가독성이 향상되었습니다.
  • 스타일 및 테마

    • 새로운 장르 색상 등 컬러 테마가 추가되었습니다.
  • 환경설정 및 라이브러리

    • 그룹/모임방 UI 구현을 위한 페이저 등 라이브러리 의존성이 추가되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented Jun 30, 2025

"""

Walkthrough

이 변경사항은 "모임" 페이지 및 관련 그룹/모임방 UI의 대규모 구현을 포함합니다. 여러 새로운 컴포저블 UI 컴포넌트, 데이터 모델, 뷰모델, 화면 및 프리뷰가 추가되었으며, 기존 컴포저블과 리소스의 네이밍 및 파라미터가 개선되었습니다. 빌드 설정과 테마, 문자열 리소스도 확장되었습니다.

Changes

파일/경로 그룹 변경 요약
.idea/deploymentTargetSelector.xml 런 설정 이름을 "BookTopAppBarPreview"에서 "app"으로 변경
app/build.gradle.kts, gradle/libs.versions.toml 네비게이션 및 accompanist pager 라이브러리 의존성 추가 및 버전 관리
app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt isSelected 파라미터 추가로 외부 선택 상태 제어 지원
app/src/main/java/com/texthip/thip/ui/common/cards/CardAlarm.kt NotificationCardCardAlarm으로 함수명 변경, 배지텍스트/색상 파라미터 추가, 프리뷰 확장
app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt, CardItemRoomSmall.kt 새로운 방 카드 UI 컴포저블 2종 및 프리뷰 추가
app/src/main/java/com/texthip/thip/ui/common/cards/CardRoomBook.kt DetailedDarkCardCardRoomBook으로 함수명 변경, 저자/출판사 텍스트 분리 및 현지화 적용
app/src/main/java/com/texthip/thip/ui/common/forms/FormTextFieldDefault.kt 입력 필드 함수명 변경 및 글자수 제한/아이콘 표시 기능 추가, 프리뷰 확장
app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt, app/src/main/java/com/texthip/thip/ui/myPage/MyPageScreen.kt MyPageScreen import 경로 및 패키지 변경, Modifier 사용 명시적 Companion 참조로 변경
app/src/main/java/com/texthip/thip/ui/theme/Color.kt genreColor 컬러 및 ThipColors 속성 추가
app/src/main/res/values/strings.xml 카드/모임 관련 다수의 문자열 리소스 추가 및 일부 제거
app/src/main/java/com/texthip/thip/ui/common/cards/CardInputBook.kt 저자 텍스트를 문자열 리소스 기반으로 변경
app/src/main/java/com/texthip/thip/ui/group/myroom/component/… 그룹/모임방 UI용 신규 컴포저블(ChipRow, DeadlineSection, MainCard, FilterRow, SectionHeader, Pager, SearchTextField, SimplePagerIndicator 등) 및 프리뷰 대량 추가
app/src/main/java/com/texthip/thip/ui/group/myroom/mock/… 그룹/모임방/카드/책/섹션 데이터 모델 및 뷰모델 신규 생성, 목업 데이터 포함
app/src/main/java/com/texthip/thip/ui/group/myroom/screen/… 모임방 리스트 및 상세 화면 컴포저블 및 프리뷰 추가
app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt GroupScreen UI 전체 리팩토링, 뷰모델 연동, 프리뷰 추가

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant GroupScreen
    participant GroupViewModel
    participant UIComponents

    User->>GroupScreen: 화면 진입
    GroupScreen->>GroupViewModel: myGroups, roomSections StateFlow 수집
    GroupScreen->>UIComponents: MyGroupSectionHeader, GroupPager, GroupRoomDeadlineSection 등 렌더링
    User->>GroupScreen: FAB 클릭/카드 클릭/필터 선택
    GroupScreen->>GroupViewModel: onFabClick/onMyGroupCardClick/onRoomCardClick 호출
    GroupViewModel-->>GroupScreen: 상태 업데이트 (필요 시)
Loading

Assessment against linked issues

Objective Addressed Explanation
내 모임방 UI 구현 (#25)
모임방 페이지 UI 구현 (#25)
MyPageScreen 구현 (#25)

Assessment against linked issues: Out-of-scope changes

(해당 변경사항에서 범위를 벗어난 기능적 코드 변경은 발견되지 않았습니다.)

Possibly related PRs

  • UI/#18 form, card #22: NotificationCardCardAlarm 리팩토링과 파라미터 확장 등 동일 파일/컴포넌트의 직접적인 후속 작업으로 보임.

Suggested labels

✅ OK merge

Suggested reviewers

  • Nico1eKim

Poem

🐇
새로운 방, 카드, 모임의 춤
칩과 페이저, 필터의 숨
검은 배경에 꿈을 그려
토끼는 UI에 귀를 세워
모두가 모이는 그날까지
코드 속에 사랑을 담아
Hop hop, merge의 기쁨과 함께!

"""


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f46d9db and d70deb5.

📒 Files selected for processing (2)
  • app/src/main/java/com/texthip/thip/ui/theme/Color.kt (3 hunks)
  • app/src/main/res/values/strings.xml (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/java/com/texthip/thip/ui/theme/Color.kt
  • app/src/main/res/values/strings.xml
✨ Finishing Touches
  • 📝 Generate Docstrings

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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: 14

🧹 Nitpick comments (21)
app/src/main/java/com/texthip/thip/ui/theme/Color.kt (1)

20-20: 네이밍 컨벤션 불일치를 수정해주세요.

다른 색상 상수들은 대문자로 시작하는데(Purple, NeonGreen, Red 등) genreColor만 소문자로 시작하여 일관성이 부족합니다.

-val genreColor = Color(0xFFB5B35D)
+val GenreColor = Color(0xFFB5B35D)
app/src/main/java/com/texthip/thip/ui/myPage/groupPage/SimplePagerIndicator.kt (1)

16-39: 기본 구현은 올바르지만 개선 사항을 고려해보세요.

페이지 인디케이터의 기본 기능은 잘 구현되었습니다. 하지만 다음 개선사항을 고려해보세요:

 @Composable
 fun SimplePagerIndicator(
     pageCount: Int,
     currentPage: Int,
     modifier: Modifier = Modifier
 ) {
+    if (pageCount <= 0) return
+    
     Row(
         modifier = modifier,
         horizontalArrangement = Arrangement.Center,
         verticalAlignment = Alignment.CenterVertically
     ) {
         repeat(pageCount) { index ->
             Box(
                 modifier = Modifier
                     .padding(horizontal = 6.dp)
                     .size(4.dp)
                     .background(
-                        color = if (currentPage == index) colors.White else colors.Grey02,
+                        color = if (currentPage.coerceIn(0, pageCount - 1) == index) colors.White else colors.Grey02,
                         shape = RoundedCornerShape(50)
                     )
             )
         }
     }
 }

선택적으로 페이지 전환 시 부드러운 애니메이션을 추가하는 것도 고려해보세요.

app/src/main/java/com/texthip/thip/ui/myPage/groupRoom/BookData.kt (1)

5-11: 책 정보 모델링이 적절히 이루어졌습니다.

BookData 클래스가 책의 기본 정보를 잘 모델링하고 있습니다. 현재 요구사항에 적합한 간단하고 명확한 구조입니다.

향후 확장성을 고려한다면 다음 개선사항을 고려해보세요:

  • imageRes@DrawableRes 어노테이션으로 타입 안전성 강화
  • description에 대한 길이 제한 고려
  • ISBN이나 출간일 등 추가 메타데이터 필드
+import androidx.annotation.DrawableRes
+
 data class BookData(
     val title: String,
     val author: String,
     val publisher: String,
     val description: String,
-    val imageRes: Int = R.drawable.bookcover_sample
+    @DrawableRes val imageRes: Int = R.drawable.bookcover_sample
 )
app/src/main/res/values/strings.xml (1)

56-56: 오타: groupPeroidgroupPeriod
Period의 철자가 잘못되었습니다. XML 키와 code-reference 모두 수정해야 합니다.

-    <string name="groupPeroid">모임 활동 기간</string>
+    <string name="group_period">모임 활동 기간</string>
.idea/deploymentTargetSelector.xml (1)

5-5: IDE 설정 파일 커밋 여부 재검토 필요
.idea/* 파일은 개발자별 설정이 달라 충돌을 유발할 수 있습니다. 팀에서 명시적으로 버전 관리하기로 합의된 파일인지 확인해주세요.

app/src/main/java/com/texthip/thip/ui/myPage/myGroup/FilterRow.kt (1)

12-32: 잘 구현된 필터 컴포넌트입니다.

상태 관리 패턴과 문자열 리소스 사용이 적절합니다. 유지보수성을 위해 하드코딩된 인덱스를 상수나 enum으로 교체하는 것을 고려해보세요.

+private const val ONGOING_INDEX = 0
+private const val RECRUITING_INDEX = 1

 @Composable
 fun FilterRow(
     selectedIndex: Int,
     onSelect: (Int) -> Unit
 ) {
     Row(
         horizontalArrangement = Arrangement.spacedBy(12.dp)
     ) {
         OptionChipButton(
             text = stringResource(R.string.onGoing),
             isFilled = true,
-            isSelected = selectedIndex == 0,
-            onClick = { onSelect(0) }
+            isSelected = selectedIndex == ONGOING_INDEX,
+            onClick = { onSelect(ONGOING_INDEX) }
         )
         OptionChipButton(
             text = stringResource(R.string.recruiting),
             isFilled = true,
-            isSelected = selectedIndex == 1,
-            onClick = { onSelect(1) }
+            isSelected = selectedIndex == RECRUITING_INDEX,
+            onClick = { onSelect(RECRUITING_INDEX) }
         )
     }
 }
app/src/main/java/com/texthip/thip/ui/myPage/groupRoom/GroupRoomData.kt (1)

5-16: 포괄적인 데이터 클래스 설계가 우수합니다!

그룹 룸의 모든 필요한 정보를 잘 포함하고 있고, 타입 안전성도 확보되어 있습니다. 다만 몇 가지 개선 사항을 고려해보세요:

  • daysLeft가 음수가 될 수 있는지 검증 로직 추가 고려
  • membersmaxMembers의 관계 검증 (members > maxMembers인 경우)
 data class GroupRoomData(
     val title: String,
     val isSecret: Boolean,
     val description: String,
     val period: String,
-    val members: Int,
-    val maxMembers: Int,
-    val daysLeft: Int,
+    val members: Int,
+    val maxMembers: Int,
+    val daysLeft: Int, // Consider validation: daysLeft >= 0
     val genre: String,
     val bookData: BookData,
     val recommendations: List<CardItemRoomData>
-)
+) {
+    init {
+        require(members >= 0) { "Members count cannot be negative" }
+        require(maxMembers > 0) { "Max members must be positive" }
+        require(members <= maxMembers) { "Members cannot exceed max members" }
+        require(daysLeft >= 0) { "Days left cannot be negative" }
+    }
+}
app/src/main/java/com/texthip/thip/ui/myPage/groupPage/MyGroupPager.kt (1)

42-48: 하드코딩된 값들을 상수로 분리하는 것을 고려해보세요

스케일링 값들과 색상 로직을 더 관리하기 쉽게 만들 수 있습니다.

+private const val PEEK_SCALE = 0.86f
+private const val CENTER_SCALE = 1f
+private const val PEEK_ALPHA = 0.7f
+private const val CENTER_ALPHA = 1f

 // 페이지와 현재페이지의 거리(0이면 중앙, 1/-1이면 peek)
 val distanceFromCenter = (pagerState.currentPage - page).toFloat()
 // scale 계산 (중앙은 1f, peek은 0.93~0.97f)
-val scale = if (pagerState.currentPage == page) 1f else 0.86f
+val scale = if (pagerState.currentPage == page) CENTER_SCALE else PEEK_SCALE
 // 색상 변경 (peek이면 밝은 회색, 중앙이면 흰색)
 val bgColor = if (pagerState.currentPage == page) colors.White else colors.DarkGrey
-// alpha(살짝 흐리게)도 가능
app/src/main/java/com/texthip/thip/ui/myPage/groupPage/MyGroupMainCard.kt (2)

46-53: 그라데이션 좌표 하드코딩 개선

그라데이션의 end 좌표가 Offset(1000f, 1000f)로 하드코딩되어 있어 다양한 화면 크기에서 일관되지 않을 수 있습니다.

다음과 같이 상대적 좌표를 사용하는 것을 고려해보세요:

-    val gradient = Brush.linearGradient(
-        colors = listOf(
-            colors.White,
-            colors.Grey01
-        ),
-        start = Offset(0f, 0f),
-        end = Offset(1000f, 1000f)
-    )
+    val gradient = Brush.linearGradient(
+        colors = listOf(
+            colors.White,
+            colors.Grey01
+        ),
+        start = Offset.Zero,
+        end = Offset.Infinite
+    )

60-63: 카드 크기 하드코딩 개선 고려

카드 크기가 320x176 dp로 하드코딩되어 있어 다양한 화면 크기에서 유연성이 부족할 수 있습니다.

반응형 디자인을 위해 비율 기반 크기 조정을 고려해보세요:

-        modifier = Modifier
-            .width(320.dp)
-            .height(176.dp)
-            .clickable { onClick() },
+        modifier = Modifier
+            .fillMaxWidth()
+            .aspectRatio(320f / 176f)
+            .clickable { onClick() },
app/src/main/java/com/texthip/thip/ui/myPage/groupPage/DeadlineRoomSection.kt (1)

96-96: 룸 필터링 성능 최적화

매 페이지 렌더링마다 rooms.filter { it.genreIndex == page }.take(3)를 실행하는 것은 룸 리스트가 클 때 성능 문제를 일으킬 수 있습니다.

ViewModel에서 미리 필터링된 데이터를 제공하거나 remember를 사용하여 캐시하는 것을 고려해보세요:

+                val filteredCards = remember(rooms, page) {
+                    rooms.filter { it.genreIndex == page }.take(3)
+                }
-                val cards = rooms.filter { it.genreIndex == page }.take(3)
+                val cards = filteredCards
app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt (1)

47-60: 테두리 조건부 적용 로직 개선

테두리 적용을 위한 조건부 modifier 체이닝이 복잡합니다. 더 읽기 쉽게 개선할 수 있습니다.

다음과 같이 단순화할 수 있습니다:

-        modifier = modifier
-            .fillMaxWidth()
-            .then(
-                if (hasBorder)
-                    Modifier
-                        .border(
-                            width = 1.dp,
-                            color = colors.Grey02,
-                            shape = RoundedCornerShape(12.dp)
-                        )
-                else Modifier
-            )
-            .clickable { onClick() },
+        modifier = modifier
+            .fillMaxWidth()
+            .let { if (hasBorder) it.border(1.dp, colors.Grey02, RoundedCornerShape(12.dp)) else it }
+            .clickable { onClick() },
app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt (1)

45-46: 고정 크기 카드의 반응형 디자인 고려

카드 크기가 232x104 dp로 고정되어 있어 다양한 화면 크기에서 유연성이 부족할 수 있습니다.

반응형 디자인을 위해 비율 기반 크기 조정을 고려해보세요:

-        modifier = modifier
-            .size(width = 232.dp, height = 104.dp)
-            .clickable { onClick() },
+        modifier = modifier
+            .width(232.dp)
+            .height(104.dp)
+            .clickable { onClick() },

또는 더 유연한 접근법:

+        modifier = modifier
+            .defaultMinSize(minWidth = 200.dp, minHeight = 90.dp)
+            .clickable { onClick() },
app/src/main/java/com/texthip/thip/ui/myPage/groupRoom/GroupRoomScreen.kt (3)

58-58: TODO 주석을 처리하세요.

배경 이미지 추가에 대한 TODO가 있습니다. 이것이 완료되지 않으면 UI가 불완전할 수 있습니다.

배경 이미지 구현을 위한 코드를 생성하거나 이슈를 생성하시겠습니까?


70-84: 중복된 Spacer 코드를 리팩토링하세요.

비밀방과 오픈방 분기에서 동일한 Spacer(Modifier.width(2.dp))가 중복됩니다.

                 ) {
                     Text(
                         text = detail.title,
                         style = typography.bigtitle_b700_s22_h24,
                         color = colors.White
                     )
+                    Spacer(Modifier.width(2.dp))
-                    if (detail.isSecret) {
-                        Spacer(Modifier.width(2.dp))
+                    if (detail.isSecret) {
                         Icon(
                             painter = painterResource(id = R.drawable.ic_lock),
                             contentDescription = "비밀방",
                             tint = colors.White
                         )
                     } else {
-                        Spacer(Modifier.width(2.dp))
                         Icon(
                             painter = painterResource(id = R.drawable.ic_unlock),
                             contentDescription = "오픈방",
                             tint = colors.White
                         )
                     }

275-373: 프리뷰 데이터가 너무 방대합니다.

프리뷰 함수의 샘플 데이터가 매우 길어서 가독성을 해칩니다. 별도의 테스트 데이터 팩토리나 상수로 분리하는 것을 고려하세요.

예시:

object GroupRoomTestData {
    val sampleRecommendations = listOf(...)
    val sampleBookData = BookData(...)
    // ...
}
app/src/main/java/com/texthip/thip/ui/myPage/myGroup/MyGroupScreen.kt (1)

95-192: 프리뷰 데이터의 중복을 줄이세요.

프리뷰에서 거의 동일한 CardItemRoomData 객체가 반복적으로 생성되고 있습니다. 데이터 생성 함수를 만들어 중복을 줄이세요.

private fun createSampleRoomData(
    isRecruiting: Boolean = true,
    endDate: Int = 3,
    participants: Int = 22,
    maxParticipants: Int = 30
) = CardItemRoomData(
    title = "모임방 이름입니다. 모임방...",
    participants = participants,
    maxParticipants = maxParticipants,
    isRecruiting = isRecruiting,
    endDate = endDate,
    genreIndex = 0
)
app/src/main/java/com/texthip/thip/ui/myPage/viewModel/MyPageViewModel.kt (2)

18-19: genres를 StateFlow로 만드는 것을 고려하세요.

현재 _genres는 private val로 되어 있지만, 서버에서 동적으로 장르 목록을 받아올 가능성을 고려하면 StateFlow로 만드는 것이 확장성에 좋습니다.

-    private val _genres = listOf("문학", "과학·IT", "사회과학", "인문학", "예술")
-    val genres: List<String> get() = _genres
+    private val _genres = MutableStateFlow(listOf("문학", "과학·IT", "사회과학", "인문학", "예술"))
+    val genres: StateFlow<List<String>> = _genres

50-64: 이벤트 핸들러에 Navigation 처리를 추가하세요.

현재 모든 이벤트 핸들러가 빈 구현입니다. Navigation 이벤트나 사이드 이펙트 처리를 위한 구조를 추가하는 것을 고려하세요.

예시:

fun onMyGroupHeaderClick() {
    // SharedFlow나 Channel을 사용한 Navigation 이벤트 발송
    viewModelScope.launch {
        _navigationEvents.emit(NavigationEvent.ToMyGroupList)
    }
}

Navigation 이벤트 처리 구조를 생성하시겠습니까?

app/src/main/java/com/texthip/thip/ui/common/forms/FormTextFieldDefault.kt (2)

42-49: 문자 제한 로직이 중복됩니다.

displayText 계산과 onValueChange에서 동일한 문자 제한 로직이 중복됩니다. 별도 함수로 추출하여 DRY 원칙을 적용하세요.

private fun String.limitLength(limit: Int, shouldLimit: Boolean): String {
    return if (shouldLimit && length > limit) substring(0, limit) else this
}

// 사용:
val displayText = text.limitLength(limit, showLimit)
onValueChange = { text = it.limitLength(limit, showLimit) }

71-86: 아이콘 상태 로직이 복잡합니다.

showIcontext.isNotEmpty() 조건이 중첩되어 있어 복잡합니다. 더 명확한 구조로 리팩토링하는 것을 고려하세요.

trailingIcon = {
    if (showIcon) {
        val iconRes = if (text.isNotEmpty()) R.drawable.ic_x_circle_white else R.drawable.ic_x_circle
        val isClickable = text.isNotEmpty()
        
        Icon(
            painter = painterResource(id = iconRes),
            contentDescription = "Clear text",
            modifier = if (isClickable) Modifier.clickable { text = "" } else Modifier,
            tint = if (isClickable) Color.Unspecified else Color.Unspecified
        )
    }
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between afdfdae and 621e9d8.

⛔ Files ignored due to path filters (1)
  • app/src/main/res/drawable/bookcover_sample_small.png is excluded by !**/*.png
📒 Files selected for processing (30)
  • .idea/deploymentTargetSelector.xml (1 hunks)
  • app/build.gradle.kts (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/common/cards/CardAlarm.kt (4 hunks)
  • app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/common/cards/CardRoomBook.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/common/forms/FormTextFieldDefault.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/groupPage/DeadlineRoomData.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/groupPage/DeadlineRoomSection.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/groupPage/GenreChipRow.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/groupPage/GroupPageScreen.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/groupPage/MainSectionHeader.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/groupPage/MyGroupCardData.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/groupPage/MyGroupMainCard.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/groupPage/MyGroupPager.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/groupPage/SearchTextField.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/groupPage/SimplePagerIndicator.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/groupRoom/BookData.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/groupRoom/GroupRoomData.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/groupRoom/GroupRoomScreen.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/myGroup/CardItemRoomData.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/myGroup/FilterRow.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/myGroup/MyGroupScreen.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/screen/MyPageScreen.kt (0 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/viewModel/MyPageViewModel.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/theme/Color.kt (3 hunks)
  • app/src/main/res/values/strings.xml (1 hunks)
  • gradle/libs.versions.toml (2 hunks)
💤 Files with no reviewable changes (1)
  • app/src/main/java/com/texthip/thip/ui/myPage/screen/MyPageScreen.kt
🧰 Additional context used
🧬 Code Graph Analysis (3)
app/src/main/java/com/texthip/thip/ui/myPage/myGroup/FilterRow.kt (1)
app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt (1)
  • OptionChipButton (28-74)
app/src/main/java/com/texthip/thip/ui/myPage/groupPage/GenreChipRow.kt (1)
app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt (1)
  • OptionChipButton (28-74)
app/src/main/java/com/texthip/thip/ui/myPage/groupPage/MyGroupPager.kt (2)
app/src/main/java/com/texthip/thip/ui/myPage/groupPage/MyGroupMainCard.kt (1)
  • MyGroupMainCard (39-144)
app/src/main/java/com/texthip/thip/ui/myPage/groupPage/SimplePagerIndicator.kt (1)
  • SimplePagerIndicator (16-39)
🔇 Additional comments (23)
app/src/main/java/com/texthip/thip/ui/theme/Color.kt (1)

56-56: 색상 정의 구조는 올바르게 구현되었습니다.

새로운 genreColor가 데이터 클래스와 기본 인스턴스에 일관되게 추가되었습니다. 하지만 앞서 언급한 네이밍 컨벤션 수정 시 이 부분들도 함께 업데이트가 필요합니다.

Also applies to: 89-89

gradle/libs.versions.toml (1)

12-12: Navigation 의존성 버전 확인 완료
navigation-runtime-android 2.9.0은 Google Maven(https://dl.google.com/dl/android/maven2/)에 등록된 가장 최신 안정(Stable) 버전이며, OSV 조회 결과 별도 보안 취약점이 보고되지 않았습니다.
의존성 추가를 승인합니다.

app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt (1)

33-63: 상태 호이스팅 패턴으로 잘 리팩토링되었습니다.

isSelected 매개변수를 추가하여 외부에서 선택 상태를 제어하도록 변경한 것은 Compose의 모범 사례를 잘 따르고 있습니다. 이로 인해:

  • 컴포넌트의 재사용성이 향상됩니다
  • 단방향 데이터 플로우 패턴을 준수합니다
  • 상위 컴포넌트에서 선택 상태를 더 잘 관리할 수 있습니다
app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt (1)

10-18: Route 명칭과 화면 이름이 불일치합니다
Routes.MyPage.route 에서 GroupPageScreen을 호출하고 있습니다. 네이밍 불일치는 추후 라우팅 수정 시 혼동을 줄 수 있으니

  1. Routes 객체의 enum(또는 sealed class) 이름을 GroupPage로 변경
  2. 혹은 기존 MyPageScreen 개념을 유지하고 파일/클래스 명을 MyPageScreen으로 맞추기

중 하나로 통일하는 것이 좋습니다.

app/src/main/java/com/texthip/thip/ui/myPage/groupPage/DeadlineRoomData.kt (1)

5-8: 단순 데이터 구조 추가 확인 완료
불필요한 로직 없이 깔끔하게 정의되었습니다. 추후 Parcelable 등이 필요하면 @Parcelize 추가를 고려해주세요.

app/build.gradle.kts (1)

55-65: 알파 버전 라이브러리 도입 전에 호환성 확인 필요
accompanist-pager 0.35.0-alpha 는 Compose 1.6 미리보기 버전과 매칭됩니다. 현재 BOM이 안정화 버전이면 런타임 크래시가 발생할 수 있습니다. 또한 hard-coding 대신 version catalog(libs.versions.toml)로 관리하면 업그레이드가 수월합니다.

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

37-37: 함수명 변경 검증: 레거시 참조 확인 요청

DetailedDarkCardPreviewDetailedDarkCard 레거시 참조가 완전히 제거되었는지 수동으로 확인해주세요:

rg -n "DetailedDarkCard" --type kotlin
rg -n "PreviewDetailedDarkCard" --type kotlin
app/src/main/java/com/texthip/thip/ui/myPage/myGroup/CardItemRoomData.kt (1)

5-13: 잘 설계된 데이터 클래스입니다.

모든 속성이 적절하게 명명되고 타입이 지정되었습니다. endDate 속성의 주석이 명확하게 의미를 전달합니다.

app/src/main/java/com/texthip/thip/ui/myPage/groupPage/MyGroupCardData.kt (1)

5-11: 명확하고 잘 구조화된 데이터 클래스입니다.

progress 속성의 범위 문서화(0~100)가 명확하며, 모든 속성이 그룹 카드 표시에 필요한 정보를 적절히 담고 있습니다.

app/src/main/java/com/texthip/thip/ui/myPage/groupPage/GenreChipRow.kt (2)

12-30: 잘 구현된 장르 선택 컴포넌트입니다.

forEachIndexed 사용과 외부 상태 관리 패턴이 적절하며, SpaceEvenly 배치로 균등한 간격이 보장됩니다.


32-40: 실제적인 데이터를 활용한 우수한 프리뷰 구현입니다.

한국어 장르명을 사용한 프리뷰가 실제 사용 환경을 잘 반영하고 있으며, 적절한 배경색과 크기 설정으로 시각적 검증이 용이합니다.

app/src/main/java/com/texthip/thip/ui/myPage/groupPage/MainSectionHeader.kt (1)

21-44: 깔끔한 헤더 컴포넌트 구현이 좋습니다!

조건부 클릭 이벤트와 아이콘 표시 로직이 잘 구현되어 있습니다. 테마 색상과 타이포그래피를 일관성 있게 사용한 점도 훌륭합니다.

app/src/main/java/com/texthip/thip/ui/myPage/groupRoom/GroupRoomData.kt (1)

18-18: 버튼 타입 enum 설계가 명확합니다!

JOIN, CANCEL, CLOSE 타입들이 UI 액션을 명확하게 표현하고 있어 좋습니다.

app/src/main/java/com/texthip/thip/ui/myPage/groupPage/MyGroupPager.kt (1)

35-64: 페이저 구현이 훌륭합니다!

스케일링과 투명도 효과를 통한 peek 기능이 잘 구현되어 있습니다. 특히 중앙 카드와 주변 카드의 시각적 차별화가 좋습니다.

app/src/main/java/com/texthip/thip/ui/myPage/groupPage/SearchTextField.kt (1)

55-62: 텍스트 필드 스타일링이 일관성 있게 잘 적용되었습니다!

다크 테마에 맞는 색상 조합과 투명한 인디케이터 사용이 적절합니다.

app/src/main/java/com/texthip/thip/ui/common/cards/CardAlarm.kt (3)

37-39: 컴포넌트 리팩토링이 잘 수행되었습니다!

NotificationCard에서 CardAlarm으로의 이름 변경과 badgeText 파라미터 추가가 적절합니다. 더 명확하고 유연한 API가 되었습니다.


78-80: 배지 패딩 개선이 좋습니다!

고정된 크기 제약을 제거하고 패딩을 추가한 것이 더 유연한 디자인을 만들어냅니다.


152-194: 포괄적인 프리뷰 예제가 훌륭합니다!

다양한 배지 텍스트("모임", "피드", "좋아요", "댓글")와 읽음 상태를 보여주는 예제들이 컴포넌트 테스트에 매우 유용합니다.

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

112-116: 참가자 수 표시 로직 개선

참가자 수 표시 로직이 명확하고 잘 구현되어 있습니다. 모집 중과 모집 완료 상태를 구분하여 적절한 정보를 보여줍니다.

app/src/main/java/com/texthip/thip/ui/myPage/groupRoom/GroupRoomScreen.kt (1)

46-50: 네비게이션 콜백이 비어있습니다.

DefaultTopAppBaronLeftClick 콜백이 빈 구현으로 되어 있어 뒤로가기 기능이 작동하지 않을 것입니다.

-                onLeftClick = {},
+                onLeftClick = { /* TODO: 네비게이션 뒤로가기 구현 */ },
app/src/main/java/com/texthip/thip/ui/myPage/myGroup/MyGroupScreen.kt (2)

42-48: 필터링 로직이 효율적입니다.

remember를 사용해 selectedIndexallDataList가 변경될 때만 재계산하도록 최적화되어 있어 성능이 좋습니다.


54-57: 네비게이션 콜백이 비어있습니다.

DefaultTopAppBaronLeftClick이 빈 구현으로 되어 있어 뒤로가기 기능이 작동하지 않을 것입니다.

-            onLeftClick = {},
+            onLeftClick = { /* TODO: 네비게이션 뒤로가기 구현 */ },
app/src/main/java/com/texthip/thip/ui/common/forms/FormTextFieldDefault.kt (1)

115-131: 프리뷰가 잘 구성되어 있습니다.

두 가지 다른 설정의 컴포넌트를 보여주는 프리뷰가 잘 구성되어 있어 다양한 사용 사례를 확인할 수 있습니다.

Comment on lines 59 to 64
<string name="groupRecruiting">"모집 "</string>
<string name="groupGenre">"장르 "</string>
<string name="deadlineString">마감 임박한 독서 모임방</string>
<string name="myGroup">내 모임방</string>
<string name="groupParticipant">%1$s명 참여</string>
<string name="groupProgress">"%1$s의 진행도 "</string>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

값에 불필요한 큰따옴표와 공백이 포함되어 있습니다
groupRecruiting, groupGenre, groupProgress 값 앞뒤의 " 문자와 공백은 실제 UI에 그대로 노출됩니다. 의도된 스타일링이 아니라면 제거해야 합니다.

-    <string name="groupRecruiting">"모집 "</string>
-    <string name="groupGenre">"장르 "</string>
-    <string name="groupProgress">"%1$s의 진행도 "</string>
+    <string name="group_recruiting">모집</string>
+    <string name="group_genre">장르</string>
+    <string name="group_progress">%1$s의 진행도</string>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/src/main/res/values/strings.xml lines 59 to 64, remove the unnecessary
double quotes and trailing spaces from the values of groupRecruiting,
groupGenre, and groupProgress strings to prevent them from appearing literally
in the UI. Ensure the string values contain only the intended text without extra
characters or spaces.

@Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360)
@Composable
fun PreviewMainSectionHeader() {
MainSectionHeader(){}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Preview 함수 구문 오류 수정 필요

람다 표현식에 중괄호가 잘못 배치되어 있습니다.

다음과 같이 수정하세요:

-    MainSectionHeader(){}
+    MainSectionHeader(onClick = {})
📝 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
MainSectionHeader(){}
MainSectionHeader(onClick = {})
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/myPage/groupPage/MainSectionHeader.kt
at line 49, the lambda expression syntax is incorrect due to misplaced braces.
Fix the syntax by properly placing the curly braces around the lambda body to
conform to Kotlin's lambda expression rules.

Comment on lines 24 to 27
val pagerState = rememberPagerState(
initialPage = 0,
pageCount = { groupCards.size }
)
Copy link

@coderabbitai coderabbitai bot Jun 30, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

빈 리스트 처리 로직 추가 필요

groupCards.size가 0일 때 페이저가 크래시할 수 있습니다.

다음과 같이 수정하세요:

     val pagerState = rememberPagerState(
         initialPage = 0,
-        pageCount = { groupCards.size }
+        pageCount = { maxOf(1, groupCards.size) }
     )

또한 빈 상태 처리를 추가하는 것을 고려해보세요:

+    if (groupCards.isEmpty()) {
+        // Empty state UI
+        return
+    }
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/myPage/groupPage/MyGroupPager.kt around
lines 24 to 27, the pagerState initialization uses groupCards.size directly,
which can cause a crash if the list is empty. Modify the pageCount lambda to
return at least 1 when groupCards.size is 0 to prevent crashes. Additionally,
implement UI logic to handle the empty state gracefully, such as showing a
placeholder or message when there are no group cards.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

수정할게요

Copy link

Choose a reason for hiding this comment

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

@rbqks529 감사합니다! 빈 리스트 처리 로직을 추가해주시면 더 안정적인 컴포넌트가 될 것입니다.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Comment on lines 33 to 43
var value by rememberSaveable { mutableStateOf("") }

Box(
Modifier
.padding(horizontal = 20.dp)
.fillMaxWidth()
.height(48.dp)
) {
OutlinedTextField(
value = value,
onValueChange = onValueChange,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

상태 관리 로직에 문제가 있습니다

onValueChange 콜백이 내부 상태 업데이트와 연결되지 않았습니다.

다음과 같이 수정하세요:

     OutlinedTextField(
         value = value,
-        onValueChange = onValueChange,
+        onValueChange = { newValue ->
+            value = newValue
+            onValueChange(newValue)
+        },
📝 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
var value by rememberSaveable { mutableStateOf("") }
Box(
Modifier
.padding(horizontal = 20.dp)
.fillMaxWidth()
.height(48.dp)
) {
OutlinedTextField(
value = value,
onValueChange = onValueChange,
var value by rememberSaveable { mutableStateOf("") }
Box(
Modifier
.padding(horizontal = 20.dp)
.fillMaxWidth()
.height(48.dp)
) {
OutlinedTextField(
value = value,
onValueChange = { newValue ->
value = newValue
onValueChange(newValue)
},
// ...other parameters...
)
}
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/myPage/groupPage/SearchTextField.kt
around lines 33 to 43, the onValueChange callback is not updating the internal
state variable 'value'. Fix this by modifying the onValueChange lambda to update
'value' with the new input and then call the external onValueChange callback,
ensuring the internal state stays in sync with user input.

Comment on lines 96 to 122
val cards = rooms.filter { it.genreIndex == page }.take(3)
Column(
verticalArrangement = Arrangement.spacedBy(20.dp),
modifier = Modifier.fillMaxWidth()
) {
cards.forEach { room ->
CardItemRoom(
title = room.title,
participants = room.participants,
maxParticipants = room.maxParticipants,
isRecruiting = room.isRecruiting,
endDate = room.endDate,
imageRes = room.imageRes,
onClick = { onRoomClick(room) },
hasBorder = true, // 카드에 테두리 추가
)
}
// 카드가 3개보다 적을 때 Spacer를 아래에 추가
if (cards.size < 3) {
Spacer(
modifier = Modifier
.weight(1f, fill = true)
.fillMaxWidth()
)
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

빈 상태 처리 추가

선택된 장르에 해당하는 룸이 없을 때의 빈 상태 처리가 없습니다. 사용자 경험을 위해 빈 상태 메시지를 표시해야 합니다.

빈 상태 처리를 추가하세요:

                } { page ->
                    val cards = rooms.filter { it.genreIndex == page }.take(3)
+                    if (cards.isEmpty()) {
+                        Box(
+                            modifier = Modifier.fillMaxSize(),
+                            contentAlignment = Alignment.Center
+                        ) {
+                            Text(
+                                text = "해당 장르의 모임방이 없습니다",
+                                color = colors.Grey01,
+                                style = typography.view_m500_s14
+                            )
+                        }
+                    } else {
                        Column(
                            verticalArrangement = Arrangement.spacedBy(20.dp),
                            modifier = Modifier.fillMaxWidth()
                        ) {
                            // 기존 코드
                        }
+                    }
                }
📝 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
val cards = rooms.filter { it.genreIndex == page }.take(3)
Column(
verticalArrangement = Arrangement.spacedBy(20.dp),
modifier = Modifier.fillMaxWidth()
) {
cards.forEach { room ->
CardItemRoom(
title = room.title,
participants = room.participants,
maxParticipants = room.maxParticipants,
isRecruiting = room.isRecruiting,
endDate = room.endDate,
imageRes = room.imageRes,
onClick = { onRoomClick(room) },
hasBorder = true, // 카드에 테두리 추가
)
}
// 카드가 3개보다 적을 때 Spacer를 아래에 추가
if (cards.size < 3) {
Spacer(
modifier = Modifier
.weight(1f, fill = true)
.fillMaxWidth()
)
}
}
}
} { page ->
val cards = rooms.filter { it.genreIndex == page }.take(3)
if (cards.isEmpty()) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "해당 장르의 모임방이 없습니다",
color = colors.Grey01,
style = typography.view_m500_s14
)
}
} else {
Column(
verticalArrangement = Arrangement.spacedBy(20.dp),
modifier = Modifier.fillMaxWidth()
) {
cards.forEach { room ->
CardItemRoom(
title = room.title,
participants = room.participants,
maxParticipants = room.maxParticipants,
isRecruiting = room.isRecruiting,
endDate = room.endDate,
imageRes = room.imageRes,
onClick = { onRoomClick(room) },
hasBorder = true, // 카드에 테두리 추가
)
}
// 카드가 3개보다 적을 때 Spacer를 아래에 추가
if (cards.size < 3) {
Spacer(
modifier = Modifier
.weight(1f, fill = true)
.fillMaxWidth()
)
}
}
}
}
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/myPage/groupPage/DeadlineRoomSection.kt
around lines 96 to 122, there is no handling for the empty state when no rooms
match the selected genre. Add a conditional check for when the filtered rooms
list is empty and display a user-friendly empty state message or UI element in
place of the cards to improve user experience.

Comment on lines 244 to 253
val buttonText = when (buttonType) {
BottomButtonType.JOIN -> "참여하기"
BottomButtonType.CANCEL -> "참여 취소하기"
BottomButtonType.CLOSE -> "모집 마감하기"
}
val buttonColor = when (buttonType) {
BottomButtonType.JOIN -> colors.Purple
BottomButtonType.CANCEL -> colors.Red
BottomButtonType.CLOSE -> colors.Grey02
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

when 표현식의 exhaustive 검사를 고려하세요.

BottomButtonType에 새로운 값이 추가되면 이 when 표현식들이 컴파일 오류를 발생시키지 않을 수 있습니다. sealed class나 enum으로 exhaustive 검사를 보장하는 것이 좋습니다.

🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/myPage/groupRoom/GroupRoomScreen.kt
around lines 244 to 253, the when expressions for buttonText and buttonColor are
not exhaustive, risking missing cases if BottomButtonType gains new values. To
fix this, ensure BottomButtonType is defined as a sealed class or enum to enable
exhaustive when checks, and add an else branch or handle all cases explicitly in
the when expressions to guarantee compile-time exhaustiveness.

Comment on lines 26 to 42
_myGroups.value = listOf(
MyGroupCardData("호르몬 체인지 완독하는 방", 22, R.drawable.bookcover_sample, 40, "uibowl1"),
MyGroupCardData("호르몬 체인지 완독하는 방", 22, R.drawable.bookcover_sample, 40, "uibowl1"),
MyGroupCardData("호르몬 체인지 완독하는 방", 22, R.drawable.bookcover_sample, 40, "uibowl1")
)
_deadlineRooms.value = listOf(
CardItemRoomData("시집만 읽는 사람들 3월", 22, 30, true, 3, R.drawable.bookcover_sample, 0), // 문학
CardItemRoomData("일본 소설 좋아하는 사람들", 22, 30, true, 3, R.drawable.bookcover_sample, 0), // 문학
CardItemRoomData("명작 같이 읽기방", 22, 30, true, 3, R.drawable.bookcover_sample, 0), // 문학

CardItemRoomData("물리책 읽는 방", 13, 20, true, 1, R.drawable.bookcover_sample, 1), // 과학·IT
CardItemRoomData("코딩 과학 동아리", 12, 15, true, 5, R.drawable.bookcover_sample, 1), // 과학·IT

CardItemRoomData("사회과학 인문 탐구", 8, 12, true, 4, R.drawable.bookcover_sample, 2), // 사회과학
)

}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

하드코딩된 샘플 데이터를 분리하세요.

init 블록에 하드코딩된 샘플 데이터가 있습니다. 실제 구현에서는 Repository 패턴을 사용해야 하므로, 이를 별도 메서드로 분리하거나 테스트용임을 명확히 표시하세요.

// 테스트용 데이터 로딩
private fun loadSampleData() {
    _myGroups.value = createSampleGroups()
    _deadlineRooms.value = createSampleRooms()
}

init {
    // TODO: Replace with actual repository call
    loadSampleData()
}
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/myPage/viewModel/MyPageViewModel.kt
between lines 26 and 42, the sample data is hardcoded directly inside the init
block. To improve code organization and clarify that this is test data, extract
the sample data initialization into a separate private method such as
loadSampleData(), and call this method from the init block with a TODO comment
indicating it should be replaced by actual repository calls later.

Comment on lines +91 to +104
if (showLimit) {
Box(
modifier = Modifier
.align(Alignment.CenterEnd)
.padding(end = 14.dp)
) {
Text(
text = "${text.length}/$limit",
color = colors.White,
style = typography.info_r400_s12_h24
)
}
},
singleLine = true
)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

문자 수 카운터 위치가 겹칠 수 있습니다.

문자 수 표시가 trailingIcon과 겹칠 수 있습니다. showIconshowLimit가 모두 true일 때의 레이아웃을 고려해야 합니다.

showIcon = true이고 showLimit = true일 때 UI 요소들이 겹치는 문제가 발생할 수 있습니다. 레이아웃 충돌을 방지하는 로직을 추가하세요.

        if (showLimit) {
            Box(
                modifier = Modifier
                    .align(Alignment.CenterEnd)
-                    .padding(end = 14.dp)
+                    .padding(end = if (showIcon) 50.dp else 14.dp)
            ) {
📝 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
if (showLimit) {
Box(
modifier = Modifier
.align(Alignment.CenterEnd)
.padding(end = 14.dp)
) {
Text(
text = "${text.length}/$limit",
color = colors.White,
style = typography.info_r400_s12_h24
)
}
},
singleLine = true
)
}
}
if (showLimit) {
Box(
modifier = Modifier
.align(Alignment.CenterEnd)
.padding(end = if (showIcon) 50.dp else 14.dp)
) {
Text(
text = "${text.length}/$limit",
color = colors.White,
style = typography.info_r400_s12_h24
)
}
}
}
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/common/forms/FormTextFieldDefault.kt
around lines 91 to 104, the character count display can overlap with the
trailingIcon when both showIcon and showLimit are true. To fix this, add layout
logic that adjusts the position or spacing of the character count when showIcon
is also true, ensuring the two UI elements do not overlap and maintain clear
separation.

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 implements the complete UI for the 모임 page, including new screens for group rooms, MyPage modifications, and component adjustments requested for consistency and improved navigation. Key changes include updates to gradle dependency versions and libraries, revisions to string and color resources, and the introduction/refactor of multiple UI components and screens (e.g. GroupRoomScreen, MyGroupScreen, and DeadlineRoomSection).

Reviewed Changes

Copilot reviewed 29 out of 31 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
gradle/libs.versions.toml & build.gradle.kts Added/updated dependencies (navigation runtime, accompanist pager)
app/src/main/res/values/strings.xml Updated and added string resources
Color.kt Introduced a new color variable (genreColor)
Navigation & ViewModel Files Adjusted navigation target and view model changes
Multiple UI components (e.g. GroupRoomScreen, MyGroupScreen, DeadlineRoomSection, FormTextFieldDefault, CardAlarm, OptionChipButton) Added new screens and refactored UI components for 모임 page implementation
Files not reviewed (1)
  • .idea/deploymentTargetSelector.xml: Language not supported
Comments suppressed due to low confidence (1)

app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt:29

  • The internal toggle state was removed in favor of an externally controlled 'isSelected' property. Ensure that the parent components consistently manage the selection state to avoid unexpected behavior.
fun OptionChipButton(

Comment on lines +41 to +49
// 글자수 제한 적용
val displayText = if (showLimit && text.length > limit) text.substring(0, limit) else text

Box(modifier = modifier) {
OutlinedTextField(
value = displayText,
onValueChange = {
// 글자수 제한 적용
text = if (showLimit && it.length > limit) it.substring(0, limit) else it
Copy link

Copilot AI Jul 1, 2025

Choose a reason for hiding this comment

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

[nitpick] Consider clarifying the character limiting logic in the text field by adding inline comments or refactoring the substring logic into a helper function, which can improve readability and future modifications.

Copilot uses AI. Check for mistakes.
Comment on lines 44 to 50
LaunchedEffect(pagerState.currentPage) {
if (selectedGenre != pagerState.currentPage) {
onGenreSelect(pagerState.currentPage)
}
}
LaunchedEffect(selectedGenre) {
if (pagerState.currentPage != selectedGenre) {
Copy link

Copilot AI Jul 1, 2025

Choose a reason for hiding this comment

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

[nitpick] Two separate LaunchedEffect blocks are used to synchronize the pager state with the selected genre. It might improve clarity and reduce redundancy by combining these effects into a single synchronization block.

Suggested change
LaunchedEffect(pagerState.currentPage) {
if (selectedGenre != pagerState.currentPage) {
onGenreSelect(pagerState.currentPage)
}
}
LaunchedEffect(selectedGenre) {
if (pagerState.currentPage != selectedGenre) {
LaunchedEffect(pagerState.currentPage, selectedGenre) {
if (selectedGenre != pagerState.currentPage) {
onGenreSelect(pagerState.currentPage)
} else if (pagerState.currentPage != selectedGenre) {

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

수정하겠읍니다

Copy link
Member

@Nico1eKim Nico1eKim left a comment

Choose a reason for hiding this comment

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

수고하셨습니당 ~ 패키지 구조는 아직 수정 안된거져?

}
Spacer(modifier = Modifier.height(5.dp))
Text(
text = "${endDate}일 뒤 " + if (isRecruiting) "모집 마감" else "종료",
Copy link
Member

Choose a reason for hiding this comment

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

text 추출해주세용

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

헉 이게 있었네 수정할게요




@Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360)
Copy link
Member

Choose a reason for hiding this comment

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

이거 이렇게 안하고 그냥 preview 안에 ThipTheme 집어넣으면 됩니닷

Comment on lines 44 to 49
LazyColumn(
Modifier
.fillMaxSize()
.padding(bottom = 32.dp), // FAB 공간 확보
verticalArrangement = Arrangement.spacedBy(0.dp)
) {
Copy link
Member

Choose a reason for hiding this comment

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

아래 부분에 크게 반복되는 아이템이 없는거같은데 LazyColumn으로 구현한 이유가 있나여?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Scroll 되게 하려고 했는디 Column에 ScrollState로 스크롤 가능하게 하는게 더 나은 방법인가요?

Copy link
Member

Choose a reason for hiding this comment

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

저는 개인적으로 반복되는 것들만 LazyColumn으로 하고 반복되는게 아니면 Column에 ScrollState 하는게 가독성 측면에서 좋다고 생각합니다

LazyColumn으로 하면 item 안에 넣어야해서 읽기 불편한거가타여 근데 이건 개인 선호라서 마음대로 하셔도 될둣?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ㅇㅎ 그러면 Column에 ScrollState로 통일하는게 좋을듯 하네용

import com.texthip.thip.ui.theme.ThipTheme.typography

@Composable
fun MainSectionHeader(onClick: (() -> Unit)? = null) {
Copy link
Member

Choose a reason for hiding this comment

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

해당 페이지에서만 쓰이는거면 페이지 이름이 앞에 들어가면 좋을 것 같아요 ~

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

확인했습니다

}
Spacer(Modifier.height(12.dp))
Text(
text = "${detail.members} / ${detail.maxMembers}",
Copy link
Member

Choose a reason for hiding this comment

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

string 추출 해주세용 ~

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵 ㅠ

color = colors.White
)
Text(
text = "${detail.daysLeft}일 남음",
Copy link
Member

Choose a reason for hiding this comment

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

string 추출 해주세용

Comment on lines 245 to 247
BottomButtonType.JOIN -> "참여하기"
BottomButtonType.CANCEL -> "참여 취소하기"
BottomButtonType.CLOSE -> "모집 마감하기"
Copy link
Member

Choose a reason for hiding this comment

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

버튼 이름들 string 추출 해주세용 ~

val recommendations: List<CardItemRoomData>
)

enum class BottomButtonType { JOIN, CANCEL, CLOSE }
Copy link
Member

Choose a reason for hiding this comment

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

enum은 다른 파일로 따로 분리하면 더 좋을거같습니당

val MintSub = Color(0xFF4FD9C0)
val Orange = Color(0xFFFDB770)
val OrangeSub = Color(0xFFFF8B17)
val genreColor = Color(0xFFB5B35D)
Copy link
Member

Choose a reason for hiding this comment

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

장르 색상이 아마 장르별로 다르게 설정될거라 이부분은 저희 상의하고 따로 정해봐야할거가타여

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

확인했습니다

Copy link
Member

@JJUYAAA JJUYAAA left a comment

Choose a reason for hiding this comment

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

수고하셨숩니당 〰️〰️ 전체적으로 패키지 구조 수정해주시면 될 것 같아욤

Copy link
Member

Choose a reason for hiding this comment

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

Modifier 잘 빼주셨는데 50-52줄에 보면 colors 설정이 CardDefaults.cardColors 로 돼있어서 외부에서는 변경할 수가 없네용 마이페이지에서는 해당 컴포넌트가 다른 색상으로 쓰여서요! modifier.background 로 카드 색상 변경 가능하도록 부탁드립니데이

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵 수정해서 올릴게요

.fillMaxWidth()
.background(
brush = Brush.verticalGradient(
colors = listOf(colors.Grey03, colors.DarkGrey, colors.Black)
Copy link
Member

Choose a reason for hiding this comment

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

오 이거 싱기하네요
근데 피그마상에서보다 더 밝은 느낌이라, 제가 그라데이션 구현한 방식대로 해보니까 쪼금 더 유동적으로 조절해가면서 그라데이션 효과 줄 수 있는 것 같아효 한번 참고해보셔도 좋을 것 같슴다
.background( Brush.verticalGradient( colors = listOf( Grey03.copy(alpha = 0.6f), Color.Black.copy(alpha = 0.5f) ) ) )

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

어디 파일 참고하면 되지? 그 캐릭터 선택창인가요?

Copy link
Member

Choose a reason for hiding this comment

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

네넹 RoleCard 컴포넌트 참고하심됩니닷

Copy link
Member

Choose a reason for hiding this comment

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

요 파일은 원래대로 되돌려주셔야 할 것 같은데유?
회의때 요 컴포넌트에서 버튼 중복선택, 토글처럼 on/off 자유롭게 조절되도록 수정하기로 했고 그부분 제가 확인해봤는데, 기존 코드 (OptionChipButton) 는 선택 여부를 외부에서 상태로 관리할 수 있도록 isFilled와 onClick만 받게끔 돼있어서 중복 선택도 가능하고 토글기능도 있고, 필터링 기능도 구현 가능하게 돼있더라고요! 기존코드로 돌려서 한번 확인해주시면 좋을 것 같습니닷

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

기존 코드로 돌려서 확인할게요

modifier: Modifier = Modifier,
hint: String
hint: String,
showLimit: Boolean = false,
Copy link
Member

Choose a reason for hiding this comment

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

굿뜨 👍👍

@rbqks529 rbqks529 requested review from JJUYAAA and Nico1eKim July 2, 2025 06:24
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

♻️ Duplicate comments (1)
app/src/main/res/values/strings.xml (1)

56-77: 네이밍 규칙 불일치와 불필요한 따옴표 문제

기존 리뷰에서 지적된 문제들이 여전히 남아있습니다:

  1. 기존 snake_case(예: reading_user_num)와 새로 추가된 camelCase(예: onGoing) 혼재
  2. 일부 값에 불필요한 따옴표와 공백 포함(예: "모집 ", "장르 ")

프로젝트 전반의 일관성을 위해 snake_case로 통일하고 불필요한 문자를 제거해주세요.

🧹 Nitpick comments (5)
app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt (1)

100-204: 프리뷰 데이터가 너무 길어 가독성 저하

프리뷰 함수 내에 긴 목 데이터가 인라인으로 포함되어 가독성을 해칩니다. 별도의 함수나 객체로 분리하는 것을 권장합니다.

다음과 같이 분리할 수 있습니다:

+private fun createSampleGroupData() = listOf(
+    GroupCardItemRoomData(
+        title = "모임방 이름입니다. 모임방...",
+        participants = 22,
+        maxParticipants = 30,
+        isRecruiting = true,
+        endDate = 3,
+        genreIndex = 0
+    ),
+    // ... 나머지 데이터들
+)

@Preview()
@Composable
fun MyGroupListFilterScreenPreview() {
    ThipTheme {
-        val dataList = listOf(
-            GroupCardItemRoomData(
-                // ... 긴 데이터들
-            )
-        )
+        val dataList = createSampleGroupData()
        GroupMyScreen(allDataList = dataList)
    }
}
app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupRoomScreen.kt (1)

61-61: 배경 이미지 구현 미완료

TODO 코멘트가 있어 배경 이미지 추가가 아직 완료되지 않았습니다. 이 부분의 구현 완료가 필요합니다.

배경 이미지 구현을 도와드릴까요? 또는 이를 추적할 이슈를 생성해드릴까요?

app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt (1)

37-40: NavController 파라미터 활용도가 낮습니다

NavHostController를 파라미터로 받고 있지만 실제 네비게이션 로직에서 사용하지 않고 있습니다. ViewModel의 콜백 메서드들을 통해 네비게이션이 처리되는 구조라면, 파라미터 제거를 고려하거나 실제 네비게이션 구현 시 활용 방안을 명확히 해야 합니다.

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

26-26: @SuppressLint 사용 이유를 명확히 해주세요

@SuppressLint("UnusedBoxWithConstraintsScope")를 사용하고 있는데, BoxWithConstraints의 scope를 실제로 사용하지 않는다면 다른 레이아웃 컴포저블 사용을 고려해보세요.

BoxWithConstraints 대신 Box나 Column 사용을 검토해보세요:

-@SuppressLint("UnusedBoxWithConstraintsScope")
-        BoxWithConstraints(
+        Box(
             modifier = Modifier
                 .fillMaxWidth()
                 .height(588.dp),
             contentAlignment = Alignment.Center
         ) {
-            val horizontalPadding = ((maxWidth - cardWidth) / 2).coerceAtLeast(0.dp)
+            val horizontalPadding = 16.dp // 또는 적절한 고정값

246-252: Preview 데이터에서 하드코딩된 문자열 발견

Preview에서 일부 문자열이 하드코딩되어 있습니다. 사용자 rbqks529가 선호하는 방식에 따라 strings.xml로 추출하는 것이 좋겠습니다.

하드코딩된 문자열을 strings.xml로 이동해주세요:

             GroupRoomSectionData(
-                title = "인기 있는 독서 모임방",
+                title = stringResource(R.string.popular_reading_rooms),
                 rooms = popularRooms,
                 genres = genres
             ),
             GroupRoomSectionData(
-                title = "인플루언서·작가 독서 모임방",
+                title = stringResource(R.string.influencer_reading_rooms),
                 rooms = influencerRooms,
                 genres = genres
             )
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 621e9d8 and 59e7cda.

📒 Files selected for processing (27)
  • app/build.gradle.kts (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/common/cards/CardAlarm.kt (5 hunks)
  • app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GenreChipRow.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupDeadlineRoomSection.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMainCard.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMyRoomFilterRow.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMySectionHeader.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupPager.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupSearchTextField.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/SimplePagerIndicator.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupBookData.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupBottomButtonType.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardData.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardItemRoomData.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomData.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomSectionData.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupViewModel.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupRoomScreen.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt (1 hunks)
  • app/src/main/java/com/texthip/thip/ui/myPage/MyPageScreen.kt (2 hunks)
  • app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt (1 hunks)
  • app/src/main/res/values/strings.xml (2 hunks)
  • gradle/libs.versions.toml (2 hunks)
✅ Files skipped from review due to trivial changes (12)
  • app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupBottomButtonType.kt
  • app/src/main/java/com/texthip/thip/ui/myPage/MyPageScreen.kt
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/SimplePagerIndicator.kt
  • app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomSectionData.kt
  • app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardData.kt
  • app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupBookData.kt
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GenreChipRow.kt
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupPager.kt
  • app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupCardItemRoomData.kt
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupSearchTextField.kt
  • app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMySectionHeader.kt
  • app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupRoomData.kt
🚧 Files skipped from review as they are similar to previous changes (7)
  • app/src/main/java/com/texthip/thip/ui/navigator/MainNavHost.kt
  • app/build.gradle.kts
  • app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt
  • app/src/main/java/com/texthip/thip/ui/common/cards/CardAlarm.kt
  • app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt
  • app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoom.kt
  • gradle/libs.versions.toml
🧰 Additional context used
🧠 Learnings (5)
📓 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.961Z
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.
app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt (1)
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.216Z
Learning: GroupPageScreen의 MyPageViewModel은 임시 ViewModel로, 현재는 프로토타입 단계이므로 오류 처리 등의 추가 기능은 나중에 구현 예정입니다.
app/src/main/java/com/texthip/thip/ui/group/myroom/mock/GroupViewModel.kt (1)
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.216Z
Learning: GroupPageScreen의 MyPageViewModel은 임시 ViewModel로, 현재는 프로토타입 단계이므로 오류 처리 등의 추가 기능은 나중에 구현 예정입니다.
app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt (1)
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.216Z
Learning: GroupPageScreen의 MyPageViewModel은 임시 ViewModel로, 현재는 프로토타입 단계이므로 오류 처리 등의 추가 기능은 나중에 구현 예정입니다.
app/src/main/res/values/strings.xml (1)
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.961Z
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.
🧬 Code Graph Analysis (2)
app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMyRoomFilterRow.kt (1)
app/src/main/java/com/texthip/thip/ui/common/buttons/OptionChipButton.kt (1)
  • OptionChipButton (28-80)
app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupRoomScreen.kt (4)
app/src/main/java/com/texthip/thip/ui/common/topappbar/DefaultTopAppBar.kt (1)
  • DefaultTopAppBar (19-62)
app/src/main/java/com/texthip/thip/ui/common/cards/CardRoomBook.kt (1)
  • CardRoomBook (36-139)
app/src/main/java/com/texthip/thip/ui/common/cards/CardItemRoomSmall.kt (1)
  • CardItemRoomSmall (35-118)
app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
  • ThipTheme (41-69)
🔇 Additional comments (9)
app/src/main/java/com/texthip/thip/ui/group/myroom/screen/GroupMyScreen.kt (1)

35-49: 필터링 로직 잘 구현됨

상태 관리와 필터링 로직이 명확하고 올바르게 구현되어 있습니다. remember를 사용하여 성능 최적화도 잘 되어 있습니다.

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

41-146: 잘 구성된 카드 컴포넌트

그라데이션 배경, 레이아웃 구성, 그리고 상태 관리가 모두 적절하게 구현되어 있습니다. remember를 사용한 성능 최적화와 StringResource 활용도 좋습니다.

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

254-283: 버튼 상태 관리 잘 구현됨

when 표현식을 사용한 버튼 텍스트와 색상 관리가 명확하고 올바르게 구현되어 있습니다. 각 상태에 따른 UI 변화가 적절합니다.

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

8-90: 프로토타입 단계에 적합한 ViewModel 구현

mock 패키지에 위치한 임시 ViewModel로서 현재 구현이 적절합니다. StateFlow 사용과 초기 목 데이터 설정이 올바르게 되어 있으며, 빈 스텁 메서드들도 프로토타입 단계에서는 합리적입니다.

app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt (3)

41-43: State 수집이 적절히 구현되었습니다

ViewModel의 StateFlow들을 collectAsState()로 적절히 수집하여 Compose 상태로 관리하고 있습니다. 상태 호이스팅 패턴을 잘 따르고 있습니다.


80-85: 구분선 구현이 깔끔합니다

색상이 있는 Spacer를 사용하여 섹션 구분선을 구현한 방식이 깔끔합니다. UI/UX적으로 적절한 시각적 구분을 제공하고 있습니다.


104-111: Preview 구현이 잘 되어있습니다

Preview 컴포저블에서 remember를 사용하여 ViewModel 인스턴스를 생성하고 ThipTheme을 적용한 구현이 적절합니다. UI 개발 시 유용한 미리보기를 제공할 수 있습니다.

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

118-125: 빈 공간 처리 로직이 좋습니다

카드가 3개 미만일 때 빈 공간을 채우는 로직이 잘 구현되어 있습니다. UI 일관성을 위한 좋은 처리입니다.


101-101: 필터링 로직의 안전성을 검토해주세요

genreIndex == selectedGenre 필터링에서 인덱스 범위를 벗어날 가능성이 있습니다. 안전성 검증을 추가하는 것이 좋습니다.

안전한 필터링 로직으로 개선해주세요:

-                        val cards = section.rooms.filter { it.genreIndex == selectedGenre }.take(3)
+                        val cards = section.rooms.filter { 
+                            it.genreIndex == selectedGenre && selectedGenre < section.genres.size 
+                        }.take(3)

Likely an incorrect or invalid review comment.

Comment on lines 12 to 30
fun GroupMyRoomFilterRow(
selectedStates: BooleanArray,
onToggle: (Int) -> Unit
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
OptionChipButton(
text = stringResource(R.string.onGoing),
isFilled = true,
onClick = { onToggle(0) }
)
OptionChipButton(
text = stringResource(R.string.recruiting),
isFilled = true,
onClick = { onToggle(1) }
)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

선택 상태가 UI에 반영되지 않는 문제

selectedStates 파라미터를 받아오지만 실제로 사용하지 않아서 버튼의 선택 상태가 시각적으로 표시되지 않습니다. OptionChipButtonisSelected 파라미터를 활용하여 선택 상태를 반영해야 합니다.

다음과 같이 수정하여 선택 상태를 반영하세요:

        OptionChipButton(
            text = stringResource(R.string.onGoing),
            isFilled = true,
+           isSelected = selectedStates[0],
            onClick = { onToggle(0) }
        )
        OptionChipButton(
            text = stringResource(R.string.recruiting),
            isFilled = true,
+           isSelected = selectedStates[1],
            onClick = { onToggle(1) }
        )
📝 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 GroupMyRoomFilterRow(
selectedStates: BooleanArray,
onToggle: (Int) -> Unit
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
OptionChipButton(
text = stringResource(R.string.onGoing),
isFilled = true,
onClick = { onToggle(0) }
)
OptionChipButton(
text = stringResource(R.string.recruiting),
isFilled = true,
onClick = { onToggle(1) }
)
}
}
fun GroupMyRoomFilterRow(
selectedStates: BooleanArray,
onToggle: (Int) -> Unit
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
OptionChipButton(
text = stringResource(R.string.onGoing),
isFilled = true,
isSelected = selectedStates[0],
onClick = { onToggle(0) }
)
OptionChipButton(
text = stringResource(R.string.recruiting),
isFilled = true,
isSelected = selectedStates[1],
onClick = { onToggle(1) }
)
}
}
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupMyRoomFilterRow.kt
around lines 12 to 30, the selectedStates parameter is not used, so the UI does
not reflect the selection state of the buttons. To fix this, pass the
corresponding selectedStates value to the isSelected parameter of each
OptionChipButton to visually indicate which button is selected.

Comment on lines +56 to +61
LogoTopAppBar(
leftIcon = painterResource(R.drawable.ic_done),
hasNotification = false,
onLeftClick = { },
onRightClick = { }
)
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

TopAppBar 아이콘 사용법을 검토해주세요

왼쪽 아이콘으로 ic_done을 사용하고 있는데, 일반적으로 done 아이콘은 완료/확인 작업에 사용됩니다. GroupScreen의 맥락에서 적절한 아이콘인지 확인이 필요합니다.

아이콘 사용이 적절한지 확인해주세요:


🏁 Script executed:

#!/bin/bash
# Description: GroupScreen의 topappbar에서 사용하는 아이콘이 적절한지 확인

# 다른 screen들에서 LogoTopAppBar의 leftIcon 사용 패턴 확인
rg -A 3 -B 3 "LogoTopAppBar" --type kotlin

Length of output: 3045


TopAppBar 왼쪽 아이콘 교체 검토 필요
LogoTopAppBarPreview 에서는 ic_search 를, 실제 GroupScreen 에서는 ic_done 를 사용 중입니다.
done 아이콘은 완료·확인 액션용이므로, 상단바 왼쪽 내비게이션에는 뒤로가기 아이콘이 더 적합합니다.

  • 파일: app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt
  • 위치: 56행 (LogoTopAppBar 호출 부분)

제안 수정안:

- LogoTopAppBar(
-     leftIcon = painterResource(R.drawable.ic_done),
+ LogoTopAppBar(
+     leftIcon = painterResource(R.drawable.ic_arrow_back),  // 뒤로가기 아이콘으로 교체
      hasNotification = false,
      onLeftClick = { /* navController.popBackStack() 등 뒤로가기 로직 연결 */ },
      onRightClick = { }
  )
📝 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
LogoTopAppBar(
leftIcon = painterResource(R.drawable.ic_done),
hasNotification = false,
onLeftClick = { },
onRightClick = { }
)
LogoTopAppBar(
leftIcon = painterResource(R.drawable.ic_arrow_back), // 뒤로가기 아이콘으로 교체
hasNotification = false,
onLeftClick = { /* navController.popBackStack() 등 뒤로가기 로직 연결 */ },
onRightClick = { }
)
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt around line
56, the left icon of LogoTopAppBar is set to ic_done, which is more suitable for
confirmation actions. Replace this icon with a back navigation icon such as
ic_back or a similar resource to better represent the expected behavior of the
top app bar's left icon.

Comment on lines 32 to 34
val cardWidth = 320.dp // 카드 폭 원하는 값으로 맞추기!
val pageSpacing = 12.dp

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

하드코딩된 값들을 상수로 추출해주세요

카드 폭과 페이지 간격이 하드코딩되어 있습니다. 유지보수와 일관성을 위해 상수로 추출하는 것이 좋습니다.

상수를 companion object로 추출해주세요:

+companion object {
+    private val CARD_WIDTH = 320.dp
+    private val PAGE_SPACING = 12.dp
+}
+
-    val cardWidth = 320.dp  // 카드 폭 원하는 값으로 맞추기!
-    val pageSpacing = 12.dp
+    val cardWidth = CARD_WIDTH
+    val pageSpacing = PAGE_SPACING

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/ui/group/myroom/component/GroupDeadlineRoomSection.kt
around lines 32 to 34, the cardWidth and pageSpacing values are hardcoded. To
improve maintainability and consistency, extract these hardcoded values into
constants inside a companion object within the class. Define cardWidth and
pageSpacing as val constants in the companion object and replace the hardcoded
usages with these constants.

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-50)]

4 participants