Skip to content

[Feat/#19] 모달 컴포넌트#20

Merged
doyeon0307 merged 15 commits intodevelopfrom
feat/#19-modal-component
Jan 13, 2026
Merged

[Feat/#19] 모달 컴포넌트#20
doyeon0307 merged 15 commits intodevelopfrom
feat/#19-modal-component

Conversation

@doyeon0307
Copy link
Copy Markdown
Contributor

@doyeon0307 doyeon0307 commented Jan 12, 2026

Related issue 🛠️

Work Description ✏️

  • 모달 컴포넌트 구현했습니다.

Screenshot 📸

기본 모달

사진 영상
image
default.mp4

스몰 모달

사진 영상
image
default.mp4

Uncompleted Tasks 😅

N/A

To Reviewers 📢

기본 Dialog 사용 시 scrimColor 커스텀이 불가해 Popup을 활용했습니다.
모달 컴포넌트는 화면 전체를 덮는 팝업이며, 팝업 content로서 딤 영역은 Box, 모달 영역은 Box의 content인 Surface에 해당됩니다.

추가 사항

Large_1 Large_2
image image

Summary by CodeRabbit

  • New Features
    • 뒤로가기·외부탭으로 닫기 설정을 지원하는 커스터마이즈 가능한 모달 추가 (배경 흐림·둥근 흰색 표면 포함).
    • 제목·본문·취소·확인 버튼을 갖춘 소형 모달 추가 — 간단한 확인/취소 흐름 지원.
    • 제목·본문·주요 액션 및 선택적 보조 액션·이미지·추가 콘텐츠를 지원하는 대형 모달 추가.

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

@doyeon0307 doyeon0307 linked an issue Jan 12, 2026 that may be closed by this pull request
2 tasks
@doyeon0307 doyeon0307 requested a review from a team January 12, 2026 17:05
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 12, 2026

📝 Walkthrough

Walkthrough

내부 Dialog을 래핑하는 PotiModal과 이를 활용한 공개 컴포저블 PotiSmallModal(2버튼) 및 PotiLargeModal(이미지·서브액션·콘텐츠 슬롯 지원) 세 가지 모달 컴포저블이 추가되었습니다. 각 컴포저블은 뒤로/외부 클릭 해제 플래그를 노출합니다. (≤50단어)

Changes

Cohort / File(s) Summary
Base modal
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt
내부 PotiModal 추가: Dialog 래핑, dismissOnBackPress/dismissOnClickOutside 전달(=DialogProperties), dim amount 설정(=SideEffect), 투명 배경 + 흰색 라운드 Surface(12.dp)에 content 슬롯, 내부 Preview 포함.
Small modal
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt
공개 PotiSmallModal(...) 추가: 제목·본문·취소·확인 2버튼(동일 가중치) 레이아웃, 버튼 콜백 및 해제 플래그 노출, 중앙 정렬·수평 패딩 적용, Preview 포함.
Large modal
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt
공개 PotiLargeModal(...) 추가: 제목·본문·주액션 버튼(필수)·선택적 서브버튼·선택적 이미지(@DrawableRes)·ColumnScope 콘텐츠 슬롯, 수직 버튼 스택, 화면 폭 기반 수평 패딩(screenWidthDp 사용), 해제 플래그 및 Preview 2종 포함.

Sequence Diagram(s)

(생성 조건 미충족 — 생략)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • cmj7271
  • jyvnee
🚥 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
Title check ✅ Passed PR 제목이 모달 컴포넌트 구현이라는 주요 변경사항을 명확하게 설명하고 있습니다.
Description check ✅ Passed PR 설명이 템플릿의 주요 섹션(Related issue, Work Description, Screenshot, Uncompleted Tasks, To Reviewers)을 모두 포함하고 있습니다.
Linked Issues check ✅ Passed PotiModal과 PotiSmallModal 구현으로 #19의 모든 코딩 요구사항을 충족했습니다. PotiLargeModal도 추가로 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 모달 컴포넌트 구현이라는 #19의 범위 내에 있으며, 범위 밖의 변경은 없습니다.

✏️ 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.

@doyeon0307 doyeon0307 self-assigned this Jan 12, 2026
@doyeon0307 doyeon0307 added 🌟 Feat 새로운 기능 구현 🔎 UI UI 작업 🐣 도연 labels Jan 12, 2026
Copy link
Copy Markdown

@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: 0

🧹 Nitpick comments (2)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt (2)

39-101: Consider whether button clicks should auto-dismiss the modal.

The current API separates onDismissRequest from button click handlers (onDismissBtnClick, onConfirmBtnClick). This means callers must remember to call the dismiss logic within their button handlers if they want the modal to close on button press.

While this provides flexibility, it's a common pattern for modal buttons to dismiss the modal automatically. Consider whether the button handlers should internally call onDismissRequest() after invoking the user's callback, or document this expectation clearly in the KDoc.

♻️ Optional: Auto-dismiss after button clicks
 PotiModalButton(
     text = dismissBtnText,
-    onClick = onDismissBtnClick,
+    onClick = {
+        onDismissBtnClick()
+        onDismissRequest()
+    },
     modifier = Modifier
         .weight(1f),
     type = ModalButtonType.SUB_1,
 )

 PotiModalButton(
     text = confirmBtnText,
-    onClick = onConfirmBtnClick,
+    onClick = {
+        onConfirmBtnClick()
+        onDismissRequest()
+    },
     modifier = Modifier
         .weight(1f),
     type = ModalButtonType.MAIN,
 )

Alternatively, keep the current design but add a note in the KDoc that callers are responsible for dismissing the modal in their click handlers.


103-131: Preview note: Modal buttons won't dismiss in this preview.

In the preview, onDismissBtnClick and onConfirmBtnClick are empty lambdas, so clicking the buttons won't close the modal. For a more realistic preview, consider having them set showModal = false:

onDismissBtnClick = { showModal = false },
onConfirmBtnClick = { showModal = false },

This would better demonstrate the expected usage pattern to developers viewing the preview.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f3b38e and 366bccc.

📒 Files selected for processing (2)
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt (4)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (1)
  • PotiModal (44-88)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiModalButton.kt (1)
  • PotiModalButton (25-59)
app/src/main/java/com/poti/android/core/designsystem/theme/Theme.kt (1)
  • PotiTheme (48-71)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiFloatingButton.kt (1)
  • PotiFloatingButton (35-83)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (3)
app/src/main/java/com/poti/android/core/designsystem/theme/Theme.kt (1)
  • PotiTheme (48-71)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiModalButton.kt (1)
  • PotiModalButton (25-59)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiFloatingButton.kt (1)
  • PotiFloatingButton (35-83)
⏰ 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). (1)
  • GitHub Check: PR CI Check (Lint & Build)
🔇 Additional comments (3)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (2)

44-88: Well-structured modal implementation with custom dismiss handling.

The approach of setting dismissOnClickOutside = false in PopupProperties and handling outside clicks manually via noRippleClickable is a good design choice that allows for the custom scrim behavior. The pointerInput with empty detectTapGestures correctly prevents tap propagation to the backdrop.

One minor observation: the remember key only includes dismissOnBackPress, which is correct since dismissOnClickOutside is handled separately in the noRippleClickable modifier and doesn't affect the PopupProperties object.


90-131: Preview demonstrates the modal behavior effectively.

The preview provides a good interactive example showing how to toggle the modal visibility using state. This is helpful for developers using the component.

app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt (1)

58-99: Clean layout implementation following the design system.

The content layout is well-structured with proper spacing and theming. The use of weight(1f) for both buttons ensures equal width distribution, and the typography/color choices align with the theme.

Copy link
Copy Markdown
Contributor

@jyvnee jyvnee left a comment

Choose a reason for hiding this comment

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

수고햇덩!!!!!

)
}

