Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions design/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -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<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function0<kotlin.Unit> 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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
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
}

/**
* 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,
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)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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
)