import { Animated } from 'react-native'; import { isIphoneX } from 'react-native-iphone-x-helper'; import conditional from '../utils/conditional'; import { StackCardInterpolationProps, StackCardInterpolatedStyle, } from '../types'; const { add, multiply } = Animated; /** * Standard iOS-style slide in from the right. */ export function forHorizontalIOS({ current, next, inverted, layouts: { screen }, }: StackCardInterpolationProps): StackCardInterpolatedStyle { const translateFocused = multiply( current.progress.interpolate({ inputRange: [0, 1], outputRange: [screen.width, 0], extrapolate: 'clamp', }), inverted ); const translateUnfocused = next ? multiply( next.progress.interpolate({ inputRange: [0, 1], outputRange: [0, screen.width * -0.3], extrapolate: 'clamp', }), inverted ) : 0; const overlayOpacity = current.progress.interpolate({ inputRange: [0, 1], outputRange: [0, 0.07], extrapolate: 'clamp', }); const shadowOpacity = current.progress.interpolate({ inputRange: [0, 1], outputRange: [0, 0.3], extrapolate: 'clamp', }); return { cardStyle: { transform: [ // Translation for the animation of the current card { translateX: translateFocused }, // Translation for the animation of the card on top of this { translateX: translateUnfocused }, ], }, overlayStyle: { opacity: overlayOpacity }, shadowStyle: { shadowOpacity }, }; } /** * Standard iOS-style slide in from the bottom (used for modals). */ export function forVerticalIOS({ current, inverted, layouts: { screen }, }: StackCardInterpolationProps): StackCardInterpolatedStyle { const translateY = multiply( current.progress.interpolate({ inputRange: [0, 1], outputRange: [screen.height, 0], extrapolate: 'clamp', }), inverted ); return { cardStyle: { transform: [ // Translation for the animation of the current card { translateY }, ], }, }; } /** * Standard iOS-style modal animation in iOS 13. */ export function forModalPresentationIOS({ index, current, next, inverted, layouts: { screen }, insets, }: StackCardInterpolationProps): StackCardInterpolatedStyle { const isLandscape = screen.width > screen.height; const topOffset = isLandscape ? 0 : 10; const statusBarHeight = insets.top; const aspectRatio = screen.height / screen.width; const progress = add( current.progress.interpolate({ inputRange: [0, 1], outputRange: [0, 1], extrapolate: 'clamp', }), next ? next.progress.interpolate({ inputRange: [0, 1], outputRange: [0, 1], extrapolate: 'clamp', }) : 0 ); const translateY = multiply( progress.interpolate({ inputRange: [0, 1, 2], outputRange: [ screen.height, index === 0 ? 0 : topOffset, (index === 0 ? statusBarHeight : 0) - topOffset * aspectRatio, ], }), inverted ); const overlayOpacity = progress.interpolate({ inputRange: [0, 1, 1.0001, 2], outputRange: [0, 0.3, 1, 1], }); const scale = isLandscape ? 1 : progress.interpolate({ inputRange: [0, 1, 2], outputRange: [ 1, 1, screen.width ? 1 - (topOffset * 2) / screen.width : 1, ], }); const borderRadius = isLandscape ? 0 : index === 0 ? progress.interpolate({ inputRange: [0, 1, 1.0001, 2], outputRange: [0, 0, isIphoneX() ? 38 : 0, 10], }) : 10; return { cardStyle: { overflow: 'hidden', borderTopLeftRadius: borderRadius, borderTopRightRadius: borderRadius, marginTop: index === 0 ? 0 : statusBarHeight, marginBottom: index === 0 ? 0 : topOffset, transform: [{ translateY }, { scale }], }, overlayStyle: { opacity: overlayOpacity }, }; } /** * Standard Android-style fade in from the bottom for Android Oreo. */ export function forFadeFromBottomAndroid({ current, inverted, layouts: { screen }, closing, }: StackCardInterpolationProps): StackCardInterpolatedStyle { const translateY = multiply( current.progress.interpolate({ inputRange: [0, 1], outputRange: [screen.height * 0.08, 0], extrapolate: 'clamp', }), inverted ); const opacity = conditional( closing, current.progress, current.progress.interpolate({ inputRange: [0, 0.5, 0.9, 1], outputRange: [0, 0.25, 0.7, 1], }) ); return { cardStyle: { opacity, transform: [{ translateY }], }, }; } /** * Standard Android-style reveal from the bottom for Android Pie. */ export function forRevealFromBottomAndroid({ current, next, inverted, layouts: { screen }, }: StackCardInterpolationProps): StackCardInterpolatedStyle { const containerTranslateY = multiply( current.progress.interpolate({ inputRange: [0, 1], outputRange: [screen.height, 0], extrapolate: 'clamp', }), inverted ); const cardTranslateYFocused = multiply( current.progress.interpolate({ inputRange: [0, 1], outputRange: [screen.height * (95.9 / 100) * -1, 0], extrapolate: 'clamp', }), inverted ); const cardTranslateYUnfocused = next ? multiply( next.progress.interpolate({ inputRange: [0, 1], outputRange: [0, screen.height * (2 / 100) * -1], extrapolate: 'clamp', }), inverted ) : 0; const overlayOpacity = current.progress.interpolate({ inputRange: [0, 0.36, 1], outputRange: [0, 0.1, 0.1], extrapolate: 'clamp', }); return { containerStyle: { overflow: 'hidden', transform: [{ translateY: containerTranslateY }], }, cardStyle: { transform: [ { translateY: cardTranslateYFocused }, { translateY: cardTranslateYUnfocused }, ], }, overlayStyle: { opacity: overlayOpacity }, }; } /** * Standard Android-style reveal from the bottom for Android Q. */ export function forScaleFromCenterAndroid({ current, next, closing, }: StackCardInterpolationProps): StackCardInterpolatedStyle { const progress = add( current.progress.interpolate({ inputRange: [0, 1], outputRange: [0, 1], extrapolate: 'clamp', }), next ? next.progress.interpolate({ inputRange: [0, 1], outputRange: [0, 1], extrapolate: 'clamp', }) : 0 ); const opacity = progress.interpolate({ inputRange: [0, 0.8, 1, 1.2, 2], outputRange: [0, 0.5, 1, 0.33, 0], }); const scale = conditional( closing, current.progress.interpolate({ inputRange: [0, 1], outputRange: [0.9, 1], extrapolate: 'clamp', }), progress.interpolate({ inputRange: [0, 1, 2], outputRange: [0.85, 1, 1.1], }) ); return { containerStyle: { opacity, transform: [{ scale }], }, }; } export function forNoAnimation(): StackCardInterpolatedStyle { return {}; }