Skip to content

[Feat] 프로필 화면 UI 구현#114

Merged
nahy-512 merged 11 commits into
developfrom
feat/#107-profile-screen-ui
Jan 16, 2026
Merged

[Feat] 프로필 화면 UI 구현#114
nahy-512 merged 11 commits into
developfrom
feat/#107-profile-screen-ui

Conversation

@nahy-512
Copy link
Copy Markdown
Contributor

@nahy-512 nahy-512 commented Jan 15, 2026

📮 관련 이슈

📌 작업 내용

📸 스크린샷

스크린샷
2026-01-16.1.04.03.mov

😅 미구현

  • 3줄일 때 배치
  • 바텀시트 표시

🫛 To. 리뷰어

FlowRow로 구현했는데 2줄일 때랑 3줄일 때랑 컴포넌트 배치가 아예 달라서 고민이네요.
image

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 프로필 화면에 새로운 키워드 섹션 추가 (칩 및 그래프 표시)
    • 프로필 상단 섹션 추가 (사용자 정보 및 프로필 이미지 표시)
    • 새로고침 아이콘 추가
  • Refactor

    • 홈 화면 컬렉션 및 저장된 콘텐츠 섹션 구조 개선
    • 프로필 화면을 지연 로딩 레이아웃으로 완전 재설계
    • 컴포넌트 매개변수 명명 규칙 정리

✏️ Tip: You can customize this high-level summary in your review settings.

@nahy-512 nahy-512 self-assigned this Jan 15, 2026
@nahy-512 nahy-512 requested a review from a team as a code owner January 15, 2026 16:21
@nahy-512 nahy-512 added Feat ✨ 신규 기능을 추가하거나 기존 기능의 동작, 정책을 변경 📱 UI feat - 컴포넌트 조립, 화면 구현 labels Jan 15, 2026
@nahy-512 nahy-512 linked an issue Jan 15, 2026 that may be closed by this pull request
1 task
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 15, 2026

📝 Walkthrough

Walkthrough

이 PR은 홈 및 프로필 화면 UI 컴포넌트를 리팩토링하여 새로운 모듈식 섹션들(CollectionSection, SavedContentsSection, ProfileKeywordSection, ProfileTopSection)을 도입하고, 기존 컴포넌트를 제거하며, 불변 리스트 타입으로 마이그레이션하고 테스트 데이터 모델을 추가합니다.

Changes

Cohort / File(s) 요약
Home 화면 컴포넌트 리팩토링
CollectionSection.kt, SavedContentsSection.kt, HomeScreen.kt
HomeRecentCollection을 CollectionSection으로 리팩토링하여 동적 제목/설명 지원 추가; 새로운 SavedContentsSection 컴포넌트 도입; HomeScreen에서 불변 리스트(ImmutableList) 사용으로 마이그레이션 및 새 섹션 컴포넌트 통합
기존 Home 컴포넌트 제거
HomeRecommendCollection.kt, HomeSavedContents.kt
HomeRecommendCollection 및 HomeSavedContents 컴포넌트 전체 삭제 (CollectionSection, SavedContentsSection으로 대체)
프로필 화면 구조 개선
ProfileScreen.kt
LazyColumn 기반 데이터-주도형 레이아웃으로 재작성; TopSection, KeywordSection, CollectionSection, SavedContentsSection 조합
프로필 섹션 컴포넌트
ProfileTopSection.kt, ProfileKeywordSection.kt
프로필 헤더 섹션(사용자명, 프로필 이미지, 배경 포함) 및 키워드 섹션(칩과 그래프 레이아웃) 새로 추가
프로필 키워드 항목 리팩토링
ProfileKeywordGraphItem.kt
ProfileKeywordGraph → ProfileKeywordGraphItem으로 함수명 변경; percent 파라미터를 percentage로 리네이밍; UI 개선 사항 추가
도메인 모델 확장
CollectionModel.kt, ContentModel.kt, PreferenceKeywordModel.kt
CollectionModel, ContentModel에 FakeList 추가; 새로운 PreferenceKeywordModel 데이터 클래스와 rotateKeywordByRank 유틸리티 함수, FakeList1/FakeList2 도입
리소스 추가
ic_refresh.xml
새로운 새로고침 아이콘 벡터 드로어블 리소스 (24x24dp, 파란색 획)

Sequence Diagram(s)

