From 0ad806c75c4870a86f481f5b3c8d8695ebed2e04 Mon Sep 17 00:00:00 2001 From: BohunD Date: Tue, 15 Jul 2025 12:51:23 +0300 Subject: [PATCH 1/2] implemented item overlay --- design/api/current.api | 48 +++++++++++ .../design/ui/overlay/OverlayConfig.kt | 15 ++++ .../android/design/ui/overlay/OverlayId.kt | 83 +++++++++++++++++++ .../design/ui/overlay/OverlayPositionInfo.kt | 9 ++ 4 files changed, 155 insertions(+) create mode 100644 design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayConfig.kt create mode 100644 design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayId.kt create mode 100644 design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayPositionInfo.kt diff --git a/design/api/current.api b/design/api/current.api index 52f02a66..5f9594bc 100644 --- a/design/api/current.api +++ b/design/api/current.api @@ -168,6 +168,54 @@ package com.urlaunched.android.design.ui.modifiers { } +package com.urlaunched.android.design.ui.overlay { + + public final class OverlayConfig { + ctor public OverlayConfig(optional long offset, optional float backgroundAlpha, optional boolean usePlatformDefaultWidth, optional boolean dismissOnClickOutside, optional boolean dismissOnBackPress, optional Integer? windowAnimationsRes, optional Integer? widthOverride, optional androidx.compose.ui.unit.IntSize? overlaySize); + method public long component1-RKDOV3M(); + method public float component2(); + method public boolean component3(); + method public boolean component4(); + method public boolean component5(); + method public Integer? component6(); + method public Integer? component7(); + method public androidx.compose.ui.unit.IntSize? component8-bOM6tXw(); + method public com.urlaunched.android.design.ui.overlay.OverlayConfig copy-xcVtckk(long offset, float backgroundAlpha, boolean usePlatformDefaultWidth, boolean dismissOnClickOutside, boolean dismissOnBackPress, Integer? windowAnimationsRes, Integer? widthOverride, androidx.compose.ui.unit.IntSize? overlaySize); + method public float getBackgroundAlpha(); + method public boolean getDismissOnBackPress(); + method public boolean getDismissOnClickOutside(); + method public long getOffset(); + method public androidx.compose.ui.unit.IntSize? getOverlaySize(); + method public boolean getUsePlatformDefaultWidth(); + method public Integer? getWidthOverride(); + method public Integer? getWindowAnimationsRes(); + property public final float backgroundAlpha; + property public final boolean dismissOnBackPress; + property public final boolean dismissOnClickOutside; + property public final long offset; + property public final androidx.compose.ui.unit.IntSize? overlaySize; + property public final boolean usePlatformDefaultWidth; + property public final Integer? widthOverride; + property public final Integer? windowAnimationsRes; + } + + public final class OverlayIdKt { + method @androidx.compose.runtime.Composable public static void CustomOverlay(com.urlaunched.android.design.ui.overlay.OverlayPositionInfo positionInfo, optional com.urlaunched.android.design.ui.overlay.OverlayConfig config, kotlin.jvm.functions.Function0 onDismissRequest, kotlin.jvm.functions.Function0 content); + } + + public final class OverlayPositionInfo { + ctor public OverlayPositionInfo(long position, long size); + method public long component1-F1C5BW0(); + method public long component2-YbymL2g(); + method public com.urlaunched.android.design.ui.overlay.OverlayPositionInfo copy-CowoxoA(long position, long size); + method public long getPosition(); + method public long getSize(); + property public final long position; + property public final long size; + } + +} + package com.urlaunched.android.design.ui.paging { public final class PagingColumnKt { diff --git a/design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayConfig.kt b/design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayConfig.kt new file mode 100644 index 00000000..600adedb --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayConfig.kt @@ -0,0 +1,15 @@ +package com.urlaunched.android.design.ui.overlay + +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.IntSize + +data class OverlayConfig( + val offset: DpOffset = DpOffset.Zero, + val backgroundAlpha: Float = 0.5f, + val usePlatformDefaultWidth: Boolean = false, + val dismissOnClickOutside: Boolean = true, + val dismissOnBackPress: Boolean = true, + val windowAnimationsRes: Int? = null, + val widthOverride: Int? = null, + val overlaySize: IntSize? = null, +) \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayId.kt b/design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayId.kt new file mode 100644 index 00000000..329cb047 --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayId.kt @@ -0,0 +1,83 @@ +package com.urlaunched.android.design.ui.overlay + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.statusBars +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.compose.ui.window.DialogWindowProvider + +private enum class OverlayId { + CONTENT +} + +@Composable +fun CustomOverlay( + positionInfo: OverlayPositionInfo, + config: OverlayConfig = OverlayConfig(), + onDismissRequest: () -> Unit, + content: @Composable () -> Unit +) { + val statusBarHeight = WindowInsets.statusBars.getTop(LocalDensity.current) + + Dialog( + onDismissRequest = onDismissRequest, + properties = DialogProperties( + usePlatformDefaultWidth = config.usePlatformDefaultWidth, + dismissOnClickOutside = config.dismissOnClickOutside, + dismissOnBackPress = config.dismissOnBackPress, + decorFitsSystemWindows = true, + ) + ) { + val dialogWindow = (LocalView.current.parent as? DialogWindowProvider)?.window + + SideEffect { + dialogWindow?.let { window -> + window.setDimAmount(config.backgroundAlpha) + config.windowAnimationsRes?.let { window.setWindowAnimations(it) } + } + } + + Layout( + modifier = Modifier.clickable( + onClick = onDismissRequest, + indication = null, + interactionSource = null + ), + content = { + Box(modifier = Modifier.layoutId(OverlayId.CONTENT)) { + content() + } + } + ) { measurables, constraints -> + + val contentMeasurable = measurables.first { it.layoutId == OverlayId.CONTENT } + + val placeable = if (config.widthOverride != null) { + contentMeasurable.measure(Constraints.fixedWidth(config.widthOverride)) + } else { + contentMeasurable.measure( + Constraints( + maxWidth = constraints.maxWidth, + maxHeight = constraints.maxHeight + ) + ) + } + + layout(constraints.maxWidth, constraints.maxHeight) { + val x = positionInfo.position.x.toInt().coerceIn(0, constraints.maxWidth - placeable.width) + val y = (positionInfo.position.y.toInt() - statusBarHeight).coerceAtLeast(0) + placeable.place(x, y) + } + } + } +} \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayPositionInfo.kt b/design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayPositionInfo.kt new file mode 100644 index 00000000..fd3b2d14 --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayPositionInfo.kt @@ -0,0 +1,9 @@ +package com.urlaunched.android.design.ui.overlay + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.unit.IntSize + +data class OverlayPositionInfo( + val position: Offset, + val size: IntSize +) \ No newline at end of file From a842c0c88a6297cc5858fe1af3639cd9d611964a Mon Sep 17 00:00:00 2001 From: BohunD Date: Tue, 15 Jul 2025 14:40:00 +0300 Subject: [PATCH 2/2] added manual --- .../android/design/ui/overlay/OverlayId.kt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayId.kt b/design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayId.kt index 329cb047..777cbd35 100644 --- a/design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayId.kt +++ b/design/src/main/java/com/urlaunched/android/design/ui/overlay/OverlayId.kt @@ -20,6 +20,45 @@ private enum class OverlayId { CONTENT } +/** + * To use this correctly: + * + * 1. Track the position and size of the component you want to attach the overlay to using `Modifier.onGloballyPositioned { ... }` + * 2. From that callback, capture `positionInWindow()` and `size` into a `OverlayPositionInfo` + * 3. Pass that info to [CustomOverlay] when triggering it (e.g. on long press or tap) + * + * Example: + * + * ```kotlin + * var overlayInfo: OverlayPositionInfo? by remember { mutableStateOf(null) } + * var isOverlayVisible by remember { mutableStateOf(false) } + * + * Box(modifier = Modifier.onGloballyPositioned { coords -> + * overlayInfo = OverlayPositionInfo( + * position = coords.positionInWindow(), + * size = coords.size + * ) + * }) { + * // item + * } + * + * if (isOverlayVisible && overlayInfo != null) { + * CustomOverlay( + * positionInfo = overlayInfo!!, + * config = OverlayConfig(...), + * onDismissRequest = { isOverlayVisible = false } + * ) { + * // your overlay content here + * } + * } + * ``` + * + * @param positionInfo The position and size of the anchor element to align overlay content + * @param config OverlayConfig with customization options + * @param onDismissRequest Called when overlay should be dismissed (e.g. outside click) + * @param content The content to display in the overlay + */ + @Composable fun CustomOverlay( positionInfo: OverlayPositionInfo,