Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 191 lines 5.0 kB view raw
1import * as React from 'react' 2import { 3 Dimensions, 4 type LayoutChangeEvent, 5 type NativeSyntheticEvent, 6 Platform, 7 type StyleProp, 8 useWindowDimensions, 9 View, 10 type ViewStyle, 11} from 'react-native' 12import {useSafeAreaInsets} from 'react-native-safe-area-context' 13import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core' 14 15import {IS_IOS} from '#/env' 16import { 17 type BottomSheetState, 18 type BottomSheetViewProps, 19} from './BottomSheet.types' 20import { 21 BottomSheetPortalProvider, 22 Context as PortalContext, 23} from './BottomSheetPortal' 24 25const NativeView: React.ComponentType< 26 BottomSheetViewProps & { 27 ref: React.RefObject<any> 28 style: StyleProp<ViewStyle> 29 } 30> = requireNativeViewManager('BottomSheet') 31 32const NativeModule = requireNativeModule('BottomSheet') 33 34const IS_IOS15 = 35 Platform.OS === 'ios' && 36 // semvar - can be 3 segments, so can't use Number(Platform.Version) 37 Number(Platform.Version.split('.').at(0)) < 16 38 39export class BottomSheetNativeComponent extends React.Component< 40 BottomSheetViewProps, 41 { 42 open: boolean 43 viewHeight?: number 44 } 45> { 46 ref = React.createRef<any>() 47 48 static contextType = PortalContext 49 50 constructor(props: BottomSheetViewProps) { 51 super(props) 52 this.state = { 53 open: false, 54 } 55 } 56 57 present() { 58 this.setState({open: true}) 59 } 60 61 dismiss() { 62 this.ref.current?.dismiss() 63 } 64 65 private onStateChange = ( 66 event: NativeSyntheticEvent<{state: BottomSheetState}>, 67 ) => { 68 const {state} = event.nativeEvent 69 const isOpen = state !== 'closed' 70 this.setState({open: isOpen}) 71 this.props.onStateChange?.(event) 72 } 73 74 private updateLayout = () => { 75 this.ref.current?.updateLayout() 76 } 77 78 static dismissAll = async () => { 79 await NativeModule.dismissAll() 80 } 81 82 render() { 83 const Portal = this.context as React.ContextType<typeof PortalContext> 84 if (!Portal) { 85 throw new Error( 86 'BottomSheet: You need to wrap your component tree with a <BottomSheetPortalProvider> to use the bottom sheet.', 87 ) 88 } 89 90 if (!this.state.open) { 91 return null 92 } 93 94 let extraStyles 95 if (IS_IOS15 && this.state.viewHeight) { 96 const screenHeight = Dimensions.get('screen').height 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 const {height: screenHeight} = useWindowDimensions() 158 159 const sheetHeight = IS_IOS ? screenHeight - insets.top : screenHeight 160 161 return ( 162 <NativeView 163 {...rest} 164 onStateChange={onStateChange} 165 ref={nativeViewRef} 166 style={{ 167 position: 'absolute', 168 height: sheetHeight, 169 width: '100%', 170 }} 171 containerBackgroundColor={backgroundColor}> 172 <View 173 style={[ 174 { 175 flex: 1, 176 backgroundColor, 177 }, 178 Platform.OS === 'android' && { 179 borderTopLeftRadius: cornerRadius, 180 borderTopRightRadius: cornerRadius, 181 overflow: 'hidden', 182 }, 183 extraStyles, 184 ]}> 185 <View onLayout={onLayout}> 186 <BottomSheetPortalProvider>{children}</BottomSheetPortalProvider> 187 </View> 188 </View> 189 </NativeView> 190 ) 191}