Changeset View
Changeset View
Standalone View
Standalone View
native/chat/reaction-selection-popover.react.js
// @flow | // @flow | ||||
import invariant from 'invariant'; | import invariant from 'invariant'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { View, TouchableOpacity, Text } from 'react-native'; | import { View, TouchableOpacity, Text } from 'react-native'; | ||||
import Animated from 'react-native-reanimated'; | import Animated from 'react-native-reanimated'; | ||||
import { | import { | ||||
useReactionSelectionPopoverPosition, | useReactionSelectionPopoverPosition, | ||||
getCalculatedMargin, | getCalculatedMargin, | ||||
reactionSelectionPopoverHeight, | reactionSelectionPopoverDimensions, | ||||
} from './reaction-message-utils.js'; | } from './reaction-message-utils.js'; | ||||
import SWMansionIcon from '../components/swmansion-icon.react.js'; | import SWMansionIcon from '../components/swmansion-icon.react.js'; | ||||
import type { AppNavigationProp } from '../navigation/app-navigator.react.js'; | import type { AppNavigationProp } from '../navigation/app-navigator.react.js'; | ||||
import { OverlayContext } from '../navigation/overlay-context.js'; | import { OverlayContext } from '../navigation/overlay-context.js'; | ||||
import type { TooltipModalParamList } from '../navigation/route-names.js'; | import type { TooltipModalParamList } from '../navigation/route-names.js'; | ||||
import { useSelector } from '../redux/redux-utils.js'; | |||||
import { useStyles } from '../themes/colors.js'; | import { useStyles } from '../themes/colors.js'; | ||||
import { useTooltipActions } from '../tooltip/tooltip-hooks.js'; | import { useTooltipActions } from '../tooltip/tooltip-hooks.js'; | ||||
import type { TooltipRoute } from '../tooltip/tooltip.react.js'; | import type { TooltipRoute } from '../tooltip/tooltip.react.js'; | ||||
import { AnimatedView } from '../types/styles.js'; | import { AnimatedView } from '../types/styles.js'; | ||||
type Props<RouteName: $Keys<TooltipModalParamList>> = { | type Props<RouteName: $Keys<TooltipModalParamList>> = { | ||||
+navigation: AppNavigationProp<RouteName>, | +navigation: AppNavigationProp<RouteName>, | ||||
+route: TooltipRoute<RouteName>, | +route: TooltipRoute<RouteName>, | ||||
+openEmojiPicker: () => mixed, | +openEmojiPicker: () => mixed, | ||||
+sendReaction: (reaction: string) => mixed, | +sendReaction: (reaction: string) => mixed, | ||||
}; | }; | ||||
/* eslint-disable import/no-named-as-default-member */ | /* eslint-disable import/no-named-as-default-member */ | ||||
const { Extrapolate, interpolateNode } = Animated; | const { Extrapolate, interpolateNode, add, multiply } = Animated; | ||||
/* eslint-enable import/no-named-as-default-member */ | /* eslint-enable import/no-named-as-default-member */ | ||||
function ReactionSelectionPopover<RouteName: $Keys<TooltipModalParamList>>( | function ReactionSelectionPopover<RouteName: $Keys<TooltipModalParamList>>( | ||||
props: Props<RouteName>, | props: Props<RouteName>, | ||||
): React.Node { | ): React.Node { | ||||
const { navigation, route, openEmojiPicker, sendReaction } = props; | const { navigation, route, openEmojiPicker, sendReaction } = props; | ||||
const { verticalBounds, initialCoordinates, margin } = route.params; | const { verticalBounds, initialCoordinates, margin } = route.params; | ||||
const { containerStyle: popoverContainerStyle, popoverLocation } = | const { containerStyle: popoverContainerStyle, popoverLocation } = | ||||
useReactionSelectionPopoverPosition({ | useReactionSelectionPopoverPosition({ | ||||
initialCoordinates, | initialCoordinates, | ||||
verticalBounds, | verticalBounds, | ||||
margin, | margin, | ||||
}); | }); | ||||
const overlayContext = React.useContext(OverlayContext); | const overlayContext = React.useContext(OverlayContext); | ||||
invariant( | invariant( | ||||
overlayContext, | overlayContext, | ||||
'ReactionSelectionPopover should have OverlayContext', | 'ReactionSelectionPopover should have OverlayContext', | ||||
); | ); | ||||
const { position } = overlayContext; | const { position } = overlayContext; | ||||
const dimensions = useSelector(state => state.dimensions); | |||||
const popoverHorizontalOffset = React.useMemo(() => { | |||||
const { x, width } = initialCoordinates; | |||||
const extraLeftSpace = x; | |||||
const extraRightSpace = dimensions.width - width - x; | |||||
const popoverWidth = reactionSelectionPopoverDimensions.width; | |||||
if (extraLeftSpace < extraRightSpace) { | |||||
const minWidth = width + 2 * extraLeftSpace; | |||||
return (minWidth - popoverWidth) / 2; | |||||
} else { | |||||
const minWidth = width + 2 * extraRightSpace; | |||||
return (popoverWidth - minWidth) / 2; | |||||
} | |||||
}, [initialCoordinates, dimensions]); | |||||
const calculatedMargin = getCalculatedMargin(margin); | const calculatedMargin = getCalculatedMargin(margin); | ||||
const animationStyle = React.useMemo(() => { | const animationStyle = React.useMemo(() => { | ||||
const style = {}; | const style = {}; | ||||
style.opacity = interpolateNode(position, { | style.opacity = interpolateNode(position, { | ||||
inputRange: [0, 0.1], | inputRange: [0, 0.1], | ||||
outputRange: [0, 1], | outputRange: [0, 1], | ||||
extrapolate: Extrapolate.CLAMP, | extrapolate: Extrapolate.CLAMP, | ||||
}); | }); | ||||
style.transform = [ | style.transform = [ | ||||
{ | { | ||||
scale: interpolateNode(position, { | scale: interpolateNode(position, { | ||||
inputRange: [0.2, 0.8], | inputRange: [0.2, 0.8], | ||||
outputRange: [0, 1], | outputRange: [0, 1], | ||||
extrapolate: Extrapolate.CLAMP, | extrapolate: Extrapolate.CLAMP, | ||||
}), | }), | ||||
}, | }, | ||||
{ | |||||
translateX: multiply( | |||||
add(1, multiply(-1, position)), | |||||
popoverHorizontalOffset, | |||||
), | |||||
}, | |||||
]; | ]; | ||||
if (popoverLocation === 'above') { | if (popoverLocation === 'above') { | ||||
style.transform.push({ | style.transform.push({ | ||||
translateY: interpolateNode(position, { | translateY: interpolateNode(position, { | ||||
inputRange: [0, 1], | inputRange: [0, 1], | ||||
outputRange: [ | outputRange: [ | ||||
calculatedMargin + reactionSelectionPopoverHeight / 2, | calculatedMargin + reactionSelectionPopoverDimensions.height / 2, | ||||
0, | 0, | ||||
], | ], | ||||
extrapolate: Extrapolate.CLAMP, | extrapolate: Extrapolate.CLAMP, | ||||
}), | }), | ||||
}); | }); | ||||
} else { | } else { | ||||
style.transform.push({ | style.transform.push({ | ||||
translateY: interpolateNode(position, { | translateY: interpolateNode(position, { | ||||
inputRange: [0, 1], | inputRange: [0, 1], | ||||
outputRange: [ | outputRange: [ | ||||
-calculatedMargin - reactionSelectionPopoverHeight / 2, | -calculatedMargin - reactionSelectionPopoverDimensions.height / 2, | ||||
0, | 0, | ||||
], | ], | ||||
extrapolate: Extrapolate.CLAMP, | extrapolate: Extrapolate.CLAMP, | ||||
}), | }), | ||||
}); | }); | ||||
} | } | ||||
return style; | return style; | ||||
}, [position, calculatedMargin, popoverLocation]); | }, [position, calculatedMargin, popoverLocation, popoverHorizontalOffset]); | ||||
const styles = useStyles(unboundStyles); | const styles = useStyles(unboundStyles); | ||||
const containerStyle = React.useMemo( | const containerStyle = React.useMemo( | ||||
() => ({ | () => ({ | ||||
...styles.reactionSelectionPopoverContainer, | ...styles.reactionSelectionPopoverContainer, | ||||
...popoverContainerStyle, | ...popoverContainerStyle, | ||||
...animationStyle, | ...animationStyle, | ||||
▲ Show 20 Lines • Show All 92 Lines • Show Last 20 Lines |