Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package androidx.compose.ui.platform

import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.geometry.Rect
Expand All @@ -33,7 +32,7 @@ import platform.UIKit.UIUserInterfaceIdiomPad
import platform.UIKit.UIView

internal class UIKitWindowInsetsManager(
val windowInsetsView: () -> UIView?,
val windowInsetsViews: List<() -> UIView?>,
val interfaceOrientation: State<InterfaceOrientation>,
userInterfaceIdiom: UIUserInterfaceIdiom = UIDevice.currentDevice.userInterfaceIdiom
) {
Expand Down Expand Up @@ -84,11 +83,10 @@ internal class UIKitWindowInsetsManager(
)
}

private fun CMPLayoutRegion.toPlatformInsets(): PlatformInsets {
val view = windowInsetsView() ?: return PlatformInsets.Zero

return edgeInsetsInView(view).toPlatformInsets(view.density)
}
private fun CMPLayoutRegion.toPlatformInsets() = windowInsetsViews
.mapNotNull { it() }
.map { edgeInsetsInView(it).toPlatformInsets(it.density) }
.union()

internal class UIKitWindowInsetsSnapshot(
val layoutMargins: PlatformInsets,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,10 @@ internal class ComposeSceneMediator(
)

private val windowInsetsManager = UIKitWindowInsetsManager(
windowInsetsView = { windowContext.window?.rootViewController?.view },
windowInsetsViews = listOf(
{ _overlayView },
{ windowContext.window?.rootViewController?.view },
),
interfaceOrientation = interfaceOrientationState
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ internal fun PlatformInsets.union(insets: PlatformInsets) = PlatformInsets(
getBottom = { maxOf(bottom, insets.bottom) }
)

internal fun List<PlatformInsets>.union(): PlatformInsets = if (isEmpty()) {
PlatformInsets.Zero
} else {
reduce { acc, insets -> acc.union(insets) }
}

private class DynamicPlatformInsets(
private val getLeft: () -> Int = { 0 },
private val getTop: () -> Int = { 0 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.systemGesturesPadding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.Text
Expand All @@ -36,6 +38,7 @@ 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.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
Expand All @@ -48,10 +51,13 @@ import androidx.compose.ui.unit.DpRect
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toDpRect
import androidx.compose.ui.viewinterop.UIKitView
import androidx.compose.ui.window.ComposeUIView
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.useContents
import platform.UIKit.UIColor
import platform.UIKit.UIInterfaceOrientationLandscapeLeft
import platform.UIKit.UIInterfaceOrientationLandscapeRight
import platform.UIKit.UIInterfaceOrientationPortrait
Expand Down Expand Up @@ -199,6 +205,98 @@ class WindowInsetsPaddingTest {

assertEquals(false, recomposed.value)
}

@OptIn(ExperimentalForeignApi::class)
@Test
fun testWindowInsetsPaddingAppliedToNonFullscreenContent() = runUIKitInstrumentedTest {
var innerBoxRect = DpRectZero()
var outerBoxRect = DpRectZero()

setContent {
Box(modifier = Modifier.background(Color.Red).fillMaxSize()) {
Box(
modifier = Modifier
.align(Alignment.Center)
.background(Color.Blue)
.size(200.dp, 200.dp)
.onGloballyPositioned {
outerBoxRect = it.boundsInWindow().toDpRect(density)
},
) {
Box(modifier = Modifier
.windowInsetsPadding(WindowInsets.statusBars)
.background(Color.Green)
.fillMaxSize()
.onGloballyPositioned {
innerBoxRect = it.boundsInWindow().toDpRect(density)
}
)
}
}
}

// WindowInsets.statusBars should only represent the insets at the top in portrait orientation
val topSafeAreaInsetsDp = viewController.view.safeAreaInsets.useContents { top }.dp

assertEquals(
DpRect(
left = outerBoxRect.left,
top = outerBoxRect.top + topSafeAreaInsetsDp,
right = outerBoxRect.right,
bottom = outerBoxRect.bottom
),
innerBoxRect
)
}

@OptIn(ExperimentalForeignApi::class)
@Test
fun testWindowInsetsPaddingAppliedToNonFullscreenComposeUIViewContent() = runUIKitInstrumentedTest {
var innerBoxRect = DpRectZero()
var outerBoxRect = DpRectZero()

setContent {
Box(modifier = Modifier.background(Color.Red).fillMaxSize()) {
UIKitView(
factory = {
ComposeUIView(
configure = { opaque = false }
) {
Box(modifier = Modifier
.windowInsetsPadding(WindowInsets.statusBars)
.background(Color.Green)
.fillMaxSize()
.onGloballyPositioned {
innerBoxRect = it.boundsInWindow().toDpRect(density)
}
)
}.apply {
backgroundColor = UIColor.blueColor
}
},
modifier = Modifier
.align(Alignment.Center)
.size(200.dp, 200.dp)
.onGloballyPositioned {
outerBoxRect = it.boundsInWindow().toDpRect(density)
}
)
}
}

// WindowInsets.statusBars should only represent the insets at the top in portrait orientation
val topSafeAreaInsetsDp = viewController.view.safeAreaInsets.useContents { top }.dp

assertEquals(
DpRect(
left = outerBoxRect.left,
top = outerBoxRect.top + topSafeAreaInsetsDp,
right = outerBoxRect.right,
bottom = outerBoxRect.bottom
),
innerBoxRect
)
}
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import androidx.compose.ui.unit.asDpOffset
import androidx.compose.ui.unit.asDpRect
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
import androidx.compose.ui.unit.size
import androidx.compose.ui.window.KeyboardVisibilityListener
import androidx.compose.ui.window.MetalRedrawer
import kotlin.coroutines.cancellation.CancellationException
Expand Down Expand Up @@ -238,8 +239,9 @@ internal class UIKitInstrumentedTest(
val appDelegate = MockAppDelegate()
val keyboardHeight: Dp get() =
KeyboardVisibilityListener.keyboardFrame.useContents { size.height.dp }
val screenSize: DpSize get() = screen.bounds().useContents { DpSize(size.width.dp, size.height.dp) }
val safeDrawingRect: DpRect get() = screen.bounds().asDpRect().let { rect ->
val screenBounds: DpRect get() = screen.bounds().asDpRect()
val screenSize: DpSize get() = screenBounds.size
val safeDrawingRect: DpRect get() = screenBounds.let { rect ->
viewController.view.safeAreaInsets.useContents {
DpRect(
left = rect.left + Dp(this.left.toFloat()),
Expand Down
Loading