Popup(
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.

P1: Popup으로 하면 시스템 상태바와 내비게이션바 영역은 제외되고 dim 처리 되는 걸 아시나요.. 되도록이면 Dialog 쓰고 배경 커스텀하고 싶다면 시스템 기본 DimAmount를 40% 정도로 조절하면 될 것 같습니다

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.

아 맞네.......

detectTapGestures { }
},
) {
content()
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: 여기도 아예 빈 칸으로 두는 것 보다 Text랑 Button 정도는 넣어두는 건 어떤가요????

Copy link
Copy Markdown

@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

🤖 Fix all issues with AI agents
In
@app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt:
- Around line 44-49: The PotiModal function accepts a modifier parameter but
never applies it, so caller modifiers have no effect; update the Surface
invocation inside PotiModal to include the passed-in modifier (e.g., combine the
incoming modifier with any existing modifiers using modifier.then(...) or
Modifier.then(modifier)) so that the Surface uses the provided modifier,
ensuring calls like Modifier.padding(...) in previews take effect; locate the
PotiModal function and the Surface composable within it and add modifier =
modifier (or modifier = modifier.then(existingModifier)) to the Surface
parameters.
- Around line 62-72: Wrap the imperative window mutations in a Compose
SideEffect: obtain the parent window via a safe cast ((LocalView.current.parent
as?) DialogWindowProvider)?.window, return early if null, then call
dialogWindow.setDimAmount(...) and dialogWindow.setBackgroundDrawable(...)
inside SideEffect to avoid running during recomposition; remove the redundant
dialogWindow.decorView.setBackgroundColor(...) call since
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) overrides it. Ensure you
reference the existing dialogWindow variable and use the safe cast as? to
prevent crashes.
🧹 Nitpick comments (4)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt (3)

33-33: Minor: Empty @modifier description in KDoc.

The @param modifier lacks a description. Consider adding a brief explanation or removing the line if it's self-explanatory.


52-53: Consider using a paired data class or requiring both sub-button parameters together.

subBtnText and onSubBtnClick are independently nullable, but line 107 requires both to be non-null for the sub-button to render. If a caller provides only one, the button silently won't appear. Consider either:

  1. Documenting this requirement explicitly in the KDoc
  2. Using a single optional data class parameter (e.g., subButton: SubButtonConfig?)
  3. Adding a runtime check during development
Option: Single parameter approach
data class SubButtonConfig(val text: String, val onClick: () -> Unit)

// In function signature:
subButton: SubButtonConfig? = null,

// In usage:
subButton?.let {
    PotiModalButton(
        text = it.text,
        onClick = it.onClick,
        modifier = Modifier.fillMaxWidth(),
        type = ModalButtonType.SUB_2,
    )
}

91-91: Consider adding a meaningful contentDescription for accessibility.

Using contentDescription = null marks the image as decorative. If the image conveys meaningful information relevant to the modal's context, consider accepting a content description parameter for accessibility.

app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (1)

35-38: Optional: Complete the @param descriptions.

The modifier and content parameters are missing descriptions. Consider adding brief descriptions for documentation consistency.

-* @param modifier
+* @param modifier 모달에 적용할 Modifier입니다.
 * @param dismissOnBackPress 시스템 뒤로가기 시 모달을 닫는 여부입니다. 기본값 true입니다.
 * @param dismissOnClickOutside 모달 바깥 영역 터치 시 모달을 닫는 여부입니다. 기본값 true입니다.
-* @param content
+* @param content 모달 내부에 표시할 컴포저블 콘텐츠입니다.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 366bccc and f01c2b8.

📒 Files selected for processing (2)
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt (3)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (1)
  • PotiModal (43-81)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiModalButton.kt (1)
  • PotiModalButton (25-59)
app/src/main/java/com/poti/android/core/designsystem/theme/Theme.kt (1)
  • PotiTheme (48-71)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (3)
app/src/main/java/com/poti/android/core/designsystem/theme/Theme.kt (1)
  • PotiTheme (48-71)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiModalButton.kt (1)
  • PotiModalButton (25-59)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiFloatingButton.kt (1)
  • PotiFloatingButton (35-83)
⏰ 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). (1)
  • GitHub Check: PR CI Check (Lint & Build)
🔇 Additional comments (3)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt (2)

88-97: Verify intended image sizing behavior.

The modifier chain .size(180.dp).padding(vertical = 24.dp) applies padding inside the 180dp box. This means:

  • The total space is 180x180dp
  • The image is inset by 24dp top and bottom, leaving only ~132dp height for actual image content
  • With ContentScale.Crop, the image will be cropped to fit this reduced space

If the intent is a 180dp image with 24dp vertical spacing around it, reverse the order or use a wrapper:

💡 If spacing around the image is intended
 image?.let {
     Image(
         painter = painterResource(id = it),
         contentDescription = null,
         modifier = Modifier
-            .size(180.dp)
-            .padding(vertical = 24.dp),
+            .padding(vertical = 24.dp)
+            .size(180.dp),
         contentScale = ContentScale.Crop,
     )
 }

120-141: LGTM!

The preview functions adequately demonstrate both modal variants (lg-1 with content/sub-button and lg-2 with image). They're properly wrapped in PotiTheme.

Minor suggestion: Consider adding a background color to the placeholder Box in PotiLarge1Preview to make the content slot visible in the preview.

app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (1)

83-124: Preview implementation looks good.

The preview properly demonstrates the modal's usage pattern with state management for visibility and wraps content in PotiTheme. Once the modifier parameter bug is fixed, the padding modifier passed at line 91 will work as expected.

Copy link
Copy Markdown

@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: 0

🧹 Nitpick comments (1)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (1)

63-73: Window 설정 코드를 SideEffect 블록으로 감싸주세요.

현재 dialogWindow 설정 코드가 composition 중에 직접 실행되고 있습니다. 이는 매 recomposition마다 실행되며, Compose의 side-effect 처리 원칙에 맞지 않습니다. 또한 Color.parseColor("#000000") 대신 Color.BLACK을 사용하면 더 간결합니다.

♻️ SideEffect 적용 제안
     Dialog(
         onDismissRequest = onDismissRequest,
         properties = dialogProperties,
     ) {
         val dialogWindow = (LocalView.current.parent as DialogWindowProvider).window

-        dialogWindow.setDimAmount(0.4f)
-
-        dialogWindow.setBackgroundDrawable(
-            ColorDrawable(Color.TRANSPARENT),
-        )
+        SideEffect {
+            dialogWindow.setDimAmount(0.4f)
+            dialogWindow.setBackgroundDrawable(
+                ColorDrawable(Color.TRANSPARENT),
+            )
+        }

SideEffect import 추가:

import androidx.compose.runtime.SideEffect
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f01c2b8 and a3ad906.

📒 Files selected for processing (1)
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (3)
app/src/main/java/com/poti/android/core/designsystem/theme/Theme.kt (1)
  • PotiTheme (48-71)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiModalButton.kt (1)
  • PotiModalButton (25-59)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiFloatingButton.kt (1)
  • PotiFloatingButton (35-83)
⏰ 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). (1)
  • GitHub Check: PR CI Check (Lint & Build)
