From 93376a09e20d2937660dd1cb59d11207279ad56d Mon Sep 17 00:00:00 2001 From: moondev03 Date: Sun, 10 May 2026 00:19:36 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EB=82=B4=20PrezelTooltipBox=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * **feat: `PrezelTooltipBox` 컴포넌트 구현** * Skydoves의 Balloon 라이브러리를 활용하여 커스텀 툴팁 컴포넌트를 구현했습니다. * 디자인 가이드에 맞춰 배경색(`bgMedium`), 화살표 크기, 코너 라운드, 패딩 등 `Balloon.Builder` 설정을 적용했습니다. * 툴팁 내부 콘텐츠 구성을 위한 `TooltipContent`를 분리하고, 선택적인 닫기 아이콘 표시 기능을 추가했습니다. * 클릭 시 툴팁이 노출되도록 `Modifier.balloon` 및 `rememberBalloonState`를 연동했습니다. * **build: Balloon 라이브러리 의존성 추가** * 툴팁 기능 구현을 위해 `core:designsystem` 모듈에 `balloon-compose` 의존성을 추가했습니다. --- Prezel/core/designsystem/build.gradle.kts | 2 + .../feedback/tooltip/PrezelTooltipBox.kt | 166 ++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt diff --git a/Prezel/core/designsystem/build.gradle.kts b/Prezel/core/designsystem/build.gradle.kts index 232071f4..919bcae9 100644 --- a/Prezel/core/designsystem/build.gradle.kts +++ b/Prezel/core/designsystem/build.gradle.kts @@ -12,4 +12,6 @@ dependencies { implementation(libs.coil.kt.compose) implementation(libs.kotlinx.datetime) implementation(libs.timber) + + implementation("com.github.skydoves:balloon-compose:1.7.6") } diff --git a/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt new file mode 100644 index 00000000..b6518bea --- /dev/null +++ b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt @@ -0,0 +1,166 @@ +package com.team.prezel.core.designsystem.component.feedback.tooltip + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.skydoves.balloon.Balloon +import com.skydoves.balloon.compose.balloon +import com.skydoves.balloon.compose.rememberBalloonBuilder +import com.skydoves.balloon.compose.rememberBalloonState +import com.skydoves.balloon.compose.setBackgroundColor +import com.team.prezel.core.designsystem.icon.PrezelIcons +import com.team.prezel.core.designsystem.preview.BasicPreview +import com.team.prezel.core.designsystem.preview.PreviewScaffold +import com.team.prezel.core.designsystem.theme.PrezelColorScheme +import com.team.prezel.core.designsystem.theme.PrezelTheme + +@Composable +private fun rememberBalloonBuilder(showArrow: Boolean): Balloon.Builder = + rememberBalloonBuilder { + setIsVisibleArrow(showArrow) + setArrowWidth(12) + setArrowHeight(6) + setCornerRadius(6f) + setPaddingVertical(4) + setPaddingLeft(8) + setPaddingRight(6) + setBackgroundColor(PrezelColorScheme.Dark.bgMedium) + } + +@Composable +fun PrezelTooltipBox( + text: String, + modifier: Modifier = Modifier, + showDismissIcon: Boolean = true, + showArrow: Boolean = true, + content: @Composable BoxScope.() -> Unit, +) { + val builder = rememberBalloonBuilder(showArrow) + val state = rememberBalloonState(builder) + + Box( + content = content, + modifier = modifier + .balloon(state) { + TooltipContent( + text = text, + showDismissIcon = showDismissIcon, + ) + }.clickable( + interactionSource = null, + indication = null, + onClick = { state.showAlignTop() }, + ), + ) +} + +@Composable +fun TooltipContent( + text: String, + showDismissIcon: Boolean = false, +) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = text, + style = PrezelTheme.typography.caption1Regular, + color = PrezelColorScheme.Dark.textLarge, + ) + + if (showDismissIcon) { + Spacer(modifier = Modifier.width(PrezelTheme.spacing.V2)) + Icon( + modifier = Modifier + .size(14.dp) + .offset(x = 2.dp), + painter = painterResource(PrezelIcons.Cancel), + tint = PrezelColorScheme.Dark.iconLarge, + contentDescription = "", + ) + } + } +} + +@BasicPreview +@Composable +private fun PrezelTooltipBoxPreview() { + PrezelTheme { + PreviewScaffold( + modifier = Modifier.fillMaxSize(), + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + .verticalScroll(rememberScrollState()), + ) { + Text(text = "정확도", style = PrezelTheme.typography.title2Bold) + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + PrezelTooltipBox(text = "SPM은 1분당 말하는 음절의 수에요.") { + Row(verticalAlignment = Alignment.CenterVertically) { + Text(text = "Label", style = PrezelTheme.typography.body3Regular) + Spacer(modifier = Modifier.width(8.dp)) + Icon( + painter = painterResource(PrezelIcons.Blank), + contentDescription = "", + ) + } + } + + PrezelTooltipBox(text = "SPM은 1분당 말하는 음절의 수에요.") { + Row(verticalAlignment = Alignment.CenterVertically) { + Text(text = "Label", style = PrezelTheme.typography.body3Regular) + Spacer(modifier = Modifier.width(8.dp)) + Icon( + painter = painterResource(PrezelIcons.Blank), + contentDescription = "", + ) + } + } + + PrezelTooltipBox(text = "SPM은 1분당 말하는 음절의 수에요.") { + Row(verticalAlignment = Alignment.CenterVertically) { + Text(text = "Label", style = PrezelTheme.typography.body3Regular) + Spacer(modifier = Modifier.width(8.dp)) + Icon( + painter = painterResource(PrezelIcons.Blank), + contentDescription = "", + ) + } + } + } + Box( + modifier = Modifier + .fillMaxWidth() + .height(1000.dp), + contentAlignment = Alignment.Center, + ) { + Text(text = "This is Blank for Scroll") + } + } + } + } +} From f11b597b813e824bf8faf0a4978a5910f0782659 Mon Sep 17 00:00:00 2001 From: moondev03 Date: Sun, 10 May 2026 00:24:39 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20PrezelTooltipBox=20=ED=99=94?= =?UTF-8?q?=EC=82=B4=ED=91=9C=20=EC=A4=91=EC=95=99=20=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * **style: PrezelTooltipBox의 Balloon 화살표 배치 설정 수정** * 툴팁 화살표가 기준이 되는 요소(Anchor)의 중앙에 정확히 위치하도록 `setArrowPosition(0.5f)` 및 `setArrowPositionRules(ArrowPositionRules.ALIGN_ANCHOR)` 설정을 추가했습니다. --- .../component/feedback/tooltip/PrezelTooltipBox.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt index b6518bea..97dca4c4 100644 --- a/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt +++ b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import com.skydoves.balloon.ArrowPositionRules import com.skydoves.balloon.Balloon import com.skydoves.balloon.compose.balloon import com.skydoves.balloon.compose.rememberBalloonBuilder @@ -40,6 +41,8 @@ private fun rememberBalloonBuilder(showArrow: Boolean): Balloon.Builder = setIsVisibleArrow(showArrow) setArrowWidth(12) setArrowHeight(6) + setArrowPosition(0.5f) + setArrowPositionRules(ArrowPositionRules.ALIGN_ANCHOR) setCornerRadius(6f) setPaddingVertical(4) setPaddingLeft(8) From 4d1f8f0b1f699929902f9eec2f25484bb380aec7 Mon Sep 17 00:00:00 2001 From: moondev03 Date: Sun, 10 May 2026 00:30:21 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20PrezelTooltipBox=20=EC=83=81?= =?UTF-8?q?=ED=98=B8=EC=9E=91=EC=9A=A9=20=EB=B0=8F=20dismiss=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * **feat: `PrezelTooltipBox` 외부 터치 시 닫힘 방지 및 클릭 동작 수정** * Balloon 설정에 `setDismissWhenTouchOutside(false)`를 추가하여 외부 영역 터치 시 툴팁이 자동으로 닫히지 않도록 변경했습니다. * 툴팁 컨텐츠를 클릭했을 때 툴팁이 닫히도록 `TooltipContent`에 클릭 이벤트를 추가했습니다. * **refactor: `Modifier.noRippleClick` 확장 함수 추가 및 적용** * 리플 효과 없이 클릭 이벤트를 처리하기 위한 `noRippleClick` 확장 함수를 정의했습니다. * `PrezelTooltipBox`의 표시(show) 및 닫기(dismiss) 로직에 해당 함수를 적용하여 불필요한 인디케이션을 제거했습니다. * **refactor: `TooltipContent` 컴포저블 구조 개선** * 상위 수준에서 레이아웃 및 이벤트를 제어할 수 있도록 `modifier` 파라미터를 추가하고 내부 `Row`에 적용했습니다. --- .../feedback/tooltip/PrezelTooltipBox.kt | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt index 97dca4c4..553443a3 100644 --- a/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt +++ b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt @@ -47,6 +47,7 @@ private fun rememberBalloonBuilder(showArrow: Boolean): Balloon.Builder = setPaddingVertical(4) setPaddingLeft(8) setPaddingRight(6) + setDismissWhenTouchOutside(false) setBackgroundColor(PrezelColorScheme.Dark.bgMedium) } @@ -64,25 +65,35 @@ fun PrezelTooltipBox( Box( content = content, modifier = modifier + .noRippleClick { state.showAlignTop() } .balloon(state) { TooltipContent( text = text, showDismissIcon = showDismissIcon, + modifier = Modifier.noRippleClick { state.dismiss() }, ) - }.clickable( - interactionSource = null, - indication = null, - onClick = { state.showAlignTop() }, - ), + }, ) } +@Composable +private fun Modifier.noRippleClick(onClick: () -> Unit): Modifier = + this.clickable( + interactionSource = null, + indication = null, + onClick = onClick, + ) + @Composable fun TooltipContent( text: String, + modifier: Modifier = Modifier, showDismissIcon: Boolean = false, ) { - Row(verticalAlignment = Alignment.CenterVertically) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier, + ) { Text( text = text, style = PrezelTheme.typography.caption1Regular, From f4dcb4e9a705d1fc71bd26e95f9d47af08303854 Mon Sep 17 00:00:00 2001 From: moondev03 Date: Sun, 10 May 2026 00:41:46 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20PrezelTooltipBox=20=EA=B0=80?= =?UTF-8?q?=EC=8B=9C=EC=84=B1=20=EC=A0=9C=EC=96=B4=20=EB=B0=8F=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EA=B4=80=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * **feat: 툴팁 노출 상태 및 화면 내 가시성 처리 로직 추가** * `onGloballyPositioned`와 `getWindowVisibleDisplayFrame`을 사용하여 툴팁의 앵커가 현재 화면(Window) 내에 위치하는지 감지하는 로직을 구현했습니다. * `shouldRestoreTooltip` 상태를 추가하여 사용자가 명시적으로 툴팁을 닫았는지 여부를 추적하고, 앵커가 화면에 보일 때만 툴팁이 유지되도록 `LaunchedEffect`로 제어합니다. * 툴팁 내부 클릭 시 `shouldRestoreTooltip`을 `false`로 설정하여 의도치 않게 다시 뜨는 현상을 방지했습니다. * **refactor: 툴팁 컴포넌트 구조 및 설정 최적화** * `TooltipContent`의 가시성을 `private`으로 제한하여 캡슐화를 강화했습니다. * `BalloonAnimation.NONE`을 적용하여 툴팁 표시 시 불필요한 애니메이션을 제거했습니다. * 툴팁의 닫기 아이콘에 접근성을 위한 `contentDescription`을 추가하고 관련 문자열 리소스를 정의했습니다. * 앵커 위치 계산을 위해 `Rect.intersects` 확장 함수를 추가했습니다. * **style: 프리뷰 코드 정리** * `PrezelTooltipBoxPreview`에서 불필요한 `innerPadding` 참조를 제거하고 레이아웃을 정돈했습니다. --- .../feedback/tooltip/PrezelTooltipBox.kt | 80 ++++++++++++------- .../src/main/res/values/strings.xml | 1 + 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt index 553443a3..a57c422b 100644 --- a/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt +++ b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState @@ -19,38 +18,35 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.toComposeRect +import androidx.compose.ui.layout.boundsInWindow +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.skydoves.balloon.ArrowPositionRules import com.skydoves.balloon.Balloon +import com.skydoves.balloon.BalloonAnimation import com.skydoves.balloon.compose.balloon import com.skydoves.balloon.compose.rememberBalloonBuilder import com.skydoves.balloon.compose.rememberBalloonState import com.skydoves.balloon.compose.setBackgroundColor +import com.team.prezel.core.designsystem.R import com.team.prezel.core.designsystem.icon.PrezelIcons import com.team.prezel.core.designsystem.preview.BasicPreview import com.team.prezel.core.designsystem.preview.PreviewScaffold import com.team.prezel.core.designsystem.theme.PrezelColorScheme import com.team.prezel.core.designsystem.theme.PrezelTheme -@Composable -private fun rememberBalloonBuilder(showArrow: Boolean): Balloon.Builder = - rememberBalloonBuilder { - setIsVisibleArrow(showArrow) - setArrowWidth(12) - setArrowHeight(6) - setArrowPosition(0.5f) - setArrowPositionRules(ArrowPositionRules.ALIGN_ANCHOR) - setCornerRadius(6f) - setPaddingVertical(4) - setPaddingLeft(8) - setPaddingRight(6) - setDismissWhenTouchOutside(false) - setBackgroundColor(PrezelColorScheme.Dark.bgMedium) - } - @Composable fun PrezelTooltipBox( text: String, @@ -61,21 +57,54 @@ fun PrezelTooltipBox( ) { val builder = rememberBalloonBuilder(showArrow) val state = rememberBalloonState(builder) + val view = LocalView.current + var shouldRestoreTooltip by remember { mutableStateOf(false) } + var isAnchorVisibleInWindow by remember { mutableStateOf(true) } + + LaunchedEffect(shouldRestoreTooltip, isAnchorVisibleInWindow) { + if (shouldRestoreTooltip && isAnchorVisibleInWindow) state.showAlignTop() else state.dismiss() + } Box( content = content, modifier = modifier - .noRippleClick { state.showAlignTop() } + .onGloballyPositioned { coordinates -> + val visibleFrame = android.graphics.Rect() + view.getWindowVisibleDisplayFrame(visibleFrame) + isAnchorVisibleInWindow = coordinates.boundsInWindow().intersects(visibleFrame.toComposeRect()) + }.noRippleClick { shouldRestoreTooltip = true } .balloon(state) { TooltipContent( text = text, showDismissIcon = showDismissIcon, - modifier = Modifier.noRippleClick { state.dismiss() }, + modifier = Modifier.noRippleClick { + shouldRestoreTooltip = false + state.dismiss() + }, ) }, ) } +@Composable +private fun rememberBalloonBuilder(showArrow: Boolean): Balloon.Builder = + rememberBalloonBuilder { + setIsVisibleArrow(showArrow) + setArrowWidth(12) + setArrowHeight(6) + setArrowPosition(0.5f) + setArrowPositionRules(ArrowPositionRules.ALIGN_ANCHOR) + setCornerRadius(6f) + setPaddingVertical(4) + setPaddingLeft(8) + setPaddingRight(6) + setDismissWhenTouchOutside(false) + setBalloonAnimation(BalloonAnimation.NONE) + setBackgroundColor(PrezelColorScheme.Dark.bgMedium) + } + +private fun Rect.intersects(other: Rect): Boolean = left < other.right && right > other.left && top < other.bottom && bottom > other.top + @Composable private fun Modifier.noRippleClick(onClick: () -> Unit): Modifier = this.clickable( @@ -85,7 +114,7 @@ private fun Modifier.noRippleClick(onClick: () -> Unit): Modifier = ) @Composable -fun TooltipContent( +private fun TooltipContent( text: String, modifier: Modifier = Modifier, showDismissIcon: Boolean = false, @@ -103,12 +132,12 @@ fun TooltipContent( if (showDismissIcon) { Spacer(modifier = Modifier.width(PrezelTheme.spacing.V2)) Icon( + painter = painterResource(PrezelIcons.Cancel), + contentDescription = stringResource(R.string.core_designsystem_tooltip_cancel_btn_content_desc), + tint = PrezelColorScheme.Dark.iconLarge, modifier = Modifier .size(14.dp) .offset(x = 2.dp), - painter = painterResource(PrezelIcons.Cancel), - tint = PrezelColorScheme.Dark.iconLarge, - contentDescription = "", ) } } @@ -118,13 +147,10 @@ fun TooltipContent( @Composable private fun PrezelTooltipBoxPreview() { PrezelTheme { - PreviewScaffold( - modifier = Modifier.fillMaxSize(), - ) { innerPadding -> + PreviewScaffold(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier .fillMaxSize() - .padding(innerPadding) .verticalScroll(rememberScrollState()), ) { Text(text = "정확도", style = PrezelTheme.typography.title2Bold) diff --git a/Prezel/core/designsystem/src/main/res/values/strings.xml b/Prezel/core/designsystem/src/main/res/values/strings.xml index f994348e..9e7e3bda 100644 --- a/Prezel/core/designsystem/src/main/res/values/strings.xml +++ b/Prezel/core/designsystem/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ 체크박스 선택하기 %1$d년 %2$d월 + 툴팁 닫기 From 9d7050d5d1db6420a975601b637fc4b7993eeaed Mon Sep 17 00:00:00 2001 From: moondev03 Date: Sun, 10 May 2026 01:26:40 +0900 Subject: [PATCH 5/6] =?UTF-8?q?refactor:=20`PrezelTooltipBox`=20=EA=B0=80?= =?UTF-8?q?=EC=8B=9C=EC=84=B1=20=EA=B3=84=EC=82=B0=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * **refactor: `onGloballyPositioned` 내 객체 할당 최적화** * 뷰의 가시 영역을 계산할 때 사용되는 `android.graphics.Rect` 객체를 `remember`를 사용하여 재사용하도록 변경함으로써, 레이아웃 계산 시 발생하는 불필요한 객체 생성을 방지했습니다. * `modifier` 내 `noRippleClick`과 `onGloballyPositioned`의 호출 순서를 변경하여 코드를 정리했습니다. --- .../component/feedback/tooltip/PrezelTooltipBox.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt index a57c422b..2ee04914 100644 --- a/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt +++ b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/feedback/tooltip/PrezelTooltipBox.kt @@ -58,6 +58,7 @@ fun PrezelTooltipBox( val builder = rememberBalloonBuilder(showArrow) val state = rememberBalloonState(builder) val view = LocalView.current + val visibleFrame = remember { android.graphics.Rect() } var shouldRestoreTooltip by remember { mutableStateOf(false) } var isAnchorVisibleInWindow by remember { mutableStateOf(true) } @@ -68,12 +69,11 @@ fun PrezelTooltipBox( Box( content = content, modifier = modifier + .noRippleClick { shouldRestoreTooltip = true } .onGloballyPositioned { coordinates -> - val visibleFrame = android.graphics.Rect() view.getWindowVisibleDisplayFrame(visibleFrame) isAnchorVisibleInWindow = coordinates.boundsInWindow().intersects(visibleFrame.toComposeRect()) - }.noRippleClick { shouldRestoreTooltip = true } - .balloon(state) { + }.balloon(state) { TooltipContent( text = text, showDismissIcon = showDismissIcon, From 2c724ed0f067ff6421db002193026b6af16e1c4f Mon Sep 17 00:00:00 2001 From: moondev03 Date: Sun, 10 May 2026 01:27:58 +0900 Subject: [PATCH 6/6] =?UTF-8?q?build:=20Balloon=20Compose=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20Version=20Catalog=20=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * **build: Balloon Compose 라이브러리 정의 및 참조 방식 변경** * `libs.versions.toml` 파일에 `balloonCompose` 버전 정보와 라이브러리 선언을 추가했습니다. * `core:designsystem` 모듈의 `build.gradle.kts`에서 하드코딩되어 있던 `balloon-compose` 의존성을 Version Catalog 참조(`libs.balloon.compose`) 방식으로 수정했습니다. --- Prezel/core/designsystem/build.gradle.kts | 2 +- Prezel/gradle/libs.versions.toml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Prezel/core/designsystem/build.gradle.kts b/Prezel/core/designsystem/build.gradle.kts index 919bcae9..75aa9036 100644 --- a/Prezel/core/designsystem/build.gradle.kts +++ b/Prezel/core/designsystem/build.gradle.kts @@ -13,5 +13,5 @@ dependencies { implementation(libs.kotlinx.datetime) implementation(libs.timber) - implementation("com.github.skydoves:balloon-compose:1.7.6") + implementation(libs.balloon.compose) } diff --git a/Prezel/gradle/libs.versions.toml b/Prezel/gradle/libs.versions.toml index 0b64faba..2529bd56 100644 --- a/Prezel/gradle/libs.versions.toml +++ b/Prezel/gradle/libs.versions.toml @@ -28,6 +28,7 @@ kotlinxCollectionsImmutable = "0.4.0" kotlinxSerialization = "1.9.0" kakao = "2.23.2" lottie = "6.6.7" +balloonCompose = "1.7.6" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -77,7 +78,9 @@ kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version. kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" } kakao-user = { module = "com.kakao.sdk:v2-user", version.ref = "kakao" } + lottie-compose = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "lottie" } +balloon-compose = { module = "com.github.skydoves:balloon-compose", version.ref = "balloonCompose" } # Dependencies of the included build-logic android-gradleApiPlugin = { group = "com.android.tools.build", name = "gradle-api", version.ref = "agp" }