mirror of
https://github.com/mattermost/mattermost-mobile.git
synced 2026-11-04 09:58:57 +00:00
* Expo-Router partial migration * Full migration to ExpoRouter * i18n * add tests * Merge branch 'main' into expo-router * align package-lock.json * i18n * feedback review * feedback review * android 16kb patch * Refactored emoji picker keyboard using a state machine * set android launch_screen in drawables instead of layout * no conditional hooks * remove duplicate call in test * address feedback * fix tests and address more feedback * small corrections * small corrections * small corrections * fix draft test * fix draft test 2 * remove navigateToScreen in draff test * fix nit in draft test * remove doc file * Feedback review * fix snapshots * improve test coverage * fix i18n * Upgrade t o RN 0.83, other dependencies and new architecture (#9737) * library hardware-keyboard android new arch support fix * library rnshare android new arch support fix * library rnutils android new arch support fix * library secure-pdf-viewer add new arch support * Enable new arch on iOS and Android * update emm and network client with new-arch * update intune to use new-arch * performance improvements * fix tests * fixes post input on new-arch * fix android emoji picker * remove doc * run tests in parallel * upgrade to React Native 0.83.9, Expo 55, React 19, and New Architecture Bumps core dependencies to their latest versions: - React Native 0.77.3 → 0.83.9 (New Architecture enabled) - React 18.3.1 → 19.2.5 - Expo SDK 52 → 55 - TypeScript 5.8.3 → 6.0.3 - Node.js 22.16.0 → 24.15.0 - react-native-reanimated 3.17.3 → 4.3.0 - Various other dependency upgrades Migrates iOS AppDelegate from Objective-C to Swift, updates Android build tooling and Gradle wrapper, refreshes all patches and font assets, and adapts app code, tests, and CI workflows throughout for API changes introduced by these upgrades. * remove unecessary dismissKeyboard in notifications * feedback review * update intune to use new-arch * update @mattermost deps * missing i18n string * additional fixes * update fastlane
199 lines
6.1 KiB
TypeScript
199 lines
6.1 KiB
TypeScript
// 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<AnimatedStyle<StyleProp<ImageStyle>>>;
|
|
}
|
|
|
|
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<boolean>(false);
|
|
const childLayoutTimeoutRef = React.useRef<NodeJS.Timeout | undefined>(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 (
|
|
<View style={styles.container}>
|
|
{renderBackdropComponent &&
|
|
renderBackdropComponent({
|
|
animatedStyles: backdropStyles,
|
|
translateY: childTranslateY,
|
|
})}
|
|
|
|
<Animated.View style={StyleSheet.absoluteFill}>
|
|
{
|
|
target.type !== 'image' &&
|
|
target.type !== 'avatar' &&
|
|
typeof renderItem === 'function' ? (
|
|
renderItem({
|
|
source: imageSource,
|
|
width: target.width,
|
|
height: target.height,
|
|
itemStyles,
|
|
})
|
|
) : (
|
|
<ExpoImageAnimated
|
|
id={target.cacheKey}
|
|
source={imageSource}
|
|
placeholder={imageSource}
|
|
placeholderContentFit='cover'
|
|
style={itemStyles}
|
|
autoplay={false}
|
|
/>
|
|
)
|
|
}
|
|
</Animated.View>
|
|
|
|
<Animated.View
|
|
style={[StyleSheet.absoluteFill, childrenAnimateStyle]}
|
|
>
|
|
{renderChildren && children}
|
|
</Animated.View>
|
|
</View>
|
|
);
|
|
}
|