Bluesky app fork with some witchin' additions 馃挮
at main 190 lines 4.9 kB view raw
1import * as React from 'react' 2import { 3 Dimensions, 4 type LayoutChangeEvent, 5 type NativeSyntheticEvent, 6 Platform, 7 type StyleProp, 8 View, 9 type ViewStyle, 10} from 'react-native' 11import {useSafeAreaInsets} from 'react-native-safe-area-context' 12import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core' 13 14import {IS_IOS} from '#/env' 15import { 16 type BottomSheetState, 17 type BottomSheetViewProps, 18} from './BottomSheet.types' 19import { 20 BottomSheetPortalProvider, 21 Context as PortalContext, 22} from './BottomSheetPortal' 23 24const screenHeight = Dimensions.get('screen').height 25 26const NativeView: React.ComponentType< 27 BottomSheetViewProps & { 28 ref: React.RefObject<any> 29 style: StyleProp<ViewStyle> 30 } 31> = requireNativeViewManager('BottomSheet') 32 33const NativeModule = requireNativeModule('BottomSheet') 34 35const IS_IOS15 = 36 Platform.OS === 'ios' && 37 // semvar - can be 3 segments, so can't use Number(Platform.Version) 38 Number(Platform.Version.split('.').at(0)) < 16 39 40export class BottomSheetNativeComponent extends React.Component< 41 BottomSheetViewProps, 42 { 43 open: boolean 44 viewHeight?: number 45 } 46> { 47 ref = React.createRef<any>() 48 49 static contextType = PortalContext 50 51 constructor(props: BottomSheetViewProps) { 52 super(props) 53 this.state = { 54 open: false, 55 } 56 } 57 58 present() { 59 this.setState({open: true}) 60 } 61 62 dismiss() { 63 this.ref.current?.dismiss() 64 } 65 66 private onStateChange = ( 67 event: NativeSyntheticEvent<{state: BottomSheetState}>, 68 ) => { 69 const {state} = event.nativeEvent 70 const isOpen = state !== 'closed' 71 this.setState({open: isOpen}) 72 this.props.onStateChange?.(event) 73 } 74 75 private updateLayout = () => { 76 this.ref.current?.updateLayout() 77 } 78 79 static dismissAll = async () => { 80 await NativeModule.dismissAll() 81 } 82 83 render() { 84 const Portal = this.context as React.ContextType<typeof PortalContext> 85 if (!Portal) { 86 throw new Error( 87 'BottomSheet: You need to wrap your component tree with a <BottomSheetPortalProvider> to use the bottom sheet.', 88 ) 89 } 90 91 if (!this.state.open) { 92 return null 93 } 94 95 let extraStyles 96 if (IS_IOS15 && this.state.viewHeight) { 97 const {viewHeight} = this.state 98 const cornerRadius = this.props.cornerRadius ?? 0 99 if (viewHeight < screenHeight / 2) { 100 extraStyles = { 101 height: viewHeight, 102 marginTop: screenHeight / 2 - viewHeight, 103 borderTopLeftRadius: cornerRadius, 104 borderTopRightRadius: cornerRadius, 105 } 106 } 107 } 108 109 return ( 110 <Portal> 111 <BottomSheetNativeComponentInner 112 {...this.props} 113 nativeViewRef={this.ref} 114 onStateChange={this.onStateChange} 115 extraStyles={extraStyles} 116 onLayout={e => { 117 if (IS_IOS15) { 118 const {height} = e.nativeEvent.layout 119 this.setState({viewHeight: height}) 120 } 121 if (Platform.OS === 'android') { 122 // TEMP HACKFIX: I had to timebox this, but this is Bad. 123 // On Android, if you run updateLayout() immediately, 124 // it will take ages to actually run on the native side. 125 // However, adding literally any delay will fix this, including 126 // a console.log() - just sending the log to the CLI is enough. 127 // TODO: Get to the bottom of this and fix it properly! -sfn 128 setTimeout(() => this.updateLayout()) 129 } else { 130 this.updateLayout() 131 } 132 }} 133 /> 134 </Portal> 135 ) 136 } 137} 138 139function BottomSheetNativeComponentInner({ 140 children, 141 backgroundColor, 142 onLayout, 143 onStateChange, 144 nativeViewRef, 145 extraStyles, 146 ...rest 147}: BottomSheetViewProps & { 148 extraStyles?: StyleProp<ViewStyle> 149 onStateChange: ( 150 event: NativeSyntheticEvent<{state: BottomSheetState}>, 151 ) => void 152 nativeViewRef: React.RefObject<View> 153 onLayout: (event: LayoutChangeEvent) => void 154}) { 155 const insets = useSafeAreaInsets() 156 const cornerRadius = rest.cornerRadius ?? 0 157 158 const sheetHeight = IS_IOS ? screenHeight - insets.top : screenHeight 159 160 return ( 161 <NativeView 162 {...rest} 163 onStateChange={onStateChange} 164 ref={nativeViewRef} 165 style={{ 166 position: 'absolute', 167 height: sheetHeight, 168 width: '100%', 169 }} 170 containerBackgroundColor={backgroundColor}> 171 <View 172 style={[ 173 { 174 flex: 1, 175 backgroundColor, 176 }, 177 Platform.OS === 'android' && { 178 borderTopLeftRadius: cornerRadius, 179 borderTopRightRadius: cornerRadius, 180 overflow: 'hidden', 181 }, 182 extraStyles, 183 ]}> 184 <View onLayout={onLayout}> 185 <BottomSheetPortalProvider>{children}</BottomSheetPortalProvider> 186 </View> 187 </View> 188 </NativeView> 189 ) 190}