Get sheet padding working consistently (#7798)
* tweak height/padding of iOS * tweak android ratio calculation * add a bit of extra padding to full height iOS to account for the bit below the safe area
This commit is contained in:
+28
-11
@@ -31,9 +31,13 @@ class BottomSheetView(
|
|||||||
private lateinit var dialogRootViewGroup: DialogRootViewGroup
|
private lateinit var dialogRootViewGroup: DialogRootViewGroup
|
||||||
private var eventDispatcher: EventDispatcher? = null
|
private var eventDispatcher: EventDispatcher? = null
|
||||||
|
|
||||||
private val screenHeight =
|
private val rawScreenHeight = context.resources.displayMetrics.heightPixels.toFloat()
|
||||||
context.resources.displayMetrics.heightPixels
|
private val safeScreenHeight = (rawScreenHeight - getNavigationBarHeight()).toFloat()
|
||||||
.toFloat()
|
|
||||||
|
private fun getNavigationBarHeight(): Int {
|
||||||
|
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
|
||||||
|
return if (resourceId > 0) resources.getDimensionPixelSize(resourceId) else 0
|
||||||
|
}
|
||||||
|
|
||||||
private val onAttemptDismiss by EventDispatcher()
|
private val onAttemptDismiss by EventDispatcher()
|
||||||
private val onSnapPointChange by EventDispatcher()
|
private val onSnapPointChange by EventDispatcher()
|
||||||
@@ -63,12 +67,12 @@ class BottomSheetView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxHeight = this.screenHeight
|
var maxHeight = this.safeScreenHeight
|
||||||
set(value) {
|
set(value) {
|
||||||
val px = dpToPx(value)
|
val px = dpToPx(value)
|
||||||
field =
|
field =
|
||||||
if (px > this.screenHeight) {
|
if (px > this.safeScreenHeight) {
|
||||||
this.screenHeight
|
this.safeScreenHeight
|
||||||
} else {
|
} else {
|
||||||
px
|
px
|
||||||
}
|
}
|
||||||
@@ -153,6 +157,19 @@ class BottomSheetView(
|
|||||||
|
|
||||||
// Presentation
|
// Presentation
|
||||||
|
|
||||||
|
private fun getHalfExpandedRatio(contentHeight: Float): Float {
|
||||||
|
return when {
|
||||||
|
// Full height sheets
|
||||||
|
contentHeight >= safeScreenHeight -> 0.99f
|
||||||
|
// Medium height sheets (>50% but <100%)
|
||||||
|
contentHeight >= safeScreenHeight / 2 ->
|
||||||
|
this.clampRatio(this.getTargetHeight() / safeScreenHeight)
|
||||||
|
// Small height sheets (<50%)
|
||||||
|
else ->
|
||||||
|
this.clampRatio(this.getTargetHeight() / rawScreenHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun present() {
|
private fun present() {
|
||||||
if (this.isOpen || this.isOpening || this.isClosing) return
|
if (this.isOpen || this.isOpening || this.isClosing) return
|
||||||
|
|
||||||
@@ -172,12 +189,12 @@ class BottomSheetView(
|
|||||||
val behavior = BottomSheetBehavior.from(it)
|
val behavior = BottomSheetBehavior.from(it)
|
||||||
behavior.state = BottomSheetBehavior.STATE_HIDDEN
|
behavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||||
behavior.isFitToContents = true
|
behavior.isFitToContents = true
|
||||||
behavior.halfExpandedRatio = this.clampRatio(this.getTargetHeight() / this.screenHeight)
|
behavior.halfExpandedRatio = getHalfExpandedRatio(contentHeight)
|
||||||
behavior.skipCollapsed = true
|
behavior.skipCollapsed = true
|
||||||
behavior.isDraggable = true
|
behavior.isDraggable = true
|
||||||
behavior.isHideable = true
|
behavior.isHideable = true
|
||||||
|
|
||||||
if (contentHeight >= this.screenHeight || this.minHeight >= this.screenHeight) {
|
if (contentHeight >= this.safeScreenHeight || this.minHeight >= this.safeScreenHeight) {
|
||||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
this.selectedSnapPoint = 2
|
this.selectedSnapPoint = 2
|
||||||
} else {
|
} else {
|
||||||
@@ -227,11 +244,11 @@ class BottomSheetView(
|
|||||||
bottomSheet?.let {
|
bottomSheet?.let {
|
||||||
val behavior = BottomSheetBehavior.from(it)
|
val behavior = BottomSheetBehavior.from(it)
|
||||||
|
|
||||||
behavior.halfExpandedRatio = this.clampRatio(this.getTargetHeight() / this.screenHeight)
|
behavior.halfExpandedRatio = getHalfExpandedRatio(contentHeight)
|
||||||
|
|
||||||
if (contentHeight > this.screenHeight && behavior.state != BottomSheetBehavior.STATE_EXPANDED) {
|
if (contentHeight > this.safeScreenHeight && behavior.state != BottomSheetBehavior.STATE_EXPANDED) {
|
||||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
} else if (contentHeight < this.screenHeight && behavior.state != BottomSheetBehavior.STATE_HALF_EXPANDED) {
|
} else if (contentHeight < this.safeScreenHeight && behavior.state != BottomSheetBehavior.STATE_HALF_EXPANDED) {
|
||||||
behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
|
behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import {
|
import {
|
||||||
Dimensions,
|
Dimensions,
|
||||||
|
LayoutChangeEvent,
|
||||||
NativeSyntheticEvent,
|
NativeSyntheticEvent,
|
||||||
Platform,
|
Platform,
|
||||||
StyleProp,
|
StyleProp,
|
||||||
View,
|
View,
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core'
|
import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core'
|
||||||
|
|
||||||
|
import {isIOS} from '#/platform/detection'
|
||||||
import {BottomSheetState, BottomSheetViewProps} from './BottomSheet.types'
|
import {BottomSheetState, BottomSheetViewProps} from './BottomSheet.types'
|
||||||
import {BottomSheetPortalProvider} from './BottomSheetPortal'
|
import {BottomSheetPortalProvider} from './BottomSheetPortal'
|
||||||
import {Context as PortalContext} from './BottomSheetPortal'
|
import {Context as PortalContext} from './BottomSheetPortal'
|
||||||
@@ -81,12 +84,10 @@ export class BottomSheetNativeComponent extends React.Component<
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const {children, backgroundColor, ...rest} = this.props
|
|
||||||
const cornerRadius = rest.cornerRadius ?? 0
|
|
||||||
|
|
||||||
let extraStyles
|
let extraStyles
|
||||||
if (isIOS15 && this.state.viewHeight) {
|
if (isIOS15 && this.state.viewHeight) {
|
||||||
const {viewHeight} = this.state
|
const {viewHeight} = this.state
|
||||||
|
const cornerRadius = this.props.cornerRadius ?? 0
|
||||||
if (viewHeight < screenHeight / 2) {
|
if (viewHeight < screenHeight / 2) {
|
||||||
extraStyles = {
|
extraStyles = {
|
||||||
height: viewHeight,
|
height: viewHeight,
|
||||||
@@ -99,39 +100,70 @@ export class BottomSheetNativeComponent extends React.Component<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<NativeView
|
<BottomSheetNativeComponentInner
|
||||||
{...rest}
|
{...this.props}
|
||||||
|
nativeViewRef={this.ref}
|
||||||
onStateChange={this.onStateChange}
|
onStateChange={this.onStateChange}
|
||||||
ref={this.ref}
|
extraStyles={extraStyles}
|
||||||
style={{
|
onLayout={e => {
|
||||||
position: 'absolute',
|
const {height} = e.nativeEvent.layout
|
||||||
height: screenHeight,
|
this.setState({viewHeight: height})
|
||||||
width: '100%',
|
this.updateLayout()
|
||||||
}}
|
}}
|
||||||
containerBackgroundColor={backgroundColor}>
|
/>
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
{
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor,
|
|
||||||
},
|
|
||||||
Platform.OS === 'android' && {
|
|
||||||
borderTopLeftRadius: cornerRadius,
|
|
||||||
borderTopRightRadius: cornerRadius,
|
|
||||||
},
|
|
||||||
extraStyles,
|
|
||||||
]}>
|
|
||||||
<View
|
|
||||||
onLayout={e => {
|
|
||||||
const {height} = e.nativeEvent.layout
|
|
||||||
this.setState({viewHeight: height})
|
|
||||||
this.updateLayout()
|
|
||||||
}}>
|
|
||||||
<BottomSheetPortalProvider>{children}</BottomSheetPortalProvider>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</NativeView>
|
|
||||||
</Portal>
|
</Portal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function BottomSheetNativeComponentInner({
|
||||||
|
children,
|
||||||
|
backgroundColor,
|
||||||
|
onLayout,
|
||||||
|
onStateChange,
|
||||||
|
nativeViewRef,
|
||||||
|
extraStyles,
|
||||||
|
...rest
|
||||||
|
}: BottomSheetViewProps & {
|
||||||
|
extraStyles?: StyleProp<ViewStyle>
|
||||||
|
onStateChange: (
|
||||||
|
event: NativeSyntheticEvent<{state: BottomSheetState}>,
|
||||||
|
) => void
|
||||||
|
nativeViewRef: React.RefObject<View>
|
||||||
|
onLayout: (event: LayoutChangeEvent) => void
|
||||||
|
}) {
|
||||||
|
const insets = useSafeAreaInsets()
|
||||||
|
const cornerRadius = rest.cornerRadius ?? 0
|
||||||
|
|
||||||
|
const sheetHeight = isIOS ? screenHeight - insets.top : screenHeight
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NativeView
|
||||||
|
{...rest}
|
||||||
|
onStateChange={onStateChange}
|
||||||
|
ref={nativeViewRef}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
height: sheetHeight,
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
containerBackgroundColor={backgroundColor}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor,
|
||||||
|
},
|
||||||
|
Platform.OS === 'android' && {
|
||||||
|
borderTopLeftRadius: cornerRadius,
|
||||||
|
borderTopRightRadius: cornerRadius,
|
||||||
|
},
|
||||||
|
extraStyles,
|
||||||
|
]}>
|
||||||
|
<View onLayout={onLayout}>
|
||||||
|
<BottomSheetPortalProvider>{children}</BottomSheetPortalProvider>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</NativeView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {isAndroid, isIOS} from '#/platform/detection'
|
|||||||
import {useA11y} from '#/state/a11y'
|
import {useA11y} from '#/state/a11y'
|
||||||
import {useDialogStateControlContext} from '#/state/dialogs'
|
import {useDialogStateControlContext} from '#/state/dialogs'
|
||||||
import {List, ListMethods, ListProps} from '#/view/com/util/List'
|
import {List, ListMethods, ListProps} from '#/view/com/util/List'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, tokens, useTheme} from '#/alf'
|
||||||
import {useThemeName} from '#/alf/util/useColorModeTheme'
|
import {useThemeName} from '#/alf/util/useColorModeTheme'
|
||||||
import {Context, useDialogContext} from '#/components/Dialog/context'
|
import {Context, useDialogContext} from '#/components/Dialog/context'
|
||||||
import {
|
import {
|
||||||
@@ -46,7 +46,7 @@ export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
|||||||
export * from '#/components/Dialog/shared'
|
export * from '#/components/Dialog/shared'
|
||||||
export * from '#/components/Dialog/types'
|
export * from '#/components/Dialog/types'
|
||||||
export * from '#/components/Dialog/utils'
|
export * from '#/components/Dialog/utils'
|
||||||
// @ts-ignore
|
|
||||||
export const Input = createInput(TextInput)
|
export const Input = createInput(TextInput)
|
||||||
|
|
||||||
export function Outer({
|
export function Outer({
|
||||||
@@ -168,7 +168,9 @@ export function Outer({
|
|||||||
onStateChange={onStateChange}
|
onStateChange={onStateChange}
|
||||||
disableDrag={disableDrag}>
|
disableDrag={disableDrag}>
|
||||||
<Context.Provider value={context}>
|
<Context.Provider value={context}>
|
||||||
<View testID={testID}>{children}</View>
|
<View testID={testID} style={[a.relative]}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
</Context.Provider>
|
</Context.Provider>
|
||||||
</BottomSheet>
|
</BottomSheet>
|
||||||
)
|
)
|
||||||
@@ -196,7 +198,7 @@ export function Inner({children, style, header}: DialogInnerProps) {
|
|||||||
|
|
||||||
export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
||||||
function ScrollableInner(
|
function ScrollableInner(
|
||||||
{children, style, contentContainerStyle, header, ...props},
|
{children, contentContainerStyle, header, ...props},
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext()
|
const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext()
|
||||||
@@ -216,13 +218,21 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
|||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
|
||||||
const basePading =
|
let paddingBottom = 0
|
||||||
(isIOS ? 30 : 50) + (isIOS ? keyboardHeight / 4 : keyboardHeight)
|
if (isIOS) {
|
||||||
const fullPaddingBase = insets.bottom + insets.top + basePading
|
paddingBottom += keyboardHeight / 4
|
||||||
const fullPadding = isIOS ? fullPaddingBase : fullPaddingBase + 50
|
if (nativeSnapPoint === BottomSheetSnapPoint.Full) {
|
||||||
|
paddingBottom += insets.bottom + tokens.space.md
|
||||||
const paddingBottom =
|
}
|
||||||
nativeSnapPoint === BottomSheetSnapPoint.Full ? fullPadding : basePading
|
paddingBottom = Math.max(paddingBottom, tokens.space._2xl)
|
||||||
|
} else {
|
||||||
|
paddingBottom += keyboardHeight
|
||||||
|
if (nativeSnapPoint === BottomSheetSnapPoint.Full) {
|
||||||
|
paddingBottom += insets.top
|
||||||
|
}
|
||||||
|
paddingBottom +=
|
||||||
|
Math.max(insets.bottom, tokens.space._5xl) + tokens.space._2xl
|
||||||
|
}
|
||||||
|
|
||||||
const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
if (!isAndroid) {
|
if (!isAndroid) {
|
||||||
@@ -238,7 +248,6 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAwareScrollView
|
<KeyboardAwareScrollView
|
||||||
style={[style]}
|
|
||||||
contentContainerStyle={[
|
contentContainerStyle={[
|
||||||
a.pt_2xl,
|
a.pt_2xl,
|
||||||
a.px_xl,
|
a.px_xl,
|
||||||
@@ -316,7 +325,7 @@ export function Handle() {
|
|||||||
style={[
|
style={[
|
||||||
a.rounded_sm,
|
a.rounded_sm,
|
||||||
{
|
{
|
||||||
top: 10,
|
top: tokens.space._2xl / 2 - 2.5,
|
||||||
width: 35,
|
width: 35,
|
||||||
height: 5,
|
height: 5,
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export function Outer({
|
|||||||
<Dialog.Handle />
|
<Dialog.Handle />
|
||||||
{/* Re-wrap with context since Dialogs are portal-ed to root */}
|
{/* Re-wrap with context since Dialogs are portal-ed to root */}
|
||||||
<Context.Provider value={context}>
|
<Context.Provider value={context}>
|
||||||
<Dialog.ScrollableInner label={_(msg`Menu`)} style={[a.py_sm]}>
|
<Dialog.ScrollableInner label={_(msg`Menu`)}>
|
||||||
<View style={[a.gap_lg]}>
|
<View style={[a.gap_lg]}>
|
||||||
{children}
|
{children}
|
||||||
{isNative && showCancel && <Cancel />}
|
{isNative && showCancel && <Cancel />}
|
||||||
|
|||||||
Reference in New Issue
Block a user