sequenceDiagram
    participant ProfileScreen
    participant LazyColumn
    participant TopSection
    participant KeywordSection
    participant CollectionSection
    participant SavedContentsSection

    ProfileScreen->>LazyColumn: 렌더링 시작 (sections 반복)
    LazyColumn->>TopSection: TopSection 렌더링
    TopSection-->>LazyColumn: 프로필 헤더 표시
    LazyColumn->>KeywordSection: KeywordSection 렌더링 (keywordList)
    KeywordSection->>KeywordSection: rotateKeywordByRank 호출
    KeywordSection-->>LazyColumn: 키워드 칩 + 그래프 표시
    LazyColumn->>CollectionSection: CollectionSection 렌더링 (컬렉션 목록)
    CollectionSection-->>LazyColumn: 컬렉션 항목 표시
    LazyColumn->>SavedContentsSection: SavedContentsSection 렌더링 (콘텐츠 목록)
    SavedContentsSection-->>LazyColumn: 저장한 콘텐츠 항목 표시
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • [Feat] 프로필 키워드 그래프 컴포넌트 구현 #89: ProfileKeywordGraph에서 ProfileKeywordGraphItem으로의 리네이밍 및 percent → percentage 파라미터 변경을 포함하며, 프로필 키워드 UI 컴포넌트와 PreferenceType 타입을 동일하게 다룸
  • [Feat] Home UI #104: HomeRecentCollection, HomeSavedContents, HomeRecommendCollection 등 동일한 Home UI 컴포넌트와 리스트 항목 컴포넌트를 수정하며, HomeScreen 연결 구조 변경이 겹침
  • [Feat] 스크린 Route 연결 #94: HomeScreen/HomeRoute 시그니처와 네비게이션 연결 변경을 포함하여 Home 화면 API 인터페이스 레벨에서 관련

Suggested labels

🧩 Component, ✨ Feature

Suggested reviewers

  • giovannijunseokim

Poem

🐰✨ 프로필 화면이 새로 옷을 입고,
섹션들이 춤을 추며 춤을 춘다네.
불변 리스트로 안전하게,
LazyColumn 위에서 우아하게 흐른다. 🎨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed PR의 코드 변경사항들이 이슈 #107의 '프로필 화면 UI 구현' 요구사항을 충족합니다. ProfileTopSection, ProfileKeywordSection, CollectionSection, SavedContentsSection 등의 새로운 UI 컴포넌트들이 구현되었고, ProfileScreen이 데이터 기반 레이아웃으로 재구성되었습니다.
Out of Scope Changes check ✅ Passed 홈 화면 컴포넌트들(HomeRecommendCollection, HomeSavedContents)의 리팩터링과 새로운 공통 컴포넌트(CollectionSection, SavedContentsSection, PreferenceKeywordModel)의 추가는 프로필 UI 구현을 지원하기 위한 범위 내 변경사항으로 볼 수 있습니다.
Title check ✅ Passed 제목이 PR의 주요 변경사항을 명확하게 요약하고 있습니다. 여러 컴포넌트 추가, 홈 화면 리팩토링, 프로필 화면 구현 등의 변경사항이 모두 '프로필 화면 UI 구현'이라는 핵심 목표에 포함됩니다.
Description check ✅ Passed PR 설명이 주어진 템플릿 구조를 따르고 있으며, 관련 이슈, 작업 내용, 스크린샷, 미구현 항목, 리뷰어 참고사항을 모두 포함하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@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 (2)
app/src/main/java/com/flint/presentation/home/HomeScreen.kt (2)

30-46: navigateToCollectionListnavigateToCollectionCreate가 HomeScreen에 전달되지 않습니다.

HomeRoute에서 받은 navigateToCollectionListnavigateToCollectionCreate 콜백이 HomeScreen에 전달되지 않아 FAB 클릭과 "전체보기" 기능이 동작하지 않습니다.

🐛 수정 제안
 fun HomeRoute(
     paddingValues: PaddingValues,
     navigateToCollectionList: () -> Unit,
     navigateToCollectionDetail: (collectionId: String) -> Unit,
     navigateToCollectionCreate: () -> Unit,
 ) {
     HomeScreen(
         onRecommendCollectionItemClick = { collectionId ->
             navigateToCollectionDetail(collectionId)
         },
         onSavedContentItemClick = { contentId ->
             // TODO show OttListBottomSheet
         },
+        onRecentCollectionAllClick = navigateToCollectionList,
+        navigateToCollectionCreate = navigateToCollectionCreate,
         modifier = Modifier.padding(paddingValues),
     )
 }

154-154: 주석의 emptyList() 대신 persistentListOf()를 사용하세요.

ImmutableList 타입을 사용하고 있으므로, 빈 리스트를 전달할 때는 emptyList() 대신 persistentListOf()를 사용해야 합니다.

