Skip to content

[Feat/#38] 프로필 취향 키워드 칩 컴포넌트 구현#82

Merged
nahy-512 merged 7 commits into
developfrom
feat/#38-profile-pref-component
Jan 14, 2026
Merged

[Feat/#38] 프로필 취향 키워드 칩 컴포넌트 구현#82
nahy-512 merged 7 commits into
developfrom
feat/#38-profile-pref-component

Conversation

@nahy-512

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

Copy link
Copy Markdown
Contributor

📮 관련 이슈

📌 작업 내용

  • 프로필 취향 키워드 칩 구현했어요.
  • 글자수 따라서 가운데만 늘어나야 해서 9-patch라는 친구로 이미지 저장했어요

📸 스크린샷

스크린샷
image

😅 미구현

  • N/A

🫛 To. 리뷰어

  • 이미지는 서버에서 url로 내려주기로 해서 String으로 받았어요. 매칭 로직이 꽤나 복잡
  • 구현 이슈로 png 이미지를 1배수로 넣으니까 좀 깨지긴 하는데,, 화면에서 보면 좀 괜찮은 거 같기도

Summary by CodeRabbit

  • 새 기능
    • 프로필 키워드를 칩 형태로 표시하는 새 UI 컴포넌트 추가(소형/대형 변형)
    • 대형 칩에 이미지와 색상 배경(초록, 주황, 노랑, 파랑, 분홍) 지원
    • 9-패치 드로어블을 이용한 향상된 배경 렌더링 지원으로 시각적 표현 개선
    • 키워드 표시용 타입 계층(크기/색상 선호) 추가로 구성 및 스타일 지정 간편화

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

@nahy-512 nahy-512 self-assigned this Jan 13, 2026
@nahy-512 nahy-512 requested a review from a team as a code owner January 13, 2026 18:49
@nahy-512 nahy-512 added 🧩 Component feat - 공통 컴포넌트 작업 Feat ✨ 신규 기능을 추가하거나 기존 기능의 동작, 정책을 변경 labels Jan 13, 2026
@nahy-512 nahy-512 linked an issue Jan 13, 2026 that may be closed by this pull request
1 task
@coderabbitai

coderabbitai Bot commented Jan 13, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

나인-패치 드로어블을 렌더링하는 Modifier 확장 함수 추가, 키워드 표시용 타입 계층(KeywordType/PreferenceType) 도입, 그리고 소형/대형 변형을 지원하는 ProfileKeywordChip 컴포저블이 추가되었습니다.

Changes

Cohort / File(s) Summary
드로잉 유틸리티
app/src/main/java/com/flint/core/common/extension/ModifierExt.kt
fun Modifier.draw9Patch(context: Context, @DrawableRes ninePatchRes: Int): Modifier 추가; drawBehind/drawIntoCanvasContextCompat로 나인-패치 드로어블을 Compose 캔버스에 렌더링합니다.
타입 정의 시스템
app/src/main/java/com/flint/domain/type/PreferenceType.kt
sealed class KeywordType( Small, Large(val preferenceType: PreferenceType) ) 및 enum class PreferenceType(@DrawableRes backgroundRes: Int)(Green, Orange, Yellow, Blue, Pink) 추가.
UI 컴포넌트
app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordChip.kt
ProfileKeywordChip(keyword: String, keywordType: KeywordType, keywordImageUrl: String = "") 공개 컴포저블 및 ProfileSmallKeywordChip, ProfileLargeKeywordChip, Preview 추가. 타입에 따라 소형(회색/기본) 또는 대형(선호 배경 + 이미지) 칩을 렌더링합니다.

Sequence Diagram(s)

sequenceDiagram
    participant App as 앱
    participant PKC as ProfileKeywordChip
    participant KT as KeywordType
    participant PT as PreferenceType
    participant ME as ModifierExt.draw9Patch
    participant Canvas as Compose Canvas

    App->>PKC: ProfileKeywordChip(keyword, keywordType)
    PKC->>KT: keywordType 검사
    alt Small
        PKC->>ME: draw9Patch(gray_ninepatch)
        ME->>Canvas: 나인-패치 렌더링(크기 기준 바운드)
        PKC->>Canvas: 텍스트 렌더링
    else Large
        PKC->>PT: preferenceType.backgroundRes 조회
        PKC->>ME: draw9Patch(preference_background)
        ME->>Canvas: 나인-패치 렌더링
        PKC->>Canvas: 이미지 렌더링 → 텍스트 렌더링
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰 나인-패치로 캔버스에 칩을 그려,
색깔 입힌 취향이 반짝이네.
모디파이어로 부드럽게 채우고,
작은 칩도 큰 칩도 웃음 짓네.
토끼가 건네는 축하당근 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
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.
Linked Issues check ❓ Inconclusive PR의 주요 변경사항(ProfileKeywordChip 컴포넌트 구현)이 #38 이슈의 바텀 네비게이션 설정 목표와 직접적인 관련이 부족해 보입니다. PR #82가 #38 이슈와 명확한 연관성이 있는지 확인이 필요합니다. 프로필 키워드 칩이 바텀 네비게이션 구현의 필수 컴포넌트인지 재검토하시기 바랍니다.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 프로필 취향 키워드 칩 컴포넌트 구현이라는 주요 변경사항을 명확하게 요약하고 있습니다.
Description check ✅ Passed 템플릿의 모든 필수 섹션(관련 이슈, 작업 내용, 스크린샷, 미구현, 리뷰어 노트)이 완성되어 있습니다.
Out of Scope Changes check ✅ Passed ModifierExt.kt의 draw9Patch 확장함수, PreferenceType 타입 정의, ProfileKeywordChip 컴포넌트는 모두 프로필 키워드 칩 구현에 필요한 범위 내 변경사항입니다.

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

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
app/src/main/java/com/flint/domain/type/PreferenceType.kt (1)

1-22: Domain 레이어에서 Android 리소스 의존성을 갖고 있습니다.

com.flint.domain.type 패키지는 도메인 레이어를 의미하는 것으로 보이는데, @DrawableResR.drawable.* 같은 Android 프레임워크 의존성이 포함되어 있습니다. Clean Architecture 관점에서 도메인 레이어는 순수 Kotlin으로 유지하는 것이 권장됩니다.

다음 옵션을 고려해 주세요:

  1. 이 파일을 presentation 레이어(예: com.flint.presentation.type)로 이동
  2. 또는 PreferenceType에서 리소스 매핑을 분리하여, 도메인 타입은 순수하게 유지하고 리소스 매핑은 presentation 레이어에서 처리
♻️ 제안하는 리팩토링 방안

옵션 1: presentation 레이어로 이동

-package com.flint.domain.type
+package com.flint.presentation.type

옵션 2: 리소스 매핑 분리 (도메인 타입)

// domain/type/PreferenceType.kt
package com.flint.domain.type

enum class PreferenceType {
    Green, Orange, Yellow, Blue, Pink
}
// presentation/mapper/PreferenceTypeMapper.kt
package com.flint.presentation.mapper

import com.flint.R
import com.flint.domain.type.PreferenceType

fun PreferenceType.toBackgroundRes(): Int = when (this) {
    PreferenceType.Green -> R.drawable.bg_tag_green
    PreferenceType.Orange -> R.drawable.bg_tag_orange
    PreferenceType.Yellow -> R.drawable.bg_tag_yellow
    PreferenceType.Blue -> R.drawable.bg_tag_blue
    PreferenceType.Pink -> R.drawable.bg_tag_pink
}
📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between afa5bb3 and c82b4c2.

📒 Files selected for processing (1)
  • app/src/main/java/com/flint/domain/type/PreferenceType.kt
⏰ 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 (1)
app/src/main/java/com/flint/domain/type/PreferenceType.kt (1)

6-12: LGTM!

KeywordType sealed class 구조가 잘 설계되어 있습니다. Smalldata object를, Largedata class를 사용한 패턴이 적절합니다.


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

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 0

🧹 Nitpick comments (6)
app/src/main/java/com/flint/core/common/extension/ModifierExt.kt (1)

85-97: Drawable 로딩 최적화 고려 필요

현재 구현은 drawBehind 람다가 실행될 때마다 ContextCompat.getDrawable을 호출합니다. Drawable 캐싱을 통해 성능을 개선할 수 있습니다. 또한 drawable이 null인 경우 조용히 실패하므로 개발 중 디버깅이 어려울 수 있습니다.

♻️ 개선된 구현 제안
-fun Modifier.draw9Patch(
-    context: Context,
-    @DrawableRes ninePatchRes: Int,
-) = this.drawBehind {
-    drawIntoCanvas {
-        ContextCompat.getDrawable(context, ninePatchRes)?.let { ninePatch ->
-            ninePatch.run {
-                bounds = Rect(0, 0, size.width.toInt(), size.height.toInt())
-                draw(it.nativeCanvas)
+@Composable
+fun Modifier.draw9Patch(
+    @DrawableRes ninePatchRes: Int,
+): Modifier {
+    val context = LocalContext.current
+    val drawable = remember(ninePatchRes) {
+        ContextCompat.getDrawable(context, ninePatchRes)
+    }
+    return this.drawBehind {
+        drawIntoCanvas { canvas ->
+            drawable?.let { ninePatch ->
+                ninePatch.bounds = Rect(0, 0, size.width.toInt(), size.height.toInt())
+                ninePatch.draw(canvas.nativeCanvas)
             }
         }
     }
 }

참고: @Composable로 변경하면 LocalContext를 내부에서 가져올 수 있어 호출부에서 context를 전달할 필요가 없어집니다.

app/src/main/java/com/flint/domain/type/PreferenceType.kt (3)

6-9: 주석 처리된 코드 제거 필요

주석 처리된 이전 구현 코드는 정리되어야 합니다. 버전 관리 시스템에서 히스토리를 추적할 수 있으므로 불필요한 주석 코드는 삭제하는 것이 좋습니다.


20-28: Domain 레이어의 Android 리소스 의존성

Domain 레이어(com.flint.domain.type)에서 @DrawableResR.drawable.*를 사용하고 있습니다. Clean Architecture 관점에서 Domain 레이어는 Android 프레임워크에 의존하지 않는 것이 이상적입니다.

대안으로 PreferenceType을 presentation 레이어로 이동하거나, Domain에서는 타입만 정의하고 drawable 매핑은 presentation 레이어에서 처리하는 방법이 있습니다.

프로젝트 규모와 팀 컨벤션에 따라 현재 구조를 유지해도 무방합니다.


14-17: 주석 처리된 imageUrl 프로퍼티

PR 설명에 이미지 리소스가 서버에서 URL로 내려온다고 명시되어 있습니다. 향후 구현 예정이라면 TODO 주석으로 명시하거나, 당장 불필요하다면 삭제하는 것이 좋습니다.

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

26-47: API 설계 개선 제안

keywordImageUrl 파라미터는 KeywordType.Large일 때만 사용됩니다. 현재 구조에서는 Small 타입 사용 시에도 불필요한 파라미터가 노출됩니다.

♻️ 타입 안전한 API 제안

KeywordType.Large 클래스에 imageUrl을 포함시키는 방안:

// PreferenceType.kt에서
data class Large(
    val preferenceType: PreferenceType,
    val imageUrl: String = "",
) : KeywordType()
 @Composable
 fun ProfileKeywordChip(
     keyword: String,
     keywordType: KeywordType,
-    keywordImageUrl: String = "",
 ) {
     when (keywordType) {
         is KeywordType.Small -> {
             ProfileSmallKeywordChip(keyword = keyword)
         }
         is KeywordType.Large -> {
             ProfileLargeKeywordChip(
                 keyword = keyword,
                 keywordType = keywordType,
-                imageUrl = keywordImageUrl,
+                imageUrl = keywordType.imageUrl,
             )
         }
     }
 }

이렇게 하면 Large 타입 생성 시 imageUrl을 함께 전달하게 되어 API가 더 명확해집니다.


104-125: Preview에 간격 추가 제안

Preview Row에서 칩 간 간격이 없어 미리보기에서 두 칩이 붙어 보일 수 있습니다.

♻️ 간격 추가
         Row(
             verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.spacedBy(8.dp),
         ) {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ada0a48 and afa5bb3.

⛔ Files ignored due to path filters (6)
  • app/src/main/res/drawable/bg_tag_blue.9.png is excluded by !**/*.png
  • app/src/main/res/drawable/bg_tag_gray.9.png is excluded by !**/*.png
  • app/src/main/res/drawable/bg_tag_green.9.png is excluded by !**/*.png
  • app/src/main/res/drawable/bg_tag_orange.9.png is excluded by !**/*.png
  • app/src/main/res/drawable/bg_tag_pink.9.png is excluded by !**/*.png
  • app/src/main/res/drawable/bg_tag_yellow.9.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • app/src/main/java/com/flint/core/common/extension/ModifierExt.kt
  • app/src/main/java/com/flint/domain/type/PreferenceType.kt
  • app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordChip.kt
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordChip.kt (2)
app/src/main/java/com/flint/core/designsystem/component/image/NetworkImage.kt (1)
  • NetworkImage (22-46)
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 (2)
app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordChip.kt (2)

49-68: LGTM!

ProfileSmallKeywordChip 구현이 적절합니다. 9-patch 배경 적용과 최소 너비 제약이 잘 설정되어 있습니다.


70-102: Large 칩 구현 확인

구현이 전반적으로 잘 되어 있습니다. 한 가지 확인 사항: imageUrl이 빈 문자열인 경우 NetworkImageFlintTheme.colors.gray200 색상의 error placeholder를 표시합니다. 의도된 동작인지 확인 부탁드립니다.

@giovannijunseokim giovannijunseokim left a comment

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.

LGTM 🚀

fun Modifier.draw9Patch(
context: Context,
@DrawableRes ninePatchRes: Int,
) = this.drawBehind {

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: 위의 Modifier.dropShadow()처럼 this 명시 없이 해주셔도 될 것 같습니다! 👍

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.

ㅎㅎ 추후에 반영해보겠습니다!

Comment on lines +70 to +92
@Composable
private fun ProfileLargeKeywordChip(
keyword: String,
keywordType: KeywordType.Large,
imageUrl: String,
) {
Box(
Modifier
.draw9Patch(LocalContext.current, keywordType.preferenceType.backgroundRes)
.padding(
vertical = 12.dp,
horizontal = 28.dp,
),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
NetworkImage(
imageUrl = imageUrl,
contentDescription = null,
modifier = Modifier.size(20.dp),
)

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.

p2: ProfileLargeKeywordChip에서 imageUrl을 제공하나요? 피그마에서 봤을 땐 아이콘만 들어간다고 생각했어요. 👀

Image

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.

아이콘이 들어가는데, 들어가는 로직들이 복잡해서 프론트에서 받게 되면 내부적으로 키워드를 다 저장하고, 매칭 로직도 가지고 있어야 하겠더라구요!!
그래서 키워드 추가나 변동 가능성에 대해 유연하게 대처하고자 서버 분들께 url로 내려달라고 말씀드려놨습니다! 아직 서버 dto 모델은 안 잡힌 것 같아 자세히 확인은 못 해봤는데, 확인 가능해지는 대로 데이터 확인해보려고 합니다!

image

@nahy-512 nahy-512 merged commit 088e548 into develop Jan 14, 2026
3 checks passed
@nahy-512 nahy-512 deleted the feat/#38-profile-pref-component branch January 14, 2026 08:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🧩 Component feat - 공통 컴포넌트 작업 Feat ✨ 신규 기능을 추가하거나 기존 기능의 동작, 정책을 변경

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 프로필 취향 키워드 칩 컴포넌트 구현

2 participants