import React, { PropsWithChildren, useEffect, useRef, useState } from 'react'; import { BackHandler, Dimensions, StyleSheet, ViewStyle } from 'react-native'; import Dayjs from 'dayjs'; import Animated, { cancelAnimation, useAnimatedStyle, useSharedValue, withTiming, } from 'react-native-reanimated'; import { AttachmentPickerProvider } from '../attachmentPickerContext/AttachmentPickerContext'; import { ImageGalleryProvider } from '../imageGalleryContext/ImageGalleryContext'; import { MessageOverlayProvider } from '../messageOverlayContext/MessageOverlayContext'; import { ThemeProvider } from '../themeContext/ThemeContext'; import { TranslationContextValue, TranslationProvider, } from '../translationContext/TranslationContext'; import { AttachmentPicker } from '../../components/AttachmentPicker/AttachmentPicker'; import { AttachmentPickerError as DefaultAttachmentPickerError } from '../../components/AttachmentPicker/components/AttachmentPickerError'; import { AttachmentPickerBottomSheetHandle as DefaultAttachmentPickerBottomSheetHandle } from '../../components/AttachmentPicker/components/AttachmentPickerBottomSheetHandle'; import { AttachmentPickerErrorImage as DefaultAttachmentPickerErrorImage } from '../../components/AttachmentPicker/components/AttachmentPickerErrorImage'; import { CameraSelectorIcon as DefaultCameraSelectorIcon } from '../../components/AttachmentPicker/components/CameraSelectorIcon'; import { FileSelectorIcon as DefaultFileSelectorIcon } from '../../components/AttachmentPicker/components/FileSelectorIcon'; import { ImageOverlaySelectedComponent as DefaultImageOverlaySelectedComponent } from '../../components/AttachmentPicker/components/ImageOverlaySelectedComponent'; import { ImageSelectorIcon as DefaultImageSelectorIcon } from '../../components/AttachmentPicker/components/ImageSelectorIcon'; import { ImageGallery } from '../../components/ImageGallery/ImageGallery'; import { MessageOverlay } from '../../components/MessageOverlay/MessageOverlay'; import { useStreami18n } from '../../hooks/useStreami18n'; import { BlurView } from '../../native'; import type BottomSheet from '@gorhom/bottom-sheet'; import { BlurType, OverlayContext, OverlayProviderProps } from './OverlayContext'; import type { DefaultAttachmentType, DefaultChannelType, DefaultCommandType, DefaultEventType, DefaultMessageType, DefaultReactionType, DefaultUserType, UnknownType, } from '../../types/types'; /** * - The highest level of these components is the `OverlayProvider`. The `OverlayProvider` allows users to interact with messages on long press above the underlying views, use the full screen image viewer, and use the `AttachmentPicker` as a keyboard-esk view. * Because these views must exist above all others `OverlayProvider` should wrap your navigation stack as well. Assuming [`React Navigation`](https://reactnavigation.org/) is being used, your highest level navigation stack should be wrapped in the provider: * * ```js * * * * * * * * ``` * * - Don't forget to check our cookbook section of [OverlayProvider](https://github.com/GetStream/stream-chat-react-native/wiki/Cookbook-v3.0#overlayprovider) * * - Also check the [visual component guide](https://github.com/GetStream/stream-chat-react-native/wiki/Cookbook-v3.0#custom-components), to learn about component customizations. * * @example ./OverlayProvider.md */ export const OverlayProvider = < At extends UnknownType = DefaultAttachmentType, Ch extends UnknownType = DefaultChannelType, Co extends string = DefaultCommandType, Ev extends UnknownType = DefaultEventType, Me extends UnknownType = DefaultMessageType, Re extends UnknownType = DefaultReactionType, Us extends UnknownType = DefaultUserType, >( props: PropsWithChildren>, ) => { const { AttachmentPickerBottomSheetHandle = DefaultAttachmentPickerBottomSheetHandle, attachmentPickerBottomSheetHandleHeight, attachmentPickerBottomSheetHeight, AttachmentPickerError = DefaultAttachmentPickerError, attachmentPickerErrorButtonText, AttachmentPickerErrorImage = DefaultAttachmentPickerErrorImage, attachmentPickerErrorText, attachmentSelectionBarHeight, bottomInset, CameraSelectorIcon = DefaultCameraSelectorIcon, children, closePicker = (ref) => { if (ref.current) { ref.current.close(); } }, FileSelectorIcon = DefaultFileSelectorIcon, i18nInstance, imageGalleryCustomComponents, imageGalleryGridHandleHeight, imageGalleryGridSnapPoints, ImageOverlaySelectedComponent = DefaultImageOverlaySelectedComponent, ImageSelectorIcon = DefaultImageSelectorIcon, MessageActions, numberOfAttachmentImagesToLoadPerCall, numberOfAttachmentPickerImageColumns, numberOfImageGalleryGridColumns, openPicker = (ref) => { if (ref.current) { ref.current.snapTo(0); } else { console.warn('bottom and top insets must be set for the image picker to work correctly'); } }, topInset, translucentStatusBar, OverlayReactionList, OverlayReactions, value, } = props; const attachmentPickerProps = { AttachmentPickerBottomSheetHandle, attachmentPickerBottomSheetHandleHeight, attachmentPickerBottomSheetHeight, AttachmentPickerError, attachmentPickerErrorButtonText, AttachmentPickerErrorImage, attachmentPickerErrorText, attachmentSelectionBarHeight, ImageOverlaySelectedComponent, numberOfAttachmentImagesToLoadPerCall, numberOfAttachmentPickerImageColumns, translucentStatusBar, }; const bottomSheetRef = useRef(null); const [translators, setTranslators] = useState({ t: (key: string) => key, tDateTimeParser: (input?: string | number | Date) => Dayjs(input), }); const [blurType, setBlurType] = useState(); const [overlay, setOverlay] = useState(value?.overlay || 'none'); const overlayOpacity = useSharedValue(0); const { height, width } = Dimensions.get('screen'); // Setup translators const loadingTranslators = useStreami18n({ i18nInstance, setTranslators }); useEffect(() => { const backAction = () => { if (overlay !== 'none') { setBlurType(undefined); setOverlay('none'); return true; } return false; }; const backHandler = BackHandler.addEventListener('hardwareBackPress', backAction); return () => backHandler.remove(); }, [overlay]); useEffect(() => { if (bottomSheetRef.current) { bottomSheetRef.current.close(); } cancelAnimation(overlayOpacity); if (overlay !== 'none') { overlayOpacity.value = withTiming(1); } else { overlayOpacity.value = withTiming(0); } }, [overlay]); // Setup translators useStreami18n({ i18nInstance, setTranslators }); const attachmentPickerContext = { attachmentPickerBottomSheetHeight, attachmentSelectionBarHeight, bottomInset, CameraSelectorIcon, closePicker: () => closePicker(bottomSheetRef), FileSelectorIcon, ImageSelectorIcon, openPicker: () => openPicker(bottomSheetRef), topInset, }; const overlayStyle = useAnimatedStyle( () => ({ opacity: overlayOpacity.value, }), [], ); const overlayContext = { overlay, setBlurType, setOverlay, style: value?.style, translucentStatusBar, }; if (loadingTranslators) return null; return ( > {children} MessageActions={MessageActions} overlayOpacity={overlayOpacity} OverlayReactionList={OverlayReactionList} OverlayReactions={OverlayReactions} visible={overlay === 'message'} /> imageGalleryCustomComponents={imageGalleryCustomComponents} imageGalleryGridHandleHeight={imageGalleryGridHandleHeight} imageGalleryGridSnapPoints={imageGalleryGridSnapPoints} numberOfImageGalleryGridColumns={numberOfImageGalleryGridColumns} overlayOpacity={overlayOpacity} visible={overlay === 'gallery'} /> ); };