// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {type ImageSource} from 'expo-image'; import React, {useMemo, useState} from 'react'; import {type ImageStyle, type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native'; import Animated, { interpolate, useAnimatedStyle, withTiming, type AnimatedStyle, } from 'react-native-reanimated'; import {scheduleOnRN, scheduleOnUI} from 'react-native-worklets'; import {ExpoImageAnimated} from '@components/expo_image'; import useDidMount from '@hooks/did_mount'; import {calculateDimensions} from '@utils/images'; import {pagerTimingConfig} from '../animation_config/timing'; import {useLightboxSharedValues} from './context'; import type {BackdropProps} from './backdrop'; import type {GalleryManagerSharedValues} from '@typings/screens/gallery'; export interface RenderItemInfo { source: ImageSource; width: number; height: number; itemStyles: ViewStyle | ImageStyle | StyleProp>>; } interface LightboxProps { children: React.ReactNode; renderBackdropComponent?: (info: BackdropProps) => React.JSX.Element; renderItem: (info: RenderItemInfo) => React.ReactNode; sharedValues: GalleryManagerSharedValues; source: ImageSource | string; } const styles = StyleSheet.create({ container: { flex: 1, }, }); export default function Lightbox({ children, renderBackdropComponent, renderItem, sharedValues, source, }: LightboxProps) { const imageSource: ImageSource = typeof source === 'string' ? {uri: source} : source; const {x, y, width, height} = sharedValues; const { animationProgress, childTranslateY, translateX, translateY, scale, imageOpacity, childrenOpacity, opacity, target, targetDimensions, } = useLightboxSharedValues(); const [renderChildren, setRenderChildren] = useState(false); const childLayoutTimeoutRef = React.useRef(undefined); const animateOnMount = () => { 'worklet'; requestAnimationFrame(() => { opacity.value = 0; }); animationProgress.value = withTiming(1, pagerTimingConfig, () => { 'worklet'; childrenOpacity.value = 1; scheduleOnRN(setRenderChildren, true); scheduleOnRN(onChildrenLayout); }); }; useDidMount(() => { scheduleOnUI(animateOnMount); return () => { if (childLayoutTimeoutRef.current) { clearTimeout(childLayoutTimeoutRef.current); childLayoutTimeoutRef.current = undefined; } }; }); const {width: tw, height: th} = useMemo(() => calculateDimensions( target.height, target.width, targetDimensions.width, targetDimensions.height, true, ), [targetDimensions.width, targetDimensions.height, target.width, target.height]); function onChildrenLayout() { if (imageOpacity.value === 0) { return; } childLayoutTimeoutRef.current = setTimeout(() => { imageOpacity.value = withTiming(0, {duration: 300}); }, 300); } const childrenAnimateStyle = useAnimatedStyle( () => ({ opacity: childrenOpacity.value, transform: [{translateY: childTranslateY.value}], }), [], ); const backdropStyles = useAnimatedStyle(() => { return { position: 'absolute', top: 0, bottom: 0, left: 0, right: 0, backgroundColor: 'black', opacity: animationProgress.value, }; }); const itemStyles = useAnimatedStyle(() => { const interpolateProgress = (range: [number, number]) => interpolate(animationProgress.value, [0, 1], range); const targetX = (targetDimensions.width - tw) / 2; const targetY = ((targetDimensions.height - th) / 2); const top = translateY.value + interpolateProgress([y.value, targetY + childTranslateY.value]); const left = translateX.value + interpolateProgress([x.value, targetX]); return { opacity: imageOpacity.value, position: 'absolute', top, left, width: interpolateProgress([width.value, tw]), height: interpolateProgress([height.value, th]), transform: [ { scale: scale.value, }, ], }; }); return ( {renderBackdropComponent && renderBackdropComponent({ animatedStyles: backdropStyles, translateY: childTranslateY, })} { target.type !== 'image' && target.type !== 'avatar' && typeof renderItem === 'function' ? ( renderItem({ source: imageSource, width: target.width, height: target.height, itemStyles, }) ) : ( ) } {renderChildren && children} ); }