From ba16632ccc5f3e62edbe9392706cf03884ccfdda Mon Sep 17 00:00:00 2001 From: Samuel Holmes Date: Tue, 12 Dec 2023 14:16:34 -0800 Subject: [PATCH] Implement SceneDrawer and WalletListSearch --- CHANGELOG.md | 1 + .../__snapshots__/MenuTabs.test.tsx.snap | 2 - ...reateWalletAccountSetupScene.test.tsx.snap | 26 ++ .../CreateWalletImportScene.test.tsx.snap | 26 ++ ...reateWalletSelectCryptoScene.test.tsx.snap | 26 ++ .../CreateWalletSelectFiatScene.test.tsx.snap | 26 ++ .../CryptoExchangeQuoteScene.test.tsx.snap | 26 ++ .../CurrencyNotificationScene.test.tsx.snap | 26 ++ .../CurrencySettings.ui.test.tsx.snap | 26 ++ .../EdgeLoginScene.test.tsx.snap | 26 ++ .../__snapshots__/SendScene2.ui.test.tsx.snap | 260 ++++++++++++++++++ .../__snapshots__/SettingsScene.test.tsx.snap | 52 ++++ .../TransactionDetailsScene.test.tsx.snap | 52 ++++ src/components/common/SceneWrapper.tsx | 12 +- src/components/scenes/WalletListScene.tsx | 51 ++-- src/components/themed/MenuTabs.tsx | 38 +-- src/components/themed/SceneDrawer.tsx | 46 ++++ src/components/themed/WalletListHeader.tsx | 59 +--- src/components/themed/WalletListSearch.tsx | 106 +++++++ src/state/SceneDrawerState.tsx | 75 +++-- 20 files changed, 831 insertions(+), 131 deletions(-) create mode 100644 src/components/themed/SceneDrawer.tsx create mode 100644 src/components/themed/WalletListSearch.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index a8f60c89e12..aa954b154f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - changed: Block Buy/Sell/Receive for Light Accounts - changed: Free FIO handle modal visual design - changed: New dynamic menu tabs that responds to scene scroll +- changed: New dynamic wallet list search drawer above tabs - changed: Scene layout to support transparent and blurred header and tab-bar - changed: Bity and Paybis plugins to use EdgeTxAction for sell transactions - changed: Do not write tx.metadata in fiat sell transactions diff --git a/src/__tests__/components/__snapshots__/MenuTabs.test.tsx.snap b/src/__tests__/components/__snapshots__/MenuTabs.test.tsx.snap index e7608dfa02e..f65289821f4 100644 --- a/src/__tests__/components/__snapshots__/MenuTabs.test.tsx.snap +++ b/src/__tests__/components/__snapshots__/MenuTabs.test.tsx.snap @@ -2,7 +2,6 @@ exports[`MenuTabs should render with loading props 1`] = ` + , ] diff --git a/src/__tests__/scenes/__snapshots__/CreateWalletImportScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/CreateWalletImportScene.test.tsx.snap index 7e14787bd72..2061f12bc8c 100644 --- a/src/__tests__/scenes/__snapshots__/CreateWalletImportScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/CreateWalletImportScene.test.tsx.snap @@ -783,6 +783,32 @@ exports[`CreateWalletImportScene should render with loading props 1`] = ` + , ] `; diff --git a/src/__tests__/scenes/__snapshots__/CreateWalletSelectCryptoScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/CreateWalletSelectCryptoScene.test.tsx.snap index 96bd816a89a..e9ac349f56d 100644 --- a/src/__tests__/scenes/__snapshots__/CreateWalletSelectCryptoScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/CreateWalletSelectCryptoScene.test.tsx.snap @@ -1026,6 +1026,32 @@ exports[`CreateWalletSelectCrypto should render with loading props 1`] = ` + , ] `; diff --git a/src/__tests__/scenes/__snapshots__/CreateWalletSelectFiatScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/CreateWalletSelectFiatScene.test.tsx.snap index 99e060540ec..5ad033a72ef 100644 --- a/src/__tests__/scenes/__snapshots__/CreateWalletSelectFiatScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/CreateWalletSelectFiatScene.test.tsx.snap @@ -989,6 +989,32 @@ exports[`CreateWalletSelectFiatComponent should render with loading props 1`] = + , ] `; diff --git a/src/__tests__/scenes/__snapshots__/CryptoExchangeQuoteScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/CryptoExchangeQuoteScene.test.tsx.snap index 4550fff8e27..4da85d98254 100644 --- a/src/__tests__/scenes/__snapshots__/CryptoExchangeQuoteScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/CryptoExchangeQuoteScene.test.tsx.snap @@ -1789,6 +1789,32 @@ exports[`CryptoExchangeQuoteScreenComponent should render with loading props 1`] } } /> + , ] `; diff --git a/src/__tests__/scenes/__snapshots__/CurrencyNotificationScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/CurrencyNotificationScene.test.tsx.snap index 9351c9caa40..2f4703debeb 100644 --- a/src/__tests__/scenes/__snapshots__/CurrencyNotificationScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/CurrencyNotificationScene.test.tsx.snap @@ -442,6 +442,32 @@ exports[`CurrencyNotificationComponent should render with loading props 1`] = ` + , ] diff --git a/src/__tests__/scenes/__snapshots__/CurrencySettings.ui.test.tsx.snap b/src/__tests__/scenes/__snapshots__/CurrencySettings.ui.test.tsx.snap index 6221a22d4d4..3e3bcf871c0 100644 --- a/src/__tests__/scenes/__snapshots__/CurrencySettings.ui.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/CurrencySettings.ui.test.tsx.snap @@ -639,6 +639,32 @@ exports[`CurrencySettings should render 1`] = ` + , ] diff --git a/src/__tests__/scenes/__snapshots__/EdgeLoginScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/EdgeLoginScene.test.tsx.snap index 0e3fb80ce77..6154cb60ca5 100644 --- a/src/__tests__/scenes/__snapshots__/EdgeLoginScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/EdgeLoginScene.test.tsx.snap @@ -635,6 +635,32 @@ exports[`EdgeLoginScene should render with loading props 1`] = ` + , ] `; diff --git a/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap b/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap index 806de0a9eba..af5ae6b8e23 100644 --- a/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap @@ -1160,6 +1160,32 @@ exports[`SendScene2 1 spendTarget 1`] = ` } } /> + , ] `; @@ -2512,6 +2538,32 @@ exports[`SendScene2 1 spendTarget with info tiles 1`] = ` } } /> + , ] `; @@ -3835,6 +3887,32 @@ exports[`SendScene2 2 spendTargets 1`] = ` } } /> + , ] `; @@ -5003,6 +5081,32 @@ exports[`SendScene2 2 spendTargets hide tiles 1`] = ` } } /> + , ] `; @@ -6147,6 +6251,32 @@ exports[`SendScene2 2 spendTargets hide tiles 2`] = ` } } /> + , ] `; @@ -7136,6 +7266,32 @@ exports[`SendScene2 2 spendTargets hide tiles 3`] = ` } } /> + , ] `; @@ -8418,6 +8574,32 @@ exports[`SendScene2 2 spendTargets lock tiles 1`] = ` } } /> + , ] `; @@ -9635,6 +9817,32 @@ exports[`SendScene2 2 spendTargets lock tiles 2`] = ` } } /> + , ] `; @@ -10811,6 +11019,32 @@ exports[`SendScene2 2 spendTargets lock tiles 3`] = ` } } /> + , ] `; @@ -11997,6 +12231,32 @@ exports[`SendScene2 Render SendScene 1`] = ` } } /> + , ] `; diff --git a/src/__tests__/scenes/__snapshots__/SettingsScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/SettingsScene.test.tsx.snap index 103f784887f..9b8e8b98f91 100644 --- a/src/__tests__/scenes/__snapshots__/SettingsScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/SettingsScene.test.tsx.snap @@ -3412,6 +3412,32 @@ exports[`MyComponent should render Locked SettingsOverview 1`] = ` + , ] @@ -6829,6 +6855,32 @@ exports[`MyComponent should render UnLocked SettingsOverview 1`] = ` + , ] diff --git a/src/__tests__/scenes/__snapshots__/TransactionDetailsScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/TransactionDetailsScene.test.tsx.snap index c8ba0dc5a4e..d1d33b3b083 100644 --- a/src/__tests__/scenes/__snapshots__/TransactionDetailsScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/TransactionDetailsScene.test.tsx.snap @@ -1742,6 +1742,32 @@ exports[`TransactionDetailsScene should render 1`] = ` } } /> + , ] @@ -3489,6 +3515,32 @@ exports[`TransactionDetailsScene should render with negative nativeAmount and fi } } /> + , ] diff --git a/src/components/common/SceneWrapper.tsx b/src/components/common/SceneWrapper.tsx index cdc9c573740..ac427722189 100644 --- a/src/components/common/SceneWrapper.tsx +++ b/src/components/common/SceneWrapper.tsx @@ -5,13 +5,14 @@ import { useMemo } from 'react' import { Animated, ScrollView, StyleSheet, useWindowDimensions, View } from 'react-native' import { EdgeInsets, useSafeAreaFrame, useSafeAreaInsets } from 'react-native-safe-area-context' -import { useDrawerOpenRatio } from '../../state/SceneDrawerState' +import { useSceneDrawerState } from '../../state/SceneDrawerState' import { useSelector } from '../../types/reactRedux' import { NavigationBase } from '../../types/routerTypes' import { maybeComponent } from '../hoc/maybeComponent' import { NotificationView } from '../notification/NotificationView' import { useTheme } from '../services/ThemeContext' import { MAX_TAB_BAR_HEIGHT } from '../themed/MenuTabs' +import { SceneDrawer } from '../themed/SceneDrawer' import { DotsBackground } from '../ui4/DotsBackground' import { KeyboardTracker } from './KeyboardTracker' @@ -57,6 +58,9 @@ interface SceneWrapperProps { // Padding to add inside the scene border: padding?: number + // Render function to render component for the tab drawer + renderDrawer?: () => React.ReactNode + // True to make the scene scrolling (if avoidKeyboard is false): scroll?: boolean } @@ -79,6 +83,7 @@ export function SceneWrapper(props: SceneWrapperProps): JSX.Element { accentColor, avoidKeyboard = false, children, + renderDrawer, hasHeader = true, hasNotifications = false, hasTabs = false, @@ -91,7 +96,7 @@ export function SceneWrapper(props: SceneWrapperProps): JSX.Element { const activeUsername = useSelector(state => state.core.account.username) const isLightAccount = accountId != null && activeUsername == null - const { drawerHeight } = useDrawerOpenRatio() + const { tabDrawerHeight = 0 } = useSceneDrawerState() const navigation = useNavigation() const theme = useTheme() @@ -137,7 +142,7 @@ export function SceneWrapper(props: SceneWrapperProps): JSX.Element { const insetStyles: InsetStyles = { paddingTop: insets.top, paddingRight: insets.right, - paddingBottom: insets.bottom + drawerHeight.value, + paddingBottom: insets.bottom + tabDrawerHeight, paddingLeft: insets.left } @@ -157,6 +162,7 @@ export function SceneWrapper(props: SceneWrapperProps): JSX.Element { {isFuncChildren ? children(info) : children} {hasNotifications ? : null} + {renderDrawer == null ? null : renderDrawer()} diff --git a/src/components/scenes/WalletListScene.tsx b/src/components/scenes/WalletListScene.tsx index ca5d58d9ae2..1a4182046cc 100644 --- a/src/components/scenes/WalletListScene.tsx +++ b/src/components/scenes/WalletListScene.tsx @@ -16,6 +16,7 @@ import { Airship, showError } from '../services/AirshipInstance' import { cacheStyles, Theme, useTheme } from '../services/ThemeContext' import { EdgeText } from '../themed/EdgeText' import { WalletListHeader } from '../themed/WalletListHeader' +import { WalletListSearch } from '../themed/WalletListSearch' import { WalletListSortable } from '../themed/WalletListSortable' import { WalletListSwipeable } from '../themed/WalletListSwipeable' import { WiredProgressBar } from '../themed/WiredProgressBar' @@ -29,7 +30,7 @@ export function WalletListScene(props: Props) { const dispatch = useDispatch() const [sorting, setSorting] = React.useState(false) - const [searching, setSearching] = React.useState(false) + const [isSearching, setIsSearching] = React.useState(false) const [searchText, setSearchText] = React.useState('') const needsPasswordCheck = useSelector(state => state.ui.passwordReminder.needsPasswordCheck) @@ -46,13 +47,25 @@ export function WalletListScene(props: Props) { }) const handleRefresh = useHandler(() => { - setSearching(true) + setIsSearching(true) }) - // Turn off searching mode when a wallet is selected - const handlReset = useHandler(() => { + const handleReset = useHandler(() => { setSearchText('') - setSearching(false) + setIsSearching(false) + }) + + const handleStartSearching = useHandler(() => { + setIsSearching(true) + }) + + const handleDoneSearching = useHandler(() => { + setSearchText('') + setIsSearching(false) + }) + + const handleChangeText = useHandler((value: string) => { + setSearchText(value) }) // Show the password reminder on mount if required: @@ -70,23 +83,25 @@ export function WalletListScene(props: Props) { // rendering ------------------------------------------------------------- const header = React.useMemo(() => { + return + }, [handleSort, navigation, isSearching, sorting]) + + const handlePressDone = useHandler(() => setSorting(false)) + + const renderDrawer = () => { return ( - ) - }, [handleSort, navigation, searchText, searching, sorting]) - - const handlePressDone = useHandler(() => setSorting(false)) + } return ( - + {({ insetStyles }) => ( <> @@ -106,10 +121,10 @@ export function WalletListScene(props: Props) { footer={undefined} navigation={navigation} insetStyles={insetStyles} - searching={searching} + searching={isSearching} searchText={searchText} onRefresh={handleRefresh} - onReset={handlReset} + onReset={handleReset} /> diff --git a/src/components/themed/MenuTabs.tsx b/src/components/themed/MenuTabs.tsx index 1a8f11b219e..1a9b58685e2 100644 --- a/src/components/themed/MenuTabs.tsx +++ b/src/components/themed/MenuTabs.tsx @@ -25,6 +25,7 @@ import { VectorIcon } from './VectorIcon' const extraTabString: LocaleStringKey = config.extraTab?.tabTitleKey ?? 'title_map' export const MAX_TAB_BAR_HEIGHT = 92 +export const MIN_TAB_BAR_HEIGHT = 74 const title: { readonly [key: string]: string } = { homeTab: lstrings.title_home, @@ -56,15 +57,13 @@ export const MenuTabs = (props: BottomTabBarProps) => { [state.routes] ) - const insets = useSafeAreaInsets() - const activeTabRoute = state.routes[activeTabFullIndex] const activeTabIndex = routes.findIndex(route => route.name === activeTabRoute.name) - const { drawerOpenRatio, handleDrawerLayout, isRatioDisabled = false, resetDrawerRatio } = useDrawerOpenRatio() + const { drawerOpenRatio, resetDrawerRatio } = useDrawerOpenRatio() return ( - + @@ -77,7 +76,6 @@ export const MenuTabs = (props: BottomTabBarProps) => { isActive={activeTabIndex === index} drawerOpenRatio={drawerOpenRatio} resetDrawerRatio={resetDrawerRatio} - isRatioDisabled={isRatioDisabled} /> ))} @@ -86,7 +84,7 @@ export const MenuTabs = (props: BottomTabBarProps) => { ) } -const ContainerLinearGradient = styled(LinearGradient)<{ bottom: number; height?: number }>({ +const ContainerLinearGradient = styled(LinearGradient)({ position: 'absolute', left: 0, right: 0, @@ -107,15 +105,13 @@ const Tab = ({ drawerOpenRatio, resetDrawerRatio, currentName, - isRatioDisabled, navigation }: { isActive: boolean currentName: string route: BottomTabBarProps['state']['routes'][number] - drawerOpenRatio: SharedValue | undefined + drawerOpenRatio: SharedValue resetDrawerRatio: () => void - isRatioDisabled: boolean navigation: NavigationHelpers }) => { const theme = useTheme() @@ -166,34 +162,25 @@ const Tab = ({ return ( {icon[route.name]} - ) } -const TabContainer = styled(TouchableOpacity)<{ insetBottom: number }>(theme => props => ({ +const TabContainer = styled(TouchableOpacity)<{ insetBottom: number }>(theme => ({ insetBottom }) => ({ flex: 1, paddingTop: theme.rem(0.75), - paddingBottom: Math.max(theme.rem(0.75), props.insetBottom), + paddingBottom: Math.max(theme.rem(0.75), insetBottom), justifyContent: 'center', alignItems: 'center' })) const Label = styled(Animated.Text)<{ isActive: boolean - isRatioDisabled: boolean - openRatio: SharedValue | undefined -}>(theme => ({ isActive, isRatioDisabled, openRatio }) => { + openRatio: SharedValue +}>(theme => ({ isActive, openRatio }) => { const rem = theme.rem(1) return [ { @@ -207,9 +194,10 @@ const Label = styled(Animated.Text)<{ }, useAnimatedStyle(() => { 'worklet' + if (openRatio == null) return {} return { - height: isRatioDisabled ? undefined : openRatio == null ? undefined : rem * openRatio.value, - opacity: isRatioDisabled ? undefined : openRatio == null ? undefined : openRatio.value + height: rem * openRatio.value, + opacity: openRatio.value } }) ] diff --git a/src/components/themed/SceneDrawer.tsx b/src/components/themed/SceneDrawer.tsx new file mode 100644 index 00000000000..0741627fde2 --- /dev/null +++ b/src/components/themed/SceneDrawer.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import Animated, { interpolate, SharedValue, useAnimatedStyle } from 'react-native-reanimated' + +import { useDrawerOpenRatio, useLayoutHeightInTabBar } from '../../state/SceneDrawerState' +import { styled } from '../hoc/styled' +import { MAX_TAB_BAR_HEIGHT, MIN_TAB_BAR_HEIGHT } from './MenuTabs' + +export interface SceneDrawerProps { + children: React.ReactNode + isKeyboardOpen: boolean +} + +export const SceneDrawer = (props: SceneDrawerProps) => { + const { children, isKeyboardOpen } = props + const { drawerOpenRatio } = useDrawerOpenRatio() + const handleLayout = useLayoutHeightInTabBar() + + return ( + + {children} + + ) +} + +const Drawer = styled(Animated.View)<{ + drawerOpenRatio: SharedValue + isKeyboardOpen: boolean +}>(() => ({ drawerOpenRatio, isKeyboardOpen }) => { + return [ + { + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + flexDirection: 'column', + justifyContent: 'flex-start', + alignItems: 'stretch', + overflow: 'hidden' + }, + useAnimatedStyle(() => { + return { + bottom: isKeyboardOpen ? 0 : interpolate(drawerOpenRatio.value, [0, 1], [MIN_TAB_BAR_HEIGHT, MAX_TAB_BAR_HEIGHT]) + } + }) + ] +}) diff --git a/src/components/themed/WalletListHeader.tsx b/src/components/themed/WalletListHeader.tsx index db09837a5ec..f1936e92524 100644 --- a/src/components/themed/WalletListHeader.tsx +++ b/src/components/themed/WalletListHeader.tsx @@ -7,49 +7,22 @@ import { lstrings } from '../../locales/strings' import { NavigationBase } from '../../types/routerTypes' import { PromoCard } from '../cards/PromoCard' import { EdgeAnim } from '../common/EdgeAnim' -import { SearchIconAnimated } from '../icons/ThemedIcons' import { cacheStyles, Theme, ThemeProps, withTheme } from '../services/ThemeContext' -import { EdgeText } from '../themed/EdgeText' import { BalanceCardUi4 } from '../ui4/BalanceCardUi4' import { SectionHeaderUi4 } from '../ui4/SectionHeaderUi4' -import { SimpleTextInput, SimpleTextInputRef } from './SimpleTextInput' interface OwnProps { navigation: NavigationBase sorting: boolean searching: boolean - searchText: string openSortModal: () => void - onChangeSearchText: (search: string) => void - onChangeSearchingState: (searching: boolean) => void } type Props = OwnProps & ThemeProps export class WalletListHeaderComponent extends React.PureComponent { - textInput = React.createRef() - - componentDidUpdate(prevProps: Props) { - if (!prevProps.searching && this.props.searching && this.textInput.current) { - this.textInput.current.focus() - } - } - - handleOnChangeText = (input: string) => this.props.onChangeSearchText(input) - - handleTextFieldFocus = () => { - this.props.onChangeSearchingState(true) - } - - handleSearchDone = () => { - this.props.onChangeSearchingState(false) - if (this.textInput.current) { - this.textInput.current.clear() - } - } - render() { - const { navigation, sorting, searching, searchText, theme } = this.props + const { navigation, sorting, searching, theme } = this.props const styles = getStyles(theme) const addSortButtons = ( @@ -65,24 +38,6 @@ export class WalletListHeaderComponent extends React.PureComponent { return ( <> - - - - - {searching && ( - - {lstrings.string_done_cap} - - )} - {searching ? null : ( @@ -112,18 +67,6 @@ const getStyles = cacheStyles((theme: Theme) => ({ }, addButton: { marginRight: theme.rem(0.5) - }, - - searchContainer: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - marginTop: theme.rem(0.5), - marginHorizontal: theme.rem(0.5) - }, - searchDoneButton: { - justifyContent: 'center', - paddingLeft: theme.rem(0.75) } })) diff --git a/src/components/themed/WalletListSearch.tsx b/src/components/themed/WalletListSearch.tsx new file mode 100644 index 00000000000..4213c7a38a0 --- /dev/null +++ b/src/components/themed/WalletListSearch.tsx @@ -0,0 +1,106 @@ +import React, { useEffect, useState } from 'react' +import { LayoutChangeEvent, StyleSheet } from 'react-native' +import Animated, { SharedValue, useAnimatedStyle, useDerivedValue } from 'react-native-reanimated' +import { BlurView } from 'rn-id-blurview' + +import { useHandler } from '../../hooks/useHandler' +import { lstrings } from '../../locales/strings' +import { useDrawerOpenRatio } from '../../state/SceneDrawerState' +import { styled } from '../hoc/styled' +import { SearchIconAnimated } from '../icons/ThemedIcons' +import { Space } from '../layout/Space' +import { useTheme } from '../services/ThemeContext' +import { SimpleTextInput, SimpleTextInputRef } from './SimpleTextInput' + +interface WalletListSearchProps { + isSearching: boolean + searchText: string + + onChangeText: (value: string) => void + onDoneSearching: () => void + onStartSearching: () => void +} + +export const WalletListSearch = (props: WalletListSearchProps) => { + const { isSearching, searchText, onChangeText, onDoneSearching, onStartSearching } = props + const theme = useTheme() + + const textInputRef = React.useRef(null) + + const { drawerOpenRatio, setKeepOpen } = useDrawerOpenRatio() + const [containerHeight, setContainerHeight] = useState(undefined) + + const inputScale = useDerivedValue(() => drawerOpenRatio.value) + + const handleLayout = useHandler((event: LayoutChangeEvent) => { + if (containerHeight != null) return + setContainerHeight(event.nativeEvent.layout.height) + }) + + const handleSearchChangeText = useHandler((text: string) => { + onChangeText(text) + }) + + const handleSearchBlur = useHandler(() => { + if (searchText === '') { + onDoneSearching() + } + }) + + const handleSearchClear = useHandler(() => { + if (!textInputRef.current?.isFocused()) { + onDoneSearching() + } + }) + + const handleSearchFocus = useHandler(() => { + onStartSearching() + }) + + useEffect(() => { + if (setKeepOpen != null) setKeepOpen(isSearching) + if (isSearching && textInputRef.current) { + textInputRef.current.focus() + } + if (!isSearching && textInputRef.current) { + textInputRef.current.blur() + } + }, [isSearching, setKeepOpen]) + + return ( + <> + + + + + + + + ) +} + +const ContainerAnimatedView = styled(Animated.View)<{ + containerHeight?: number + drawerOpenRatio: SharedValue +}>(() => ({ containerHeight, drawerOpenRatio }) => [ + { + overflow: 'hidden' + }, + useAnimatedStyle(() => { + if (containerHeight == null) return {} + return { + height: containerHeight * drawerOpenRatio.value + } + }) +]) diff --git a/src/state/SceneDrawerState.tsx b/src/state/SceneDrawerState.tsx index 4175fe97442..be4a1983b4a 100644 --- a/src/state/SceneDrawerState.tsx +++ b/src/state/SceneDrawerState.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react' +import { useEffect, useMemo } from 'react' import { LayoutChangeEvent, Platform } from 'react-native' import { runOnJS, useAnimatedReaction, useSharedValue, withTiming } from 'react-native-reanimated' @@ -9,20 +9,21 @@ import { createStateProvider } from './createStateProvider' import { useSceneScrollContext } from './SceneScrollState' export const [SceneDrawerProvider, useSceneDrawerState] = createStateProvider(() => { - const [isRatioDisabled, setIsRatioDisabled] = useState(false) - const drawerHeight = useSharedValue(0) + const [keepOpen, setKeepOpen] = useState(false) + const [tabDrawerHeight, setTabDrawerHeight] = useState(undefined) const drawerOpenRatio = useSharedValue(1) const drawerOpenRatioStart = useSharedValue(1) return useMemo( () => ({ - drawerHeight, drawerOpenRatio, drawerOpenRatioStart, - isRatioDisabled, - setIsRatioDisabled + keepOpen, + setKeepOpen, + tabDrawerHeight, + setTabDrawerHeight }), - [drawerHeight, drawerOpenRatio, drawerOpenRatioStart, isRatioDisabled] + [drawerOpenRatio, drawerOpenRatioStart, keepOpen, tabDrawerHeight] ) }) @@ -31,7 +32,7 @@ export const useDrawerOpenRatio = () => { const scrollYStart = useSharedValue(undefined) const snapTo = useSharedValue(undefined) - const { drawerHeight, drawerOpenRatio, drawerOpenRatioStart, isRatioDisabled, setIsRatioDisabled } = useSceneDrawerState() + const { drawerOpenRatio, drawerOpenRatioStart, keepOpen, setKeepOpen, tabDrawerHeight = 1 } = useSceneDrawerState() function resetDrawerRatio() { snapTo.value = 1 @@ -83,14 +84,14 @@ export const useDrawerOpenRatio = () => { useAnimatedReaction( () => { - // Drawer height is not ready - if (drawerHeight.value === 0) return drawerOpenRatio.value + // Keep it open when disabled + if (keepOpen) return 1 // Scrolling hasn't started yet if (scrollYStart.value == null) return const scrollYDelta = scrollY.value - scrollYStart.value - const ratioDelta = scrollYDelta / drawerHeight.value / 2 // Constant is to lower jumpy-ness + const ratioDelta = scrollYDelta / tabDrawerHeight // Constant is to lower jumpy-ness return Math.min(1, Math.max(0, drawerOpenRatioStart.value - ratioDelta)) }, @@ -115,31 +116,55 @@ export const useDrawerOpenRatio = () => { snapTo.value = undefined drawerOpenRatio.value = currentValue }, - [] + [keepOpen, tabDrawerHeight] ) useAnimatedReaction( - () => snapTo.value, + () => { + // Keep it open when disabled + if (keepOpen) return 1 + + return snapTo.value + }, (currentValue, previousValue) => { if (currentValue === previousValue) return if (currentValue == null) return drawerOpenRatio.value = withTiming(currentValue, { duration: 300 }) - } + }, + [keepOpen] ) - const handleDrawerLayout = useHandler((event: LayoutChangeEvent) => { - // Only handle the initial layout (re-layout is not yet supported): - if (drawerHeight.value !== 0) return - drawerHeight.value = event.nativeEvent.layout.height - }) - return { - drawerHeight, drawerOpenRatio, - isRatioDisabled, - setIsRatioDisabled, - handleDrawerLayout, - resetDrawerRatio + resetDrawerRatio, + + keepOpen, + setKeepOpen } } + +export const useLayoutHeightInTabBar = (): ((event: LayoutChangeEvent) => void) => { + const { setTabDrawerHeight } = useSceneDrawerState() + + const [layoutHeight, setLayoutHeight] = useState(undefined) + + // One-time layout measurement handler: + const handleLayout = useHandler((event: LayoutChangeEvent) => { + if (layoutHeight == null) { + const layoutHeight = event.nativeEvent.layout.height + setLayoutHeight((prev = 0) => prev + layoutHeight) + } + }) + + // Add/subtract container height to the tab-bar height when mounted/unmounted + useEffect(() => { + if (layoutHeight == null) return + setTabDrawerHeight((prev = 0) => prev + layoutHeight) + return () => { + setTabDrawerHeight((prev = 0) => prev - layoutHeight) + } + }, [layoutHeight, setTabDrawerHeight]) + + return handleLayout +}