🔇 Additional comments (3)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (3)

43-57: LGTM!

함수 시그니처와 DialogPropertiesremember 사용이 적절합니다. dismissOnBackPress, dismissOnClickOutside를 key로 사용하여 불필요한 재생성을 방지한 점이 좋습니다.


67-73: 중복되거나 상충하는 배경 설정을 확인해 주세요.

decorView.setBackgroundColor(Color.BLACK)setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))가 함께 설정되어 있습니다. setDimAmount(0.4f)가 이미 딤 효과를 처리하므로, 67-69번 줄의 검은색 배경 설정이 실제로 필요한지 확인이 필요합니다. 의도한 시각적 효과가 맞는지 검토해 주세요.


85-126: LGTM!

Preview 구현이 적절합니다. 상태 관리와 모달 트리거 버튼을 통한 사용 예시가 잘 구성되어 있습니다.

@doyeon0307 doyeon0307 moved this from To-do to In review in POTI-ANDROID Jan 13, 2026
Copy link
Copy Markdown
Contributor

@jyvnee jyvnee left a comment

Choose a reason for hiding this comment

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

코리 확인해조~~

Comment on lines +62 to +72
val dialogWindow = (LocalView.current.parent as DialogWindowProvider).window

dialogWindow.setDimAmount(0.4f)

dialogWindow.decorView.setBackgroundColor(
Color.parseColor("#000000"),
)

dialogWindow.setBackgroundDrawable(
ColorDrawable(Color.TRANSPARENT),
)
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.

P1: 이 코드는 리컴포지션 때마다 계속 실행되기 때문에 컴포저블 안에 그냥 쓰면 안 돼요. SideEffect 블록으로 감싸서 사용해주세요!

Comment on lines +66 to +72
dialogWindow.decorView.setBackgroundColor(
Color.parseColor("#000000"),
)

dialogWindow.setBackgroundDrawable(
ColorDrawable(Color.TRANSPARENT),
)
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.

P1: 이 코드는 왜 사용한 건가요??

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.

안써주면 Surface 모서리 깎은 부분/패딩이 검정색이 되더라구욤,,
image

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.

image

어라랏 저는 안 뜨는데??

SideEffect {
     dialogWindow?.setDimAmount(0.4f)
}

이렇게 해도 뒤에 배경 안 나와요

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.

        dialogWindow.decorView.setBackgroundColor(
            Color.parseColor("#000000"),
        )

검은 배경 이 코드 때문이엇음! 고로 필요없는 코드가 맞앗슴다

modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(top = 36.dp, bottom = 12.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.

P1: 버튼 하나일 때는 패딩 16.dp 입니당

btnText = "확인",
onBtnClick = {},
modifier = Modifier
.padding(horizontal = 32.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.

P1: 이거 large 모달일 경우 옆에 패딩 36.dp 고 small 모달일 경우는 32.dp 인데 지금 모달 나눠서 만들었으니까 컴포넌트에 고정시키는게 나을 것 같아요

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
@app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt:
- Around line 109-116: The condition using the intermediate boolean hasSubBtn
prevents Kotlin smart casts, causing nullable subBtnText and onSubBtnClick to
still be treated as nullable when calling PotiModalButton; update the if check
to directly test nullability (e.g., if (subBtnText != null && onSubBtnClick !=
null)) or explicitly assert non-null with !! when invoking PotiModalButton so
that subBtnText and onSubBtnClick are passed as non-null String and () -> Unit
(follow the pattern used in PotiBottomButton).
🧹 Nitpick comments (3)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (1)

64-71: Dialog window 설정이 적용되지 않는 경우에 대한 고려

LocalView.current.parentDialogWindowProvider가 아닌 경우 dialogWindow가 null이 되어 dim 및 배경 설정이 적용되지 않습니다. 현재 Compose Dialog 내에서 사용되므로 정상 동작하지만, 디버깅을 위해 로그를 추가하거나 주석으로 이 동작을 명시하는 것을 고려해 보세요.

app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt (2)

41-41: @author 태그가 비어 있습니다.

문서화 일관성을 위해 작성자 이름을 추가하거나, 불필요한 경우 태그를 제거하세요.


90-99: 이미지 접근성을 위한 contentDescription 파라미터 추가 고려

현재 contentDescription = null로 설정되어 있어 스크린 리더가 이미지를 설명하지 않습니다. 이미지가 순수하게 장식적인 경우에는 괜찮지만, 의미를 전달하는 경우 접근성에 문제가 될 수 있습니다.

imageContentDescription: String? = null 파라미터를 추가하여 호출자가 접근성 설명을 제공할 수 있도록 하는 것을 고려해 보세요.

♻️ 제안된 수정
 @Composable
 fun PotiLargeModal(
     onDismissRequest: () -> Unit,
     title: String,
     text: String,
     btnText: String,
     onBtnClick: () -> Unit,
     modifier: Modifier = Modifier,
     subBtnText: String? = null,
     onSubBtnClick: (() -> Unit)? = null,
     @DrawableRes image: Int? = null,
+    imageContentDescription: String? = null,
     dismissOnBackPress: Boolean = true,
     dismissOnClickOutside: Boolean = true,
     content: (@Composable ColumnScope.() -> Unit)? = null,
 ) {
     // ...
     image?.let {
         Image(
             painter = painterResource(id = it),
-            contentDescription = null,
+            contentDescription = imageContentDescription,
             // ...
         )
     }
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3ad906 and 51a3205.

📒 Files selected for processing (3)
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt (3)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (1)
  • PotiModal (44-81)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiModalButton.kt (1)
  • PotiModalButton (25-59)
app/src/main/java/com/poti/android/core/designsystem/theme/Theme.kt (1)
  • PotiTheme (48-71)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (3)
app/src/main/java/com/poti/android/core/designsystem/theme/Theme.kt (1)
  • PotiTheme (48-71)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiModalButton.kt (1)
  • PotiModalButton (25-59)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiFloatingButton.kt (1)
  • PotiFloatingButton (35-83)
⏰ 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). (1)
  • GitHub Check: PR CI Check (Lint & Build)
🔇 Additional comments (4)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (2)

44-58: LGTM!

DialogPropertiesremember로 캐싱하면서 dismissOnBackPress, dismissOnClickOutside를 key로 사용한 것이 적절합니다. 값이 변경될 때만 새로운 인스턴스가 생성됩니다.


83-124: LGTM!

Preview가 모달의 상태 토글과 실제 사용 패턴을 잘 보여주고 있습니다.

app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt (2)

44-58: LGTM!

함수 시그니처가 잘 설계되었습니다. 필수 파라미터와 선택적 파라미터가 명확히 구분되어 있고, content 람다가 ColumnScope를 receiver로 받아 내부 정렬이 가능합니다.


122-156: LGTM!

두 가지 주요 변형(lg-1: content + sub button, lg-2: image)을 모두 보여주는 Preview가 잘 구현되어 있습니다.

Copy link
Copy Markdown
Contributor

@jyvnee jyvnee left a comment

Choose a reason for hiding this comment

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

헤헤.. 해조~~


PotiModal(
onDismissRequest = onDismissRequest,
modifier = modifier.padding(horizontal = 36.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: 이거 기기대응 확장함수 쓰면 좋겠다~

Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.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: 이것두!!

contentDescription = null,
modifier = Modifier
.padding(vertical = 24.dp)
.size(180.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: 이것도!!!

) {
PotiModal(
onDismissRequest = onDismissRequest,
modifier = modifier.padding(horizontal = 32.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: 여기도!!

Column(
modifier = Modifier
.fillMaxWidth()
.padding(all = 16.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: 요기두

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
@app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt:
- Around line 112-119: The code passes nullable subBtnText and onSubBtnClick
into PotiModalButton even though it requires non-null parameters because the
smart cast on hasSubBtn doesn't apply; update the guard to directly check the
nullable variables (e.g., if (subBtnText != null && onSubBtnClick != null) {
PotiModalButton(text = subBtnText, onClick = onSubBtnClick, modifier =
Modifier.fillMaxWidth(), type = ModalButtonType.SUB_2) }) so the compiler can
smart-cast them to non-null, referencing the variables subBtnText,
onSubBtnClick, the boolean hasSubBtn, and the PotiModalButton invocation in
PotiLargeModal.kt.
🧹 Nitpick comments (1)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt (1)

61-61: hasSubBtn 값을 remember로 감싸는 것을 고려해 주세요.

현재 hasSubBtn은 매 리컴포지션마다 재계산됩니다. 단순한 null 체크이므로 성능 영향은 미미하지만, subBtnTextonSubBtnClick이 변경될 때만 재계산되도록 remember를 사용하면 Compose 모범 사례에 더 부합합니다.

♻️ 제안하는 수정 사항
-    val hasSubBtn = subBtnText != null && onSubBtnClick != null
+    val hasSubBtn = remember(subBtnText, onSubBtnClick) {
+        subBtnText != null && onSubBtnClick != null
+    }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51a3205 and 6ab57ea.

📒 Files selected for processing (2)
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt

⚙️ CodeRabbit configuration file

**/*.kt: 코드를 리뷰할 때 다음 Kotlin 및 Jetpack Compose 모범 사례를 중점적으로 확인:

  1. Jetpack Compose 성능 및 UI 최적화:

    • Recomposition 방지: 불필요한 리컴포지션을 유발하는 코드를 지적해줘. 특히 LazyColumn 등 리스트에서 key 파라미터가 누락되었는지 확인해줘.
    • 스마트한 계산: 계산 비용이 높은 로직은 rememberderivedStateOf로 감싸져 있는지 확인해줘.
    • 안정성(Stability): List나 일반 클래스를 State로 사용할 때, 불필요한 리컴포지션을 막기 위해 @Immutable이나 @Stable 어노테이션 사용을 제안해줘.
    • Modifier 규칙: Modifier는 항상 컴포저블 함수의 첫 번째 선택적 파라미터로 받아야 하며, 재사용성을 위해 외부에서 주입받아야 해.
    • 리소스 사용: 하드코딩된 값(색상, 크기, 문자열) 대신 Theme, Dimens, strings.xml 리소스를 사용하는지 체크해줘.
    • 접근성 예외: Icon이나 Image 컴포저블의 contentDescriptionnull인 경우는 장식용(Decorative) 이미지로 간주하고 지적하지 마.
  2. MVI (Model-View-Intent) 아키텍처 및 상태 관리:

    • 단방향 데이터 흐름(UDF): UI는 상태를 직접 변경할 수 없고, 반드시 Intent(또는 Event)를 통해 ViewModel로 요청을 보내야 해.
    • 불변 상태(Immutable State): UiState는 반드시 data class + val 프로퍼티로 구성되어야 하며, 상태 업데이트 시 .copy()를 사용해야 해.
    • Side Effect 분리: 네비게이션, Toast 같은 일회성 이벤트는 UiState가 아닌 Channel이나 SharedFlow로 처리하여 화면 회전 시 재발생하지 않도록 해야 해.
    • 안전한 수집: UI에서 Flow 수집 시 collectAsStateWithLifecycle()을 사용하여 생명주기를 준수하는지 확인해줘.
    • 상태 노출: ViewModel 내부에서 StateFlowSharedFlow를 올바르게 노출하고 있는지 확인해줘.
    • 호이스팅(Hoisting): 상태나 UI 이벤트 처리가 적절하게 호이스팅되었는지 확인해줘. UI 로직과 비즈니스 로직을 분리하고, UI가 로직을 직접 수행하지 않도록 확인해줘.
  3. Clean Architecture (Dependency Rule) 준수:

    • Domain 순수성: Domain 레이어(domain 패키지)는 절대로 안드로이드 의존성(android.*, R 클래스 등)을 가져서는 안 돼. 순수 Kotlin 코드인지 엄격히 확인해줘.
    • 데이터 노출 금지: Data 레이어의 DTO(@Entity, @SerializedName)가 UI나 Domain 레이어로 바로 노출되면 안 돼. 반드시 Mapper를 통해 변환했는지 확인해줘.
    • 계층 간 의존성: UI -> Domain <- Data 방향이 올바른지, UI가 Data 레이어를 직접 참조하고 있진 않은지 감시해줘.
  4. Kotlin & Coroutines 안전성:

    • Scope 사용: GlobalScope 사용을 금지하고, viewModelScopelifecycleScope 사용을 권장해줘.
    • Dispatcher 주입: Dispatchers.IO 등을 하드코딩하지 말고, 테스트 용이성을 위해 생성자를 통해 주입(Injection)받도록 제안해줘.
    • 예외 처리: Coroutine 실행 시 runCatching이나 CoroutineExceptionHandler 등을 통해 예외 처리가 누락되지 않았는지 확인해줘.
    • Null-Safety: 불필요한 !! 연산자 사용을 지적...

Files:

  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt
🧬 Code graph analysis (1)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt (4)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (1)
  • PotiModal (44-81)
app/src/main/java/com/poti/android/core/common/util/DimensionUtil.kt (1)
  • screenWidthDp (20-26)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiModalButton.kt (1)
  • PotiModalButton (25-59)
app/src/main/java/com/poti/android/core/designsystem/theme/Theme.kt (1)
  • PotiTheme (48-71)
🔇 Additional comments (2)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt (2)

27-45: 문서화가 잘 되어 있습니다!

컴포넌트의 각 파라미터에 대한 KDoc 설명이 명확하게 작성되어 있어 사용자가 쉽게 이해할 수 있습니다. @sample 태그로 프리뷰 함수를 연결한 점도 좋습니다.

참고로, @author 태그(Line 43)가 비어 있는데, 작성자 정보를 추가하시거나 필요 없으시면 제거하시는 것을 권장드립니다.


125-159: 프리뷰 함수가 잘 구성되어 있습니다!

PotiLarge1Preview는 content 슬롯 사용 케이스를, PotiLarge2Preview는 이미지 사용 케이스를 잘 보여주고 있습니다. PotiTheme으로 감싸서 테마가 적용된 상태로 프리뷰가 표시되도록 한 점도 Modern Android Development 권장 사항에 부합합니다.

Comment on lines +112 to +119
if (hasSubBtn) {
PotiModalButton(
text = subBtnText,
onClick = onSubBtnClick,
modifier = Modifier.fillMaxWidth(),
type = ModalButtonType.SUB_2,
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "PotiLargeModal.kt" | head -5

Repository: team-poti/POTI-ANDROID

Length of output: 154


🏁 Script executed:

find . -type f -name "*.kt" -path "*designsystem*" | grep -i modal

Repository: team-poti/POTI-ANDROID

Length of output: 418


🏁 Script executed:

# Search for PotiModalButton definition
rg -n "fun PotiModalButton" --type=kt -A 5

Repository: team-poti/POTI-ANDROID

Length of output: 92


🏁 Script executed:

cat -n ./app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt | sed -n '100,130p'

Repository: team-poti/POTI-ANDROID

Length of output: 1104


🏁 Script executed:

cat -n ./app/src/main/java/com/poti/android/core/designsystem/component/button/PotiModalButton.kt | head -50

Repository: team-poti/POTI-ANDROID

Length of output: 2419


🏁 Script executed:

# Get full view of PotiModalButton signature
cat ./app/src/main/java/com/poti/android/core/designsystem/component/button/PotiModalButton.kt | head -100

Repository: team-poti/POTI-ANDROID

Length of output: 3303


🏁 Script executed:

# Read full PotiLargeModal.kt to find function signature and parameter types
cat -n ./app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt | head -80

Repository: team-poti/POTI-ANDROID

Length of output: 3734


🏁 Script executed:

# Check if there are any compilation issues or error reports in the codebase
find . -name "*.log" -o -name "build-log*" 2>/dev/null | head -5

Repository: team-poti/POTI-ANDROID

Length of output: 48


🏁 Script executed:

# Search for similar patterns with hasSubBtn or sub button handling
rg "hasSubBtn" --type-list | head -20

Repository: team-poti/POTI-ANDROID

Length of output: 455


🏁 Script executed:

# Look for other modal files that might show correct pattern
cat -n ./app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt | head -80

Repository: team-poti/POTI-ANDROID

Length of output: 3679


🏁 Script executed:

# Search for actual usage patterns of PotiLargeModal to see if code compiles
rg "PotiLargeModal" -A 8 -B 2 --no-heading

Repository: team-poti/POTI-ANDROID

Length of output: 3799


🏁 Script executed:

# Check if there are gradle build files that might show compilation settings
find . -name "build.gradle.kts" -o -name "build.gradle" | head -5

Repository: team-poti/POTI-ANDROID

Length of output: 107


🏁 Script executed:

# Look at how parameters are passed when calling PotiLargeModal
rg "PotiLargeModal\(" -A 10 | head -50

Repository: team-poti/POTI-ANDROID

Length of output: 3728


nullable 타입을 non-null 파라미터에 전달하고 있습니다.

hasSubBtn 변수로 null 체크를 했지만, Kotlin의 스마트 캐스트는 직접 null 검사한 변수에만 적용됩니다. 별도 boolean 변수 검사에서는 스마트 캐스트가 동작하지 않으므로, subBtnTextString?로, onSubBtnClick(() -> Unit)?로 유지됩니다. 그런데 PotiModalButton은 각각 non-null String() -> Unit을 요구하므로 타입 불일치가 발생합니다.

아래 중 하나로 수정해주세요:

  1. 직접 null 검사로 스마트 캐스트 활성화:
if (subBtnText != null && onSubBtnClick != null) {
    PotiModalButton(
        text = subBtnText,
        onClick = onSubBtnClick,
        modifier = Modifier.fillMaxWidth(),
        type = ModalButtonType.SUB_2,
    )
}
  1. Non-null assertion (권장하지 않음):
if (hasSubBtn) {
    PotiModalButton(
        text = subBtnText!!,
        onClick = onSubBtnClick!!,
        modifier = Modifier.fillMaxWidth(),
        type = ModalButtonType.SUB_2,
    )
}
🤖 Prompt for AI Agents
In
@app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt
around lines 112 - 119, The code passes nullable subBtnText and onSubBtnClick
into PotiModalButton even though it requires non-null parameters because the
smart cast on hasSubBtn doesn't apply; update the guard to directly check the
nullable variables (e.g., if (subBtnText != null && onSubBtnClick != null) {
PotiModalButton(text = subBtnText, onClick = onSubBtnClick, modifier =
Modifier.fillMaxWidth(), type = ModalButtonType.SUB_2) }) so the compiler can
smart-cast them to non-null, referencing the variables subBtnText,
onSubBtnClick, the boolean hasSubBtn, and the PotiModalButton invocation in
PotiLargeModal.kt.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Jan 13, 2026

Ktlint check passed.

Run: https://github.com/team-poti/POTI-ANDROID/actions/runs/20963058836

Copy link
Copy Markdown

@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: 0

🧹 Nitpick comments (2)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (2)

3-4: 사용되지 않는 import를 제거해 주세요.

android.graphics.Colorandroid.graphics.drawable.ColorDrawable이 import되어 있지만 실제 코드에서 사용되지 않고 있습니다. 코드 정리를 위해 제거하시는 것이 좋겠습니다.

🧹 제안된 수정
 package com.poti.android.core.designsystem.component.modal
 
-import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
 import androidx.compose.foundation.layout.Arrangement

66-72: 하드코딩된 값들을 디자인 시스템 상수로 추출하는 것을 고려해 주세요.

현재 dimAmount(0.4f)와 RoundedCornerShape(12.dp)가 하드코딩되어 있습니다. 코딩 가이드라인에 따르면 이러한 값들은 Theme이나 Dimens 리소스로 관리하는 것이 좋습니다. 디자인 시스템의 일관성과 유지보수성을 위해 상수로 추출하시는 것을 권장드립니다.

♻️ 제안된 수정 예시
// PotiModal.kt 상단 또는 별도 상수 파일에 정의
private const val MODAL_DIM_AMOUNT = 0.4f
private val ModalCornerRadius = 12.dp

// 사용 시
SideEffect {
    dialogWindow?.setDimAmount(MODAL_DIM_AMOUNT)
}

Surface(
    modifier = modifier,
    shape = RoundedCornerShape(ModalCornerRadius),
    color = PotiTheme.colors.white,
)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 17648f3 and a6df8fc.

📒 Files selected for processing (1)
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt

⚙️ CodeRabbit configuration file

**/*.kt: 코드를 리뷰할 때 다음 Kotlin 및 Jetpack Compose 모범 사례를 중점적으로 확인:

  1. Jetpack Compose 성능 및 UI 최적화:

    • Recomposition 방지: 불필요한 리컴포지션을 유발하는 코드를 지적해줘. 특히 LazyColumn 등 리스트에서 key 파라미터가 누락되었는지 확인해줘.
    • 스마트한 계산: 계산 비용이 높은 로직은 rememberderivedStateOf로 감싸져 있는지 확인해줘.
    • 안정성(Stability): List나 일반 클래스를 State로 사용할 때, 불필요한 리컴포지션을 막기 위해 @Immutable이나 @Stable 어노테이션 사용을 제안해줘.
    • Modifier 규칙: Modifier는 항상 컴포저블 함수의 첫 번째 선택적 파라미터로 받아야 하며, 재사용성을 위해 외부에서 주입받아야 해.
    • 리소스 사용: 하드코딩된 값(색상, 크기, 문자열) 대신 Theme, Dimens, strings.xml 리소스를 사용하는지 체크해줘.
    • 접근성 예외: Icon이나 Image 컴포저블의 contentDescriptionnull인 경우는 장식용(Decorative) 이미지로 간주하고 지적하지 마.
  2. MVI (Model-View-Intent) 아키텍처 및 상태 관리:

    • 단방향 데이터 흐름(UDF): UI는 상태를 직접 변경할 수 없고, 반드시 Intent(또는 Event)를 통해 ViewModel로 요청을 보내야 해.
    • 불변 상태(Immutable State): UiState는 반드시 data class + val 프로퍼티로 구성되어야 하며, 상태 업데이트 시 .copy()를 사용해야 해.
    • Side Effect 분리: 네비게이션, Toast 같은 일회성 이벤트는 UiState가 아닌 Channel이나 SharedFlow로 처리하여 화면 회전 시 재발생하지 않도록 해야 해.
    • 안전한 수집: UI에서 Flow 수집 시 collectAsStateWithLifecycle()을 사용하여 생명주기를 준수하는지 확인해줘.
    • 상태 노출: ViewModel 내부에서 StateFlowSharedFlow를 올바르게 노출하고 있는지 확인해줘.
    • 호이스팅(Hoisting): 상태나 UI 이벤트 처리가 적절하게 호이스팅되었는지 확인해줘. UI 로직과 비즈니스 로직을 분리하고, UI가 로직을 직접 수행하지 않도록 확인해줘.
  3. Clean Architecture (Dependency Rule) 준수:

    • Domain 순수성: Domain 레이어(domain 패키지)는 절대로 안드로이드 의존성(android.*, R 클래스 등)을 가져서는 안 돼. 순수 Kotlin 코드인지 엄격히 확인해줘.
    • 데이터 노출 금지: Data 레이어의 DTO(@Entity, @SerializedName)가 UI나 Domain 레이어로 바로 노출되면 안 돼. 반드시 Mapper를 통해 변환했는지 확인해줘.
    • 계층 간 의존성: UI -> Domain <- Data 방향이 올바른지, UI가 Data 레이어를 직접 참조하고 있진 않은지 감시해줘.
  4. Kotlin & Coroutines 안전성:

    • Scope 사용: GlobalScope 사용을 금지하고, viewModelScopelifecycleScope 사용을 권장해줘.
    • Dispatcher 주입: Dispatchers.IO 등을 하드코딩하지 말고, 테스트 용이성을 위해 생성자를 통해 주입(Injection)받도록 제안해줘.
    • 예외 처리: Coroutine 실행 시 runCatching이나 CoroutineExceptionHandler 등을 통해 예외 처리가 누락되지 않았는지 확인해줘.
    • Null-Safety: 불필요한 !! 연산자 사용을 지적...

Files:

  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt
🔇 Additional comments (2)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (2)

44-78: 잘 구현된 모달 컴포넌트입니다!

  • Modifier가 첫 번째 선택적 파라미터로 올바르게 배치되어 Compose 컨벤션을 잘 따르고 있습니다.
  • DialogPropertiesremember로 캐싱하고 적절한 key(dismissOnBackPress, dismissOnClickOutside)를 사용하여 불필요한 객체 재생성을 방지하고 있습니다.
  • internal 가시성을 사용하여 래퍼 모달들의 베이스 컴포넌트로 적절하게 설계되었습니다.
  • content 슬롯 패턴을 통해 유연한 확장성을 제공합니다.

80-121: Preview가 잘 구성되어 있습니다.

PotiTheme으로 감싸고 상태 토글을 통해 모달 동작을 시연하는 패턴이 적절합니다. 다른 디자인 시스템 컴포넌트(PotiFloatingButton, PotiModalButton)와의 연동도 잘 보여주고 있습니다.

Copy link
Copy Markdown
Contributor

@sonyerim sonyerim 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
Copy Markdown

@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: 0

🧹 Nitpick comments (2)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (2)

62-66: SideEffect 내 null 체크 개선을 고려해 보세요.

현재 dialogWindow?.setDimAmount(0.4f)로 null-safe 호출을 하고 있지만, SideEffect는 매 리컴포지션마다 실행됩니다. dialogWindow가 유효할 때만 dim 설정을 하도록 명시적으로 처리하면 의도가 더 명확해집니다.

또한, dim 값 0.4f를 상수로 추출하시면 향후 유지보수에 도움이 될 것 같습니다.

♻️ 선택적 개선 제안
+private const val MODAL_DIM_AMOUNT = 0.4f
+
 @Composable
 internal fun PotiModal(
     // ...
 ) {
     // ...
     Dialog(
         onDismissRequest = onDismissRequest,
         properties = dialogProperties,
     ) {
         val dialogWindow = (LocalView.current.parent as? DialogWindowProvider)?.window

         SideEffect {
-            dialogWindow?.setDimAmount(0.4f)
+            dialogWindow?.setDimAmount(MODAL_DIM_AMOUNT)
         }
         // ...
     }
 }

68-75: RoundedCornerShape 객체 재생성 최적화를 고려해 보세요.

RoundedCornerShape(12.dp)는 매 리컴포지션마다 새로운 객체를 생성합니다. 디자인 시스템의 기반 컴포넌트로서 자주 사용될 것이므로, 상수로 추출하거나 remember로 캐싱하시면 미세하지만 불필요한 객체 생성을 줄일 수 있습니다.

♻️ 선택적 개선 제안
+private val ModalShape = RoundedCornerShape(12.dp)
+
 @Composable
 internal fun PotiModal(
     // ...
 ) {
     // ...
         Surface(
             modifier = modifier,
-            shape = RoundedCornerShape(12.dp),
+            shape = ModalShape,
             color = PotiTheme.colors.white,
         ) {
             content()
         }
     // ...
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6df8fc and c70482c.

📒 Files selected for processing (1)
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt

⚙️ CodeRabbit configuration file

**/*.kt: 코드를 리뷰할 때 다음 Kotlin 및 Jetpack Compose 모범 사례를 중점적으로 확인:

  1. Jetpack Compose 성능 및 UI 최적화:

    • Recomposition 방지: 불필요한 리컴포지션을 유발하는 코드를 지적해줘. 특히 LazyColumn 등 리스트에서 key 파라미터가 누락되었는지 확인해줘.
    • 스마트한 계산: 계산 비용이 높은 로직은 rememberderivedStateOf로 감싸져 있는지 확인해줘.
    • 안정성(Stability): List나 일반 클래스를 State로 사용할 때, 불필요한 리컴포지션을 막기 위해 @Immutable이나 @Stable 어노테이션 사용을 제안해줘.
    • Modifier 규칙: Modifier는 항상 컴포저블 함수의 첫 번째 선택적 파라미터로 받아야 하며, 재사용성을 위해 외부에서 주입받아야 해.
    • 리소스 사용: 하드코딩된 값(색상, 크기, 문자열) 대신 Theme, Dimens, strings.xml 리소스를 사용하는지 체크해줘.
    • 접근성 예외: Icon이나 Image 컴포저블의 contentDescriptionnull인 경우는 장식용(Decorative) 이미지로 간주하고 지적하지 마.
  2. MVI (Model-View-Intent) 아키텍처 및 상태 관리:

    • 단방향 데이터 흐름(UDF): UI는 상태를 직접 변경할 수 없고, 반드시 Intent(또는 Event)를 통해 ViewModel로 요청을 보내야 해.
    • 불변 상태(Immutable State): UiState는 반드시 data class + val 프로퍼티로 구성되어야 하며, 상태 업데이트 시 .copy()를 사용해야 해.
    • Side Effect 분리: 네비게이션, Toast 같은 일회성 이벤트는 UiState가 아닌 Channel이나 SharedFlow로 처리하여 화면 회전 시 재발생하지 않도록 해야 해.
    • 안전한 수집: UI에서 Flow 수집 시 collectAsStateWithLifecycle()을 사용하여 생명주기를 준수하는지 확인해줘.
    • 상태 노출: ViewModel 내부에서 StateFlowSharedFlow를 올바르게 노출하고 있는지 확인해줘.
    • 호이스팅(Hoisting): 상태나 UI 이벤트 처리가 적절하게 호이스팅되었는지 확인해줘. UI 로직과 비즈니스 로직을 분리하고, UI가 로직을 직접 수행하지 않도록 확인해줘.
  3. Clean Architecture (Dependency Rule) 준수:

    • Domain 순수성: Domain 레이어(domain 패키지)는 절대로 안드로이드 의존성(android.*, R 클래스 등)을 가져서는 안 돼. 순수 Kotlin 코드인지 엄격히 확인해줘.
    • 데이터 노출 금지: Data 레이어의 DTO(@Entity, @SerializedName)가 UI나 Domain 레이어로 바로 노출되면 안 돼. 반드시 Mapper를 통해 변환했는지 확인해줘.
    • 계층 간 의존성: UI -> Domain <- Data 방향이 올바른지, UI가 Data 레이어를 직접 참조하고 있진 않은지 감시해줘.
  4. Kotlin & Coroutines 안전성:

    • Scope 사용: GlobalScope 사용을 금지하고, viewModelScopelifecycleScope 사용을 권장해줘.
    • Dispatcher 주입: Dispatchers.IO 등을 하드코딩하지 말고, 테스트 용이성을 위해 생성자를 통해 주입(Injection)받도록 제안해줘.
    • 예외 처리: Coroutine 실행 시 runCatching이나 CoroutineExceptionHandler 등을 통해 예외 처리가 누락되지 않았는지 확인해줘.
    • Null-Safety: 불필요한 !! 연산자 사용을 지적...

Files:

  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt
🧬 Code graph analysis (1)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (3)
app/src/main/java/com/poti/android/core/designsystem/theme/Theme.kt (1)
  • PotiTheme (48-71)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiModalButton.kt (1)
  • PotiModalButton (25-59)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiFloatingButton.kt (1)
  • PotiFloatingButton (35-83)
⏰ 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). (1)
  • GitHub Check: PR CI Check (Lint & Build)
🔇 Additional comments (3)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (3)

42-48: LGTM! Modifier 파라미터 위치가 올바릅니다.

Jetpack Compose 컨벤션에 따라 Modifier가 첫 번째 선택적 파라미터로 올바르게 배치되어 있습니다. onDismissRequest는 필수 파라미터이므로 먼저 오는 것이 적절합니다.


50-56: remember를 활용한 DialogProperties 캐싱이 잘 되어 있습니다.

dismissOnBackPressdismissOnClickOutside를 키로 사용하여 불필요한 재생성을 방지하고 있습니다. Modern Android Development 권장 사항을 잘 따르고 계시네요.


78-119: Preview 함수가 잘 구성되어 있습니다.

private 접근 제한자를 사용하여 Preview가 외부로 노출되지 않도록 했고, PotiTheme으로 감싸서 테마가 올바르게 적용되고 있습니다. 실제 사용 시나리오를 잘 보여주는 좋은 Preview입니다.

Copy link
Copy Markdown
Contributor

@jyvnee jyvnee left a comment

Choose a reason for hiding this comment

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

한 번 만... 확인 부탁띠......

Comment on lines +98 to +99
.heightForScreenPercentage(180.dp)
.widthForScreenPercentage(180.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.

P1: 요거는 정사각형을 의도한 것 같아서 .widthForScreenPercentage(180.dp)를 aspectRatio를 1f로 설정해주세요!

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.

오 이게 맞네요!!!

Row(
modifier = Modifier
.padding(top = 24.dp),
horizontalArrangement = Arrangement.spacedBy(8.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.

P1: 요기도 확장함수 한 번만.... ㅎㅎ

modifier = Modifier
.fillMaxWidth()
.padding(
vertical = screenHeightDp(16.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.

P3: 요기 근데 너비에 맞춰서 vertical이랑 horizontal이랑 다 screenWidthDp(16.dp)로 줘도 될 거 가타욤

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.

옹 패딩이 똑같이 적용되는거니까 all로 넣어주는게 맞겟네요!

@doyeon0307 doyeon0307 force-pushed the feat/#19-modal-component branch from c70482c to e8b4178 Compare January 13, 2026 15:48
Copy link
Copy Markdown

@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: 0

🧹 Nitpick comments (3)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt (1)

67-84: 패딩 값 사용 방식이 일관되지 않습니다.

Line 63에서는 screenWidthDp(16.dp)를 사용하여 반응형 사이징을 적용하셨는데, Line 70-71, 83에서는 하드코딩된 16.dp, 8.dp, 24.dp를 직접 사용하고 계십니다. 다양한 화면 크기에서 일관된 UI 스케일링을 위해 screenWidthDp()를 일관되게 사용하시거나, 디자인 토큰으로 추출하시는 것을 권장드립니다.

♻️ 일관된 반응형 패딩 적용 제안
 Text(
     text = title,
     modifier = Modifier
-        .padding(top = 16.dp, bottom = 8.dp),
+        .padding(top = screenWidthDp(16.dp), bottom = screenWidthDp(8.dp)),
     color = PotiTheme.colors.black,
     style = PotiTheme.typography.body16sb,
 )
 ...
 Row(
     modifier = Modifier
-        .padding(top = 24.dp),
+        .padding(top = screenWidthDp(24.dp)),
     horizontalArrangement = Arrangement.spacedBy(screenWidthDp(8.dp)),
 )
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (2)

62-66: SideEffect 사용이 적절합니다만, dim 값 추출을 고려해 주세요.

Dialog window의 속성을 수정하기 위해 SideEffect를 사용하신 것은 올바른 패턴입니다. 다만 0.4f라는 매직 넘버는 상수로 추출하시면 향후 디자인 변경 시 관리가 용이해집니다.

♻️ 상수 추출 제안
private const val MODAL_DIM_AMOUNT = 0.4f

// 사용 시
dialogWindow?.setDimAmount(MODAL_DIM_AMOUNT)

68-75: 테마 색상 사용이 좋습니다. 모서리 반경도 디자인 토큰으로 관리하시면 더 좋을 것 같습니다.

PotiTheme.colors.white를 사용하여 테마 시스템을 잘 활용하고 계십니다. RoundedCornerShape(12.dp) 역시 디자인 시스템의 일관성을 위해 별도 상수나 테마 값으로 추출하시는 것을 권장드립니다.

♻️ 모서리 반경 상수화 제안
private val ModalCornerRadius = 12.dp

// 사용 시
shape = RoundedCornerShape(ModalCornerRadius),
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c70482c and e8b4178.

📒 Files selected for processing (3)
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiLargeModal.kt
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt

⚙️ CodeRabbit configuration file

**/*.kt: 코드를 리뷰할 때 다음 Kotlin 및 Jetpack Compose 모범 사례를 중점적으로 확인:

  1. Jetpack Compose 성능 및 UI 최적화:

    • Recomposition 방지: 불필요한 리컴포지션을 유발하는 코드를 지적해줘. 특히 LazyColumn 등 리스트에서 key 파라미터가 누락되었는지 확인해줘.
    • 스마트한 계산: 계산 비용이 높은 로직은 rememberderivedStateOf로 감싸져 있는지 확인해줘.
    • 안정성(Stability): List나 일반 클래스를 State로 사용할 때, 불필요한 리컴포지션을 막기 위해 @Immutable이나 @Stable 어노테이션 사용을 제안해줘.
    • Modifier 규칙: Modifier는 항상 컴포저블 함수의 첫 번째 선택적 파라미터로 받아야 하며, 재사용성을 위해 외부에서 주입받아야 해.
    • 리소스 사용: 하드코딩된 값(색상, 크기, 문자열) 대신 Theme, Dimens, strings.xml 리소스를 사용하는지 체크해줘.
    • 접근성 예외: Icon이나 Image 컴포저블의 contentDescriptionnull인 경우는 장식용(Decorative) 이미지로 간주하고 지적하지 마.
  2. MVI (Model-View-Intent) 아키텍처 및 상태 관리:

    • 단방향 데이터 흐름(UDF): UI는 상태를 직접 변경할 수 없고, 반드시 Intent(또는 Event)를 통해 ViewModel로 요청을 보내야 해.
    • 불변 상태(Immutable State): UiState는 반드시 data class + val 프로퍼티로 구성되어야 하며, 상태 업데이트 시 .copy()를 사용해야 해.
    • Side Effect 분리: 네비게이션, Toast 같은 일회성 이벤트는 UiState가 아닌 Channel이나 SharedFlow로 처리하여 화면 회전 시 재발생하지 않도록 해야 해.
    • 안전한 수집: UI에서 Flow 수집 시 collectAsStateWithLifecycle()을 사용하여 생명주기를 준수하는지 확인해줘.
    • 상태 노출: ViewModel 내부에서 StateFlowSharedFlow를 올바르게 노출하고 있는지 확인해줘.
    • 호이스팅(Hoisting): 상태나 UI 이벤트 처리가 적절하게 호이스팅되었는지 확인해줘. UI 로직과 비즈니스 로직을 분리하고, UI가 로직을 직접 수행하지 않도록 확인해줘.
  3. Clean Architecture (Dependency Rule) 준수:

    • Domain 순수성: Domain 레이어(domain 패키지)는 절대로 안드로이드 의존성(android.*, R 클래스 등)을 가져서는 안 돼. 순수 Kotlin 코드인지 엄격히 확인해줘.
    • 데이터 노출 금지: Data 레이어의 DTO(@Entity, @SerializedName)가 UI나 Domain 레이어로 바로 노출되면 안 돼. 반드시 Mapper를 통해 변환했는지 확인해줘.
    • 계층 간 의존성: UI -> Domain <- Data 방향이 올바른지, UI가 Data 레이어를 직접 참조하고 있진 않은지 감시해줘.
  4. Kotlin & Coroutines 안전성:

    • Scope 사용: GlobalScope 사용을 금지하고, viewModelScopelifecycleScope 사용을 권장해줘.
    • Dispatcher 주입: Dispatchers.IO 등을 하드코딩하지 말고, 테스트 용이성을 위해 생성자를 통해 주입(Injection)받도록 제안해줘.
    • 예외 처리: Coroutine 실행 시 runCatching이나 CoroutineExceptionHandler 등을 통해 예외 처리가 누락되지 않았는지 확인해줘.
    • Null-Safety: 불필요한 !! 연산자 사용을 지적...

Files:

  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt
  • app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt
🧬 Code graph analysis (1)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (3)
app/src/main/java/com/poti/android/core/designsystem/theme/Theme.kt (1)
  • PotiTheme (48-71)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiModalButton.kt (1)
  • PotiModalButton (25-59)
app/src/main/java/com/poti/android/core/designsystem/component/button/PotiFloatingButton.kt (1)
  • PotiFloatingButton (35-83)
🔇 Additional comments (4)
app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiSmallModal.kt (2)

40-52: LGTM! 함수 시그니처가 잘 구성되어 있습니다.

필수 파라미터들이 먼저 오고, Modifier가 첫 번째 선택적 파라미터로 배치되어 Kotlin 및 Compose 컨벤션을 준수하고 있습니다. dismissOnBackPressdismissOnClickOutside 플래그로 dismiss 동작을 세밀하게 제어할 수 있어 좋습니다.


106-133: Preview 구현이 깔끔합니다.

remembermutableStateOf를 활용한 상태 토글 패턴이 적절하며, PotiTheme으로 감싸서 테마 적용을 확인할 수 있도록 구성하셨네요. Preview 함수가 private으로 선언되어 있어 외부 노출을 방지한 점도 좋습니다.

app/src/main/java/com/poti/android/core/designsystem/component/modal/PotiModal.kt (2)

42-56: LGTM! internal 가시성과 remember를 통한 최적화가 잘 적용되어 있습니다.

기본 모달 컴포넌트를 internal로 선언하여 모듈 내부에서만 사용하도록 캡슐화한 점이 좋습니다. DialogPropertiesremember로 감싸고 dismissOnBackPress, dismissOnClickOutside를 키로 사용하여 불필요한 재생성을 방지하신 것도 적절합니다.


78-119: Preview가 컴포넌트 사용 방법을 잘 보여주고 있습니다.

@sample 태그로 KDoc에 연결된 Preview가 기본적인 사용 패턴을 명확하게 보여주고 있어 좋습니다. private 선언과 PotiTheme 래핑도 적절합니다.

Copy link
Copy Markdown
Contributor

@jyvnee jyvnee left a comment

Choose a reason for hiding this comment

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

진짜 짱!!!! 수고햇어!!!!!

@doyeon0307 doyeon0307 merged commit 33318ea into develop Jan 13, 2026
2 checks passed
@github-project-automation github-project-automation bot moved this from In review to Done in POTI-ANDROID Jan 13, 2026
@doyeon0307 doyeon0307 deleted the feat/#19-modal-component branch January 13, 2026 15:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🌟 Feat 새로운 기능 구현 🐣 도연 🔎 UI UI 작업

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Feat] 모달 컴포넌트 구현

3 participants