📝 수정 제안
-            recentCollectionModelList = collectionModelList, // or 'emptyList()'
+            recentCollectionModelList = collectionModelList, // or 'persistentListOf()'
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/flint/domain/model/PreferenceKeywordModel.kt`:
- Around line 17-32: rotateKeywordByRank currently zips topRanks and bottomRanks
which drops any unmatched elements when sizes differ (e.g., 4 or 5 items), so
change the assembly logic to interleave pairs from topRanks and bottomRanks and
then append any remaining elements from the longer list; specifically, in
rotateKeywordByRank after computing topRanks and bottomRanks use the zip result
plus the remainder of topRanks or bottomRanks (starting at the zip size) to
ensure no element is lost, preserving original rank order within each subgroup.

In
`@app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordSection.kt`:
- Around line 97-100: Calling keywordList.subList(0, MAX) can throw
IndexOutOfBoundsException when keywordList.size < MAX; update the call in
ProfileKeywordSection (where KeywordGraphLayout is invoked) to pass
keywordList.take(MAX) (and convert to immutable with toImmutableList() if an
immutable list is required), ensuring to import toImmutableList() from
kotlinx.collections.immutable when used.
🧹 Nitpick comments (12)
app/src/main/java/com/flint/domain/model/CollectionModel.kt (1)

14-46: FakeList 데이터에 고유 ID 사용을 권장합니다.

Preview나 테스트용 더미 데이터로 적절하지만, collectionId가 빈 문자열이고 userId가 모두 0입니다. LazyColumn에서 key로 사용할 경우 중복 키 문제가 발생할 수 있으므로, 각 항목에 고유한 ID 값을 설정하는 것이 좋습니다.

💡 고유 ID 적용 예시
 CollectionModel(
-    collectionId = "",
+    collectionId = "fake_1",
     collectionTitle = "컬렉션 제목",
     ...
     author =
         AuthorModel(
-            userId = 0,
+            userId = 1,
             ...
         ),
 ),
 CollectionModel(
-    collectionId = "",
+    collectionId = "fake_2",
     collectionTitle = "컬렉션 제목2",
     ...
     author =
         AuthorModel(
-            userId = 0,
+            userId = 2,
             ...
         ),
 ),
app/src/main/java/com/flint/domain/model/ContentModel.kt (1)

13-62: FakeList 항목에 고유한 contentId 사용을 권장합니다.

CollectionModel.FakeList와 동일하게 모든 항목의 contentId가 0으로 설정되어 있습니다. 리스트 렌더링 시 key 파라미터로 사용될 경우 성능 저하나 예상치 못한 동작이 발생할 수 있습니다.

💡 고유 contentId 적용 예시
 ContentModel(
-    contentId = 0,
+    contentId = 1,
     title = "드라마 제목",
     ...
 ),
 ContentModel(
-    contentId = 0,
+    contentId = 2,
     title = "드라마 제목2",
     ...
 ),
 ContentModel(
-    contentId = 0,
+    contentId = 3,
     title = "드라마 제목3",
     ...
 ),
 ContentModel(
-    contentId = 0,
+    contentId = 4,
     title = "드라마 제목4",
     ...
 ),
app/src/main/java/com/flint/presentation/profile/component/ProfileTopSection.kt (2)

38-96: 불필요한 외부 Column 래퍼 제거를 권장합니다.

외부 Column(Line 38)은 하나의 Box 자식만 포함하고 있어 불필요합니다. 또한 modifier 파라미터가 내부 Box에 적용되어 있어, 외부에서 전달된 modifier가 예상과 다르게 동작할 수 있습니다.

♻️ 리팩토링 제안
 `@Composable`
 fun ProfileTopSection(
     userName: String,
     profileUrl: String,
     isFliner: Boolean,
     modifier: Modifier = Modifier,
 ) {
-    Column {
-        Box(
-            modifier =
-                modifier
-                    .fillMaxWidth()
-                    .height(260.dp)
+    Box(
+        modifier =
+            modifier
+                .fillMaxWidth()
+                .height(260.dp)
                     ...
-        )
-    }
+    )
 }

49-52: 불필요한 .copy(1f) 호출 제거

Color(0xFF424BBD).copy(1f)에서 .copy(1f)는 기본값(alpha = 1f)이므로 생략할 수 있습니다.

💡 간소화 제안
 val gradient =
     Brush.verticalGradient(
-        colors = listOf(Color(0xFF424BBD).copy(1f), Color(0xFF121212).copy(alpha = 0.04f)),
+        colors = listOf(Color(0xFF424BBD), Color(0xFF121212).copy(alpha = 0.04f)),
     )
app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordGraphItem.kt (1)

101-127: Preview 함수명 일관성 고려

컴포넌트명이 ProfileKeywordGraphItem으로 변경되었으나, Preview 함수명은 ProfileKeywordGraphPreview로 유지되어 있습니다. 일관성을 위해 ProfileKeywordGraphItemPreview로 변경을 고려해 주세요.

💡 이름 변경 제안
 `@Preview`(showBackground = false)
 `@Composable`
-private fun ProfileKeywordGraphPreview() {
+private fun ProfileKeywordGraphItemPreview() {
app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordSection.kt (1)

79-83: 접근성: 아이콘에 contentDescription 추가 권장

새로고침 아이콘의 contentDescriptionnull로 설정되어 있습니다. 스크린 리더 사용자를 위해 의미 있는 설명을 추가하는 것이 좋습니다.

♻️ 제안하는 수정
 Icon(
     imageVector = ImageVector.vectorResource(R.drawable.ic_refresh),
-    contentDescription = null,
+    contentDescription = "취향 키워드 업데이트",
     tint = FlintTheme.colors.secondary400,
 )
app/src/main/java/com/flint/core/designsystem/component/listView/CollectionSection.kt (2)

32-40: Compose 컨벤션: modifier 파라미터 위치

Compose 컨벤션에서는 modifier가 필수 파라미터 다음, 선택적 파라미터 앞에 위치하는 것이 권장됩니다. 현재 isAllVisible, onAllClick 등 필수 콜백들 사이에 위치해 있습니다.


93-100: itemsIndexed 대신 itemskey 파라미터 사용 권장

index가 사용되지 않으므로 items()로 충분하며, key를 지정하면 리스트 변경 시 불필요한 recomposition을 방지할 수 있습니다. CollectionModel에는 고유 식별자인 collectionId가 있으므로 이를 key로 활용할 수 있습니다.

♻️ 제안하는 수정
-itemsIndexed(collectionModelList) { _, item ->
+items(
+    items = collectionModelList,
+    key = { it.collectionId }
+) { item ->
     CollectionItem(
         collectionModel = item,
         onItemClick = { id ->
             onItemClick(id)
         },
     )
 }
app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt (1)

76-83: onRefreshClick이 빈 람다로 설정됨

ProfileKeywordSectiononRefreshClick{}로 전달되어 새로고침 버튼이 아무 동작도 하지 않습니다. 향후 구현 예정이라면 TODO 주석을 추가하는 것이 좋습니다.

♻️ TODO 주석 추가 제안
 ProfileKeywordSection(
     nickname = userName,
     keywordList = keywordList,
-    onRefreshClick = {},
+    onRefreshClick = {}, // TODO: 키워드 새로고침 기능 구현
     modifier =
         Modifier.fillMaxWidth(),
 )
app/src/main/java/com/flint/core/designsystem/component/listView/SavedContentsSection.kt (3)

94-101: itemsIndexed 대신 itemskey 사용 권장

CollectionSection과 동일하게, index가 사용되지 않으므로 items()key를 사용하는 것이 좋습니다.

♻️ 제안하는 수정
-itemsIndexed(contentModelList) { _, item ->
+items(
+    items = contentModelList,
+    key = { it.contentId }
+) { item ->
     SavedContentItem(
         contentModel = item,
         onItemClick = { contentId ->
             onItemClick(contentId)
         },
     )
 }

106-121: Preview 함수명 불일치

함수명이 PreviewHomeSavedContents로 "Home" 접두사가 포함되어 있지만, 컴포넌트명은 SavedContentsSection입니다. 일관성을 위해 SavedContentsSectionPreview로 변경하는 것이 좋습니다.

♻️ 제안하는 수정
 `@Preview`
 `@Composable`
-private fun PreviewHomeSavedContents() {
+private fun SavedContentsSectionPreview() {

31-104: CollectionSection과 코드 중복 가능성

SavedContentsSectionCollectionSection의 헤더 Row, 아이콘 처리, LazyRow 패턴이 거의 동일합니다. 향후 유지보수성을 위해 공통 헤더 컴포넌트를 추출하는 것을 고려해 볼 수 있습니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b41ead and 2a91819.

📒 Files selected for processing (13)
  • app/src/main/java/com/flint/core/designsystem/component/listView/CollectionSection.kt
  • app/src/main/java/com/flint/core/designsystem/component/listView/SavedContentsSection.kt
  • app/src/main/java/com/flint/domain/model/CollectionModel.kt
  • app/src/main/java/com/flint/domain/model/ContentModel.kt
  • app/src/main/java/com/flint/domain/model/PreferenceKeywordModel.kt
  • app/src/main/java/com/flint/presentation/home/HomeScreen.kt
  • app/src/main/java/com/flint/presentation/home/component/HomeRecommendCollection.kt
  • app/src/main/java/com/flint/presentation/home/component/HomeSavedContents.kt
  • app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt
  • app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordGraphItem.kt
  • app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordSection.kt
  • app/src/main/java/com/flint/presentation/profile/component/ProfileTopSection.kt
  • app/src/main/res/drawable/ic_refresh.xml
💤 Files with no reviewable changes (2)
  • app/src/main/java/com/flint/presentation/home/component/HomeRecommendCollection.kt
  • app/src/main/java/com/flint/presentation/home/component/HomeSavedContents.kt
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-01-13T17:03:16.816Z
Learnt from: giovannijunseokim
Repo: imflint/Flint-Android PR: 72
File: app/src/main/java/com/flint/core/designsystem/component/collection/PeopleBottomSheet.kt:85-98
Timestamp: 2026-01-13T17:03:16.816Z
Learning: In PeopleBottomSheet.kt, the `people` list passed to LazyColumn is static and does not change while the bottom sheet is visible, so a key parameter is not required for the items() call.

Applied to files:

  • app/src/main/java/com/flint/presentation/home/HomeScreen.kt
  • app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordGraphItem.kt
  • app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt
📚 Learning: 2026-01-13T19:02:41.580Z
Learnt from: chanmi1125
Repo: imflint/Flint-Android PR: 77
File: app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateThumbnail.kt:45-72
Timestamp: 2026-01-13T19:02:41.580Z
Learning: In `app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateThumbnail.kt`, the height difference between CollectionCreateEmptyThumbnail (no aspectRatio) and CollectionCreateFillThumbnail (aspectRatio 1.5f / 1f) is intentional design - the empty state should not have the same aspectRatio as the filled state.

Applied to files:

  • app/src/main/java/com/flint/core/designsystem/component/listView/CollectionSection.kt
📚 Learning: 2026-01-13T19:02:56.195Z
Learnt from: chanmi1125
Repo: imflint/Flint-Android PR: 77
File: app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateThumbnail.kt:45-72
Timestamp: 2026-01-13T19:02:56.195Z
Learning: In `app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateThumbnail.kt`, the aspect ratio difference between `CollectionCreateEmptyThumbnail` (no aspect ratio) and `CollectionCreateFillThumbnail` (1.5f / 1f) is intentional by design.

Applied to files:

  • app/src/main/java/com/flint/core/designsystem/component/listView/CollectionSection.kt
🧬 Code graph analysis (5)
app/src/main/java/com/flint/presentation/profile/component/ProfileTopSection.kt (2)
app/src/main/java/com/flint/core/designsystem/component/image/ProfileImage.kt (1)
  • ProfileImage (11-29)
app/src/main/java/com/flint/core/designsystem/theme/Theme.kt (1)
  • FlintTheme (8-16)
app/src/main/java/com/flint/core/designsystem/component/listView/CollectionSection.kt (1)
app/src/main/java/com/flint/core/designsystem/theme/Theme.kt (1)
  • FlintTheme (8-16)
app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt (5)
app/src/main/java/com/flint/presentation/profile/component/ProfileTopSection.kt (1)
  • ProfileTopSection (31-97)
app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordSection.kt (1)
  • ProfileKeywordSection (33-102)
app/src/main/java/com/flint/core/designsystem/component/listView/CollectionSection.kt (1)
  • CollectionSection (31-103)
app/src/main/java/com/flint/core/designsystem/component/listView/SavedContentsSection.kt (1)
  • SavedContentsSection (31-104)
app/src/main/java/com/flint/core/designsystem/theme/Theme.kt (1)
  • FlintTheme (8-16)
app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordSection.kt (4)
app/src/main/java/com/flint/domain/model/PreferenceKeywordModel.kt (1)
  • rotateKeywordByRank (17-32)
app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordChip.kt (1)
  • ProfileKeywordChip (26-47)
app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordGraphItem.kt (1)
  • ProfileKeywordGraphItem (29-75)
app/src/main/java/com/flint/core/designsystem/theme/Theme.kt (1)
  • FlintTheme (8-16)
app/src/main/java/com/flint/core/designsystem/component/listView/SavedContentsSection.kt (2)
app/src/main/java/com/flint/core/designsystem/component/listItem/SavedContentItem.kt (1)
  • SavedContentItem (24-74)
app/src/main/java/com/flint/core/designsystem/theme/Theme.kt (1)
  • FlintTheme (8-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: PR Lint Check
  • GitHub Check: PR Build Check
🔇 Additional comments (9)
app/src/main/res/drawable/ic_refresh.xml (1)

1-13: LGTM!

새로 고침 아이콘이 올바르게 정의되었습니다. 벡터 드로어블 설정이 적절합니다.

app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordGraphItem.kt (1)

29-75: LGTM!

파라미터명 변경(percentpercentage)과 텍스트 정렬 개선이 잘 적용되었습니다. TextAlign.EndwidthIn(min = 32.dp) 조합으로 퍼센트 값이 일관되게 정렬됩니다.

app/src/main/java/com/flint/domain/model/PreferenceKeywordModel.kt (1)

34-91: FakeList 정의가 적절합니다.

샘플 데이터가 명확하게 정의되어 있으며, FakeList2FakeList1copy()로 재사용하는 패턴은 중복을 줄이는 좋은 접근입니다.

app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordSection.kt (1)

33-44: LGTM!

remember(keywordList)를 사용하여 rotateKeywordByRank 결과를 메모이제이션하는 것은 불필요한 재계산을 방지하는 좋은 패턴입니다.

app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt (2)

35-41: ProfileRoute에서 onFilmItemClick 콜백 미전달

ProfileScreenonFilmItemClick 파라미터가 ProfileRoute에서 전달되지 않아 기본값 빈 람다 {}가 사용됩니다. Line 52의 TODO 주석("바텀시트 띄우기")과 관련이 있는 것으로 보이지만, 향후 구현 시 누락되지 않도록 주의가 필요합니다.


57-64: LGTM!

LazyColumn을 사용한 섹션 기반 레이아웃 구조가 잘 설계되어 있습니다. overscrollEffect = null로 오버스크롤 효과를 비활성화하고 contentPadding으로 하단 여백을 확보한 것이 적절합니다.

app/src/main/java/com/flint/presentation/home/HomeScreen.kt (3)

18-28: LGTM!

ImmutableList로의 마이그레이션은 Compose의 안정성(stability) 측면에서 좋은 선택입니다. 불필요한 리컴포지션을 방지할 수 있습니다.


48-61: LGTM!

파라미터가 ImmutableList로 잘 마이그레이션되었고, 기본값으로 persistentListOf()를 사용한 것이 적절합니다.


88-129: LGTM!

새로운 CollectionSectionSavedContentsSection 컴포넌트가 적절하게 사용되었습니다. isAllVisible 플래그에 따른 조건부 표시 로직도 명확합니다.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +17 to +32
fun rotateKeywordByRank(keywordList: ImmutableList<PreferenceKeywordModel>): ImmutableList<PreferenceKeywordModel> {
if (keywordList.size < 2) return keywordList

val sortedByRank = keywordList.sortedBy { it.rank }

// rank 1, 2, 3 (상위 3개) → Large
val topRanks = sortedByRank.take(3)
// rank 4, 5, 6 (하위 3개) → Small
val bottomRanks = sortedByRank.drop(3)

// 번갈아 배치: 1, 4, 2, 5, 3, 6
return topRanks
.zip(bottomRanks)
.flatMap { (top, bottom) -> listOf(top, bottom) }
.toPersistentList()
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Jan 15, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

rotateKeywordByRank에서 리스트 크기가 6 미만일 때 요소 누락 가능성

zip은 두 리스트 중 짧은 쪽의 길이만큼만 쌍을 생성합니다. 따라서 keywordList의 크기가 4 또는 5일 경우, topRanks의 일부 요소가 결과에서 누락됩니다.

예시: 5개 아이템 → topRanks=[1,2,3], bottomRanks=[4,5] → 결과: [1,4,2,5] (3번 누락)

🐛 제안하는 수정 방안
 fun rotateKeywordByRank(keywordList: ImmutableList<PreferenceKeywordModel>): ImmutableList<PreferenceKeywordModel> {
     if (keywordList.size < 2) return keywordList

     val sortedByRank = keywordList.sortedBy { it.rank }

     // rank 1, 2, 3 (상위 3개) → Large
     val topRanks = sortedByRank.take(3)
     // rank 4, 5, 6 (하위 3개) → Small
     val bottomRanks = sortedByRank.drop(3)

     // 번갈아 배치: 1, 4, 2, 5, 3, 6
-    return topRanks
-        .zip(bottomRanks)
-        .flatMap { (top, bottom) -> listOf(top, bottom) }
-        .toPersistentList()
+    val result = mutableListOf<PreferenceKeywordModel>()
+    val maxSize = maxOf(topRanks.size, bottomRanks.size)
+    for (i in 0 until maxSize) {
+        if (i < topRanks.size) result.add(topRanks[i])
+        if (i < bottomRanks.size) result.add(bottomRanks[i])
+    }
+    return result.toPersistentList()
 }
🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/domain/model/PreferenceKeywordModel.kt` around
lines 17 - 32, rotateKeywordByRank currently zips topRanks and bottomRanks which
drops any unmatched elements when sizes differ (e.g., 4 or 5 items), so change
the assembly logic to interleave pairs from topRanks and bottomRanks and then
append any remaining elements from the longer list; specifically, in
rotateKeywordByRank after computing topRanks and bottomRanks use the zip result
plus the remainder of topRanks or bottomRanks (starting at the zip size) to
ensure no element is lost, preserving original rank order within each subgroup.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

