Skip to content

Commit

Permalink
Merge pull request #143 from MohamedRejeb/0.5.x
Browse files Browse the repository at this point in the history
Support partially expanded sheet on iOS
  • Loading branch information
MohamedRejeb authored Aug 7, 2024
2 parents c6c586c + 0b8ca62 commit f4d41ce
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import kotlinx.coroutines.CancellationException

private class SheetValueHolder @OptIn(ExperimentalMaterial3Api::class) constructor(
Expand All @@ -21,28 +23,35 @@ fun rememberAdaptiveSheetState(
skipPartiallyExpanded: Boolean = false,
confirmValueChange: (SheetValue) -> Boolean = { true },
): AdaptiveSheetState {
val density = LocalDensity.current

val sheetValueHolder = remember {
SheetValueHolder(SheetValue.Hidden)
}

val state = rememberSaveable(
skipPartiallyExpanded,
confirmValueChange,
density,
sheetValueHolder,
saver = AdaptiveSheetState.Saver(
skipPartiallyExpanded = skipPartiallyExpanded,
confirmValueChange = confirmValueChange
confirmValueChange = confirmValueChange,
density = density,
)
) {
if (skipPartiallyExpanded && sheetValueHolder.value == SheetValue.PartiallyExpanded)
sheetValueHolder.value = SheetValue.Expanded

AdaptiveSheetState(skipPartiallyExpanded, sheetValueHolder.value, confirmValueChange)
AdaptiveSheetState(skipPartiallyExpanded, density, sheetValueHolder.value, confirmValueChange)
}


LaunchedEffect(Unit) {
LaunchedEffect(state) {
snapshotFlow { state.currentValue }
.collect { sheetValueHolder.value = it }
.collect {
sheetValueHolder.value = it
}
}

return state
Expand All @@ -52,6 +61,7 @@ fun rememberAdaptiveSheetState(
@Stable
expect class AdaptiveSheetState(
skipPartiallyExpanded: Boolean,
density: Density,
initialValue: SheetValue = SheetValue.Hidden,
confirmValueChange: (SheetValue) -> Boolean = { true },
skipHiddenState: Boolean = false,
Expand Down Expand Up @@ -79,7 +89,8 @@ expect class AdaptiveSheetState(
*/
fun Saver(
skipPartiallyExpanded: Boolean,
confirmValueChange: (SheetValue) -> Boolean
confirmValueChange: (SheetValue) -> Boolean,
density: Density
): Saver<AdaptiveSheetState, SheetValue>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetValue
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.currentCompositionLocalContext
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameMillis
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
Expand All @@ -34,11 +37,13 @@ actual fun AdaptiveBottomSheet(
content: @Composable() (ColumnScope.() -> Unit)
) {
val compositionLocalContext = currentCompositionLocalContext
val currentUIViewController = LocalUIViewController.current

val isDark = isSystemInDarkTheme()

val sheetManager = remember {
val sheetManager = remember(currentUIViewController) {
BottomSheetManager(
parentUIViewController = currentUIViewController,
dark = isDark,
onDismiss = {
onDismissRequest()
Expand All @@ -48,8 +53,23 @@ actual fun AdaptiveBottomSheet(

CompositionLocalProvider(compositionLocalContext) {
CompositionLocalProvider(sheetCompositionLocalContext) {
if (!adaptiveSheetState.skipPartiallyExpanded) {
var update by remember { mutableIntStateOf(0) }

LaunchedEffect(Unit) {
while (true) {
withFrameMillis {
update++
}
}
}

@Suppress("UNUSED_EXPRESSION")
update
}

Column(
modifier = modifier.fillMaxSize(),
modifier = modifier,
content = content,
)
}
Expand All @@ -74,7 +94,7 @@ actual fun AdaptiveBottomSheet(
}
)
} else {
sheetManager.show()
sheetManager.show(adaptiveSheetState.skipPartiallyExpanded)
}
}

Expand All @@ -84,4 +104,4 @@ actual fun AdaptiveBottomSheet(
adaptiveSheetState.sheetValue = SheetValue.Hidden
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,15 @@ package com.mohamedrejeb.calf.ui.sheet
import androidx.compose.runtime.Composable
import androidx.compose.ui.window.ComposeUIViewController
import com.mohamedrejeb.calf.ui.utils.applyTheme
import kotlinx.cinterop.CValue
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.cValue
import platform.CoreGraphics.CGFloat
import platform.CoreGraphics.CGRect
import platform.CoreGraphics.CGRectGetMaxY
import platform.CoreGraphics.CGRectGetMinX
import platform.CoreGraphics.CGRectGetWidth
import platform.CoreGraphics.CGRectMake
import platform.CoreGraphics.CGRectZero
import platform.UIKit.UIAdaptivePresentationControllerDelegateProtocol
import platform.UIKit.UIApplication
import platform.UIKit.UIEdgeInsetsInsetRect
import platform.UIKit.UIModalPresentationPopover
import platform.UIKit.UIModalPresentationPageSheet
import platform.UIKit.UIModalTransitionStyleCoverVertical
import platform.UIKit.UIPresentationController
import platform.UIKit.UISheetPresentationControllerDetent
import platform.UIKit.UIViewController
import platform.UIKit.UIViewControllerTransitioningDelegateProtocol
import platform.UIKit.presentationController
import platform.UIKit.sheetPresentationController
import platform.UIKit.transitioningDelegate
import platform.darwin.NSObject

/**
Expand All @@ -33,6 +21,7 @@ import platform.darwin.NSObject
* @param content The Compose content of the bottom sheet.
*/
internal class BottomSheetManager(
private val parentUIViewController: UIViewController,
dark: Boolean,
private val onDismiss: () -> Unit,
private val content: @Composable () -> Unit
Expand All @@ -47,56 +36,64 @@ internal class BottomSheetManager(
*/
private var isAnimating = false

private var isDark = dark

/**
* The ui view controller that is used to present the bottom sheet.
* The presentation controller delegate that is used to detect when the bottom sheet is dismissed.
*/
private val bottomSheetUIViewController = ComposeUIViewController {
content()
private val presentationControllerDelegate by lazy {
BottomSheetControllerDelegate(
onDismiss = {
isPresented = false
onDismiss()
}
)
}

private val bottomSheetTransitioningDelegate = BottomSheetTransitioningDelegate()

/**
* The presentation controller delegate that is used to detect when the bottom sheet is dismissed.
* The ui view controller that is used to present the bottom sheet.
*/
private val presentationControllerDelegate = BottomSheetControllerDelegate(
onDismiss = {
isPresented = false
onDismiss()
private val bottomSheetUIViewController: UIViewController by lazy {
ComposeUIViewController(content).apply {
modalPresentationStyle = UIModalPresentationPageSheet
modalTransitionStyle = UIModalTransitionStyleCoverVertical
presentationController?.delegate = presentationControllerDelegate
applyTheme(isDark)
}
)

init {
applyTheme(dark)
}

fun applyTheme(dark: Boolean) {
bottomSheetUIViewController.applyTheme(dark)
isDark = dark

if (isPresented)
bottomSheetUIViewController.applyTheme(dark)
}

/**
* Shows the bottom sheet.
*/
fun show() {
fun show(
skipPartiallyExpanded: Boolean,
) {
applyTheme(isDark)

if (isPresented || isAnimating) return
isAnimating = true

bottomSheetUIViewController.modalPresentationStyle = UIModalPresentationPopover
bottomSheetUIViewController.transitioningDelegate = bottomSheetTransitioningDelegate
bottomSheetUIViewController.presentationController?.setDelegate(presentationControllerDelegate)

bottomSheetUIViewController.sheetPresentationController?.setDetents(
listOf(
UISheetPresentationControllerDetent.mediumDetent(),
UISheetPresentationControllerDetent.largeDetent()
)
if (skipPartiallyExpanded)
listOf(
UISheetPresentationControllerDetent.largeDetent()
)
else
listOf(
UISheetPresentationControllerDetent.largeDetent(),
UISheetPresentationControllerDetent.mediumDetent(),
)
)
bottomSheetUIViewController.sheetPresentationController?.prefersGrabberVisible = true

println(bottomSheetUIViewController.sheetPresentationController)
println(bottomSheetUIViewController.sheetPresentationController?.detents)

UIApplication.sharedApplication.keyWindow?.rootViewController?.presentViewController(
parentUIViewController.presentViewController(
viewControllerToPresent = bottomSheetUIViewController,
animated = true,
completion = {
Expand Down Expand Up @@ -133,46 +130,9 @@ class BottomSheetControllerDelegate(
override fun presentationControllerShouldDismiss(presentationController: UIPresentationController): Boolean {
return true
}

override fun presentationControllerDidDismiss(presentationController: UIPresentationController) {
onDismiss()
}
}

class BottomSheetTransitioningDelegate : NSObject(), UIViewControllerTransitioningDelegateProtocol {
override fun presentationControllerForPresentedViewController(
presented: UIViewController,
presentingViewController: UIViewController?,
sourceViewController: UIViewController
): UIPresentationController {
return BottomSheetPresentationController(
presented,
presentingViewController,
)
}
}

class BottomSheetPresentationController(
presentedViewController: UIViewController,
presentingViewController: UIViewController?
): UIPresentationController(
presentedViewController = presentedViewController,
presentingViewController = presentingViewController,
) {
@OptIn(ExperimentalForeignApi::class)
override fun frameOfPresentedViewInContainerView(): CValue<CGRect> {
val containerView = containerView ?: return cValue { CGRectZero }
val safeAreaInsets = containerView.safeAreaInsets

val safeAreaFrame = UIEdgeInsetsInsetRect(containerView.bounds, safeAreaInsets)
val presentedHeight: CGFloat = 300.0

return cValue {
CGRectMake(
x = CGRectGetMinX(safeAreaFrame),
y = CGRectGetMaxY(safeAreaFrame) - presentedHeight,
width = CGRectGetWidth(safeAreaFrame),
height = presentedHeight
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.Density
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlin.concurrent.Volatile
Expand All @@ -15,7 +16,8 @@ import kotlin.concurrent.Volatile
@Stable
actual class AdaptiveSheetState @OptIn(ExperimentalMaterial3Api::class)
actual constructor(
skipPartiallyExpanded: Boolean,
internal val skipPartiallyExpanded: Boolean,
density: Density,
initialValue: SheetValue,
confirmValueChange: (SheetValue) -> Boolean,
skipHiddenState: Boolean,
Expand Down Expand Up @@ -67,11 +69,12 @@ actual constructor(
*/
actual fun Saver(
skipPartiallyExpanded: Boolean,
confirmValueChange: (SheetValue) -> Boolean
confirmValueChange: (SheetValue) -> Boolean,
density: Density
) = Saver<AdaptiveSheetState, SheetValue>(
save = { it.currentValue },
restore = { savedValue ->
AdaptiveSheetState(skipPartiallyExpanded, savedValue, confirmValueChange, false)
AdaptiveSheetState(skipPartiallyExpanded, density, savedValue, confirmValueChange, false)
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,9 @@ actual fun AdaptiveBottomSheet(
contentColor: Color,
tonalElevation: Dp,
scrimColor: Color,
dragHandle:
@Composable()
(() -> Unit)?,
dragHandle: @Composable (() -> Unit)?,
windowInsets: WindowInsets,
content:
@Composable()
(ColumnScope.() -> Unit),
content: @Composable (ColumnScope.() -> Unit),
) {
ModalBottomSheet(
onDismissRequest = onDismissRequest,
Expand Down
Loading

0 comments on commit f4d41ce

Please sign in to comment.