diff --git a/native/chat/chat-thread-list-search.react.js b/native/chat/chat-thread-list-search.react.js index 9f7194c14..d8c4909ff 100644 --- a/native/chat/chat-thread-list-search.react.js +++ b/native/chat/chat-thread-list-search.react.js @@ -1,175 +1,166 @@ // @flow import * as React from 'react'; import { TextInput as BaseTextInput } from 'react-native'; -import Animated from 'react-native-reanimated'; +import Animated, { + useSharedValue, + interpolate, + useAnimatedStyle, + withTiming, + useDerivedValue, +} from 'react-native-reanimated'; import type { ReactRefSetter } from 'lib/types/react-types.js'; import type { SearchStatus } from './chat-thread-list.react.js'; import Button from '../components/button.react.js'; import Search from '../components/search.react.js'; import { useStyles } from '../themes/colors.js'; -import { AnimatedView, type AnimatedStyleObj } from '../types/styles.js'; -import { animateTowards } from '../utils/animation-utils.js'; - -const { Node, Value, interpolateNode, useValue } = Animated; +import { AnimatedView } from '../types/styles.js'; type Props = { +searchText: string, +onChangeText: (updatedSearchText: string) => mixed, +onBlur: () => mixed, +additionalProps?: Partial>, +onSearchCancel: () => mixed, +searchStatus: SearchStatus, +innerSearchAutoFocus?: boolean, +innerSearchActive?: boolean, }; function ForwardedChatThreadListSearch( props: Props, ref: ReactRefSetter>, ): React.Node { const { searchText, onChangeText, onBlur, onSearchCancel, searchStatus, innerSearchActive, innerSearchAutoFocus, } = props; const styles = useStyles(unboundStyles); - const searchCancelButtonOpen: Value = useValue(0); - const searchCancelButtonProgress: Node = React.useMemo( - () => animateTowards(searchCancelButtonOpen, 100), - [searchCancelButtonOpen], - ); - const searchCancelButtonOffset: Node = React.useMemo( - () => - interpolateNode(searchCancelButtonProgress, { - inputRange: [0, 1], - outputRange: [0, 56], - }), - [searchCancelButtonProgress], - ); - + const cancelButtonExpansion = useSharedValue(0); const isActiveOrActivating = searchStatus === 'active' || searchStatus === 'activating'; React.useEffect(() => { if (isActiveOrActivating) { - searchCancelButtonOpen.setValue(1); + cancelButtonExpansion.value = withTiming(1); } else { - searchCancelButtonOpen.setValue(0); + cancelButtonExpansion.value = withTiming(0); } - }, [isActiveOrActivating, searchCancelButtonOpen]); + }, [isActiveOrActivating, cancelButtonExpansion]); - const animatedSearchBoxStyle: AnimatedStyleObj = React.useMemo( - () => ({ - marginRight: searchCancelButtonOffset, - }), - [searchCancelButtonOffset], + const searchCancelButtonOffset = useDerivedValue(() => + interpolate(cancelButtonExpansion.value, [0, 1], [0, 56]), ); + const animatedSearchBoxStyle = useAnimatedStyle(() => ({ + marginRight: searchCancelButtonOffset.value, + })); + const searchBoxStyle = React.useMemo( () => [styles.searchBox, animatedSearchBoxStyle], [animatedSearchBoxStyle, styles.searchBox], ); + const animatedButtonStyle = useAnimatedStyle(() => ({ + opacity: cancelButtonExpansion.value, + })); const buttonStyle = React.useMemo( - () => [ - styles.cancelSearchButtonText, - { opacity: searchCancelButtonProgress }, - ], - [searchCancelButtonProgress, styles.cancelSearchButtonText], + () => [styles.cancelSearchButtonText, animatedButtonStyle], + [animatedButtonStyle, styles.cancelSearchButtonText], ); const innerSearchNode = React.useMemo( () => ( ), [ innerSearchActive, innerSearchAutoFocus, onBlur, onChangeText, ref, searchText, styles.search, ], ); const searchContainer = React.useMemo( () => {innerSearchNode}, [innerSearchNode, searchBoxStyle], ); const cancelButton = React.useMemo( () => ( ), [buttonStyle, onSearchCancel, searchStatus, styles.cancelSearchButton], ); const chatThreadListSearch = React.useMemo( () => ( <> {cancelButton} {searchContainer} ), [cancelButton, searchContainer], ); return chatThreadListSearch; } const unboundStyles = { searchBox: { flex: 1, }, search: { marginBottom: 8, marginHorizontal: 18, marginTop: 16, }, cancelSearchButton: { position: 'absolute', right: 0, top: 0, bottom: 0, display: 'flex', justifyContent: 'center', }, cancelSearchButtonText: { color: 'link', fontSize: 16, paddingHorizontal: 16, paddingTop: 8, }, }; const ChatThreadListSearch: React.AbstractComponent< Props, React.ElementRef, > = React.forwardRef>( ForwardedChatThreadListSearch, ); ChatThreadListSearch.displayName = 'ChatThreadListSearch'; export default ChatThreadListSearch; diff --git a/native/types/styles.js b/native/types/styles.js index e90e97686..ae067c2a6 100644 --- a/native/types/styles.js +++ b/native/types/styles.js @@ -1,73 +1,76 @@ // @flow import * as React from 'react'; import { View, Text, Image } from 'react-native'; import Animated, { type ReanimatedAnimationBuilder, type EntryExitAnimationFunction, + type SharedValue, } from 'react-native-reanimated'; import type { ViewStyleObj } from './react-native.js'; type ViewProps = React.ElementConfig; export type ViewStyle = $PropertyType; type TextProps = React.ElementConfig; export type TextStyle = $PropertyType; type ImageProps = React.ElementConfig; export type ImageStyle = $PropertyType; +type Value = ?number | Animated.Node | SharedValue; + export type ReanimatedTransform = { - +scale?: ?number | Animated.Node, - +translateX?: ?number | Animated.Node, - +translateY?: ?number | Animated.Node, + +scale?: Value, + +translateX?: Value, + +translateY?: Value, ... }; export type WritableAnimatedStyleObj = { ...ViewStyleObj, - opacity?: ?number | Animated.Node, - height?: ?number | Animated.Node, - width?: ?number | Animated.Node, - marginTop?: ?number | Animated.Node, - marginRight?: ?number | Animated.Node, - marginLeft?: ?number | Animated.Node, - backgroundColor?: ?string | Animated.Node, - bottom?: ?number | Animated.Node, + opacity?: Value, + height?: Value, + width?: Value, + marginTop?: Value, + marginRight?: Value, + marginLeft?: Value, + backgroundColor?: ?string | Animated.Node | SharedValue, + bottom?: Value, transform?: $ReadOnlyArray, ... }; export type AnimatedStyleObj = $ReadOnly; export type AnimatedViewStyle = | AnimatedStyleObj | $ReadOnlyArray; const AnimatedView: React.ComponentType<{ ...$Diff, +style: AnimatedViewStyle, +entering?: | ReanimatedAnimationBuilder | EntryExitAnimationFunction | Keyframe, +exiting?: ReanimatedAnimationBuilder | EntryExitAnimationFunction | Keyframe, }> = Animated.View; export type AnimatedTextStyle = | AnimatedStyleObj | $ReadOnlyArray; const AnimatedText: React.ComponentType<{ ...$Diff, +style: AnimatedTextStyle, }> = Animated.Text; export type AnimatedImageStyle = | AnimatedStyleObj | $ReadOnlyArray; const AnimatedImage: React.ComponentType<{ ...$Diff, +style: AnimatedImageStyle, }> = Animated.Image; export { AnimatedView, AnimatedText, AnimatedImage };