정책상 서버에서 무조건 키워드 6개를 넘겨주는데, 그래도 처리해야할까?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nahy-512, 서버에서 항상 6개를 보장한다면 현재 구현으로도 정상 동작합니다!

다만 몇 가지 옵션을 고려해보실 수 있습니다:

  1. 현재대로 유지 - 서버 정책이 명확하다면 추가 처리 없이 그대로 사용
  2. 주석 추가 - 함수 상단에 "서버에서 항상 6개 키워드 보장" 명시
  3. 방어적 처리 - 혹시 모를 변경이나 테스트 시나리오 대비

예시 (주석 추가):

/**
 * 키워드를 rank 기준으로 번갈아 배치 (1,4,2,5,3,6)
 * `@param` keywordList 서버에서 보장하는 6개의 키워드 리스트
 */
fun rotateKeywordByRank(keywordList: ImmutableList<PreferenceKeywordModel>): ImmutableList<PreferenceKeywordModel> {

또는 assertion 추가:

fun rotateKeywordByRank(keywordList: ImmutableList<PreferenceKeywordModel>): ImmutableList<PreferenceKeywordModel> {
    require(keywordList.size == 6) { "서버는 항상 6개의 키워드를 제공해야 합니다" }
    // ... 기존 로직

팀의 코딩 스타일에 맞게 선택하시면 됩니다. 현재대로 진행하셔도 문제없습니다! 👍

Comment on lines +97 to +100
KeywordGraphLayout(
keywordList = keywordList.subList(0, MAX),
modifier = Modifier.fillMaxWidth(),
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

subList 호출 시 IndexOutOfBoundsException 발생 가능

keywordList의 크기가 MAX(3) 미만일 경우 subList(0, MAX)에서 예외가 발생합니다.

🐛 제안하는 수정 방안
 KeywordGraphLayout(
-    keywordList = keywordList.subList(0, MAX),
+    keywordList = keywordList.take(MAX).toImmutableList(),
     modifier = Modifier.fillMaxWidth(),
 )

take()를 사용하면 리스트 크기가 MAX보다 작아도 안전하게 처리됩니다. toImmutableList()kotlinx.collections.immutable에서 import해야 합니다.

📝 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
KeywordGraphLayout(
keywordList = keywordList.subList(0, MAX),
modifier = Modifier.fillMaxWidth(),
)
KeywordGraphLayout(
keywordList = keywordList.take(MAX).toImmutableList(),
modifier = Modifier.fillMaxWidth(),
)
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordSection.kt`
around lines 97 - 100, Calling keywordList.subList(0, MAX) can throw
IndexOutOfBoundsException when keywordList.size < MAX; update the call in
ProfileKeywordSection (where KeywordGraphLayout is invoked) to pass
keywordList.take(MAX) (and convert to immutable with toImmutableList() if an
immutable list is required), ensuring to import toImmutableList() from
kotlinx.collections.immutable when used.

@nahy-512 nahy-512 changed the title [Feat/#107] 프로필 화면 UI 구현 [Feat] 프로필 화면 UI 구현 Jan 15, 2026
Copy link
Copy Markdown
Contributor

@giovannijunseokim giovannijunseokim left a comment

Choose a reason for hiding this comment

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

LGTM 🦔

keyword: String,
preferenceType: PreferenceType,
percent: Int,
percentage: Int,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

p3: 사용될 때 Int 그 자체로써 쓰이는 경우는 없는 것 같아요. Float으로 받아도 좋을 것 같습니다. 👍

isFliner: Boolean,
modifier: Modifier = Modifier,
) {
Column {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

p3: 최상위를 Column으로 감싸신 이유가 있으실까요? 👀

Comment on lines +49 to +52
val gradient =
Brush.verticalGradient(
colors = listOf(Color(0xFF424BBD).copy(1f), Color(0xFF121212).copy(alpha = 0.04f)),
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

p3: 이 부분도 FlintTheme.colors에 추가하는건 어떨까요? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

디자인 시스템에 정의되지 않은 색상이라 '해당 화면에만 쓰이는 건가?' 싶어서 컴포넌트 단에서 정의를 했었는데요.
리뷰어에 내용 적는 걸 까먹었네요ㅠㅠ 디자인 쌤들께 여쭤보고 추가 부탁드렸습니다!

image

수정해두겠습니다!

Comment on lines +99 to +109
@Preview(showBackground = false)
@Composable
private fun ProfileTopSectionPreview() {
FlintTheme {
ProfileTopSection(
userName = "안두콩",
profileUrl = "",
isFliner = false,
)
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

p3: 플리너일 경우의 Preview도 볼 수 있으면 좋을 것 같습니다. 👍

Suggested change
@Preview(showBackground = false)
@Composable
private fun ProfileTopSectionPreview() {
FlintTheme {
ProfileTopSection(
userName = "안두콩",
profileUrl = "",
isFliner = false,
)
}
}
@Preview(showBackground = false)
@Composable
private fun ProfileTopSectionPreview1() {
FlintTheme {
ProfileTopSection(
userName = "안두콩",
profileUrl = "",
isFliner = false,
)
}
}
@Preview(showBackground = false)
@Composable
private fun ProfileTopSectionPreview2() {
FlintTheme {
ProfileTopSection(
userName = "안두콩",
profileUrl = "",
isFliner = true,
)
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

감사합니다ㅎㅎ 케이스 추가해보겠습니다!

import com.flint.domain.type.KeywordType
import kotlinx.collections.immutable.ImmutableList

private const val MAX = 3
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

p3: 어떤 값의 최댓값인지 써주시면 좋을 것 같아요. 👍
ex) KEYWORD_LIST_MAX_SIZE

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

나중에 수정해야지 해놓고 까먹었네요ㅎㅎ 앞으로 PR 올린 뒤에 코드 확인을 한번 더 해봐야겠어요ㅎㅎ

Comment on lines +120 to +122
modifier =
Modifier
.align(Alignment.Center),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

p3: 이미 상위 레이아웃 Box에서 Alignment.Center를 정해준 것 같아요. 👀

onAllClick()
},
)
if (isAllVisible) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

p3: 이 분기처리가 도입된 배경이 있나요? 화면설계서나 피그마에선 못 찾았어요. 😢

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

홈에서 추천 컬렉션, 최근 저장한 콘텐츠는 상세보기로 못 가게 화살표가 아예 없어서용!!
home/data stacked user 부분에서 확인해보실 수 있습니당

image

Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_more),
contentDescription = null,
tint = Color.Unspecified,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

p3: 코드만 보는 입장에서(ex: 깃허브 환경) 어떤 색인지 알기 어려울 것 같아요. FlintTheme.colors.white와 같은 식으로 명시해주는건 어떨까요?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

오앙 IDE에서 바로 흰색임을 확인할 수 있어서 별도로 설정하지 않았는데, 원본 아이콘 색상이 뭔지 코드에서 바로 알긴 힘드니 확실히 한번 더 명시해주는 편이 좋겠네요. 감사합니다!

horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.fillMaxWidth(),
) {
itemsIndexed(contentModelList) { _, item ->
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

p3: index를 사용하지 않고 있는 것 같아요. itemsIndexed 대신 items를 활용할 수 있지 않을까요? 👀

modifier
.fillMaxWidth(),
) {
FlowRow(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

p3: FlowRow처럼 자연스럽게 넘어가야 하는 상황이 아니라면 단순 Row를 쓰는게 더 편할 것 같아요. 👍

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

안그래도 2~3줄 처리에 컴포넌트 배치가 달라져서 조금 고민이었는데, 글자수 기반으로 단순 Row로 구현하는 방식이 더 나을 수도 있겠네요. 감사합니다!

@nahy-512 nahy-512 merged commit 6bbb5a1 into develop Jan 16, 2026
3 checks passed
@nahy-512 nahy-512 deleted the feat/#107-profile-screen-ui branch January 16, 2026 11:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feat ✨ 신규 기능을 추가하거나 기존 기능의 동작, 정책을 변경 📱 UI feat - 컴포넌트 조립, 화면 구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 프로필 화면 UI 구현

2 participants