skydoves / FlexibleBottomSheet

🐬 Advanced Compose Multiplatform bottom sheet for segmented sizing, non-modal type, and allows interaction behind the bottom sheet similar to Google Maps.
Apache License 2.0
791 stars 36 forks source link

Popup Placement is Inconsistent in FlexibleBottomSheet #50

Open moicompsci opened 3 months ago

moicompsci commented 3 months ago

Tested on API 30 (Samsung S23) and API 34 (Pixel 7a).

In my FlexibleBottomSheet, I am showing a Popup (in my example code the Popup/tooltip can be shown when holding/pressing on the button).

1. When isModal is set to false, the tooltip does not remain in place consistently in all of its "expanded/slightly-expanded" layouts: isModalFalse0

After sliding the bottom sheet: (Notice the offset between the button) isModalFalse1

2. When isModal is set to true, and comparing to Material 3's ModalBottomSheet and BottomSheetScaffold, the Popup has an unexpected offset (the offset is not happening in the Material 3's ModalBottomSheet and BottomSheetScaffold).

Flexible Bottom Sheet (isModal = true): isModalTrue0

Material 3 BottomSheetScaffold: bottomsheetScaffold0

Material 3 ModalBottomSheet: modalBottomSheet0

Here is my relavant code:

import androidx.compose.foundation.interaction.HoverInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.BottomSheetScaffold
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Surface
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.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupPositionProvider
import com.skydoves.flexible.bottomsheet.material3.FlexibleBottomSheet
import com.skydoves.flexible.core.FlexibleSheetSize
import com.skydoves.flexible.core.rememberFlexibleBottomSheetState

@Preview
@Composable
fun FlexibleBottomSheetExample() {
    val sheetSize = FlexibleSheetSize(
        fullyExpanded = 1f,
        intermediatelyExpanded = 0.5f,
        slightlyExpanded = 0.10f,
    )
    val sheetState  = rememberFlexibleBottomSheetState(
        flexibleSheetSize = sheetSize,
        containSystemBars = false,
        isModal = true,
        skipSlightlyExpanded = true,
        confirmValueChange = { true }
    )
    FlexibleBottomSheet(
        onDismissRequest = {},
        sheetState = sheetState,
        content = {
            LazyColumn {
                item {
                    Spacer(modifier = Modifier.height(24.dp))
                    ButtonWithTooltip()
                    Spacer(modifier = Modifier.height(64.dp))
                }
            }
        }
    )
}

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun BottomSheetScaffoldExample() {
    BottomSheetScaffold(
        sheetContent = {
            Spacer(modifier = Modifier.height(24.dp))
            ButtonWithTooltip()
            Spacer(modifier = Modifier.height(64.dp))
        },
        sheetPeekHeight = 140.dp
    ) {
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun ModalBottomSheetExample() {
    ModalBottomSheet(
        onDismissRequest = {},
        content = {
            Spacer(modifier = Modifier.height(24.dp))
            ButtonWithTooltip()
            Spacer(modifier = Modifier.height(64.dp))
        },
    )
}

@Composable
fun ButtonWithTooltip() {
    var showTooltip by remember { mutableStateOf(false) }
    val interactionSource = remember { MutableInteractionSource() }

    LaunchedEffect(interactionSource) {
        interactionSource.interactions.collect { interaction ->
            when (interaction) {
                is PressInteraction.Press -> showTooltip = true
                is PressInteraction.Release,
                is PressInteraction.Cancel -> showTooltip = false
                is HoverInteraction.Enter -> showTooltip = true
                is HoverInteraction.Exit -> showTooltip = false
            }
        }
    }
    Box {
        Button(
            onClick = { },
            interactionSource = interactionSource
        ) {
            Text(text = "Button")
        }
        if (showTooltip) {
            Popup(
                popupPositionProvider = object : PopupPositionProvider {
                    override fun calculatePosition(
                        anchorBounds: IntRect,
                        windowSize: IntSize,
                        layoutDirection: LayoutDirection,
                        popupContentSize: IntSize
                    ): IntOffset {
                        val x = anchorBounds.left
                        val y = anchorBounds.top - popupContentSize.height
                        return IntOffset(x, y)
                    }
                }
            ) {
                Surface(
                    shape = RoundedCornerShape(4.dp),
                    color = if (isSystemInDarkTheme()) Color.White else Color.Black,
                ) {
                    Text(
                        text = "Tooltip",
                        color = if (isSystemInDarkTheme()) Color.Black else Color.White,
                        modifier = Modifier.padding(8.dp)
                    )
                }
            }
        }
    }
}