Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 41 additions & 141 deletions src/__swaps__/screens/Swap/components/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,160 +1,60 @@
import React, { useCallback, useMemo } from 'react';
import { StyleSheet } from 'react-native';
import Animated, {
runOnJS,
runOnUI,
useAnimatedProps,
useAnimatedReaction,
useDerivedValue,
useSharedValue,
} from 'react-native-reanimated';
import { Input } from '@/components/inputs';
import { Bleed, Box, Column, Columns, Text, useColorMode, useForegroundColor } from '@/design-system';
import * as i18n from '@/languages';
import Routes from '@/navigation/routesNames';
import { useUserAssetsStore } from '@/state/assets/userAssets';
import { useNavigationStore } from '@/state/navigation/navigationStore';
import React from 'react';
import { useDerivedValue } from 'react-native-reanimated';
import { SearchInput as BaseSearchInput } from '@/components/token-search/SearchInput';
import { NavigationSteps, useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider';
import { opacity } from '@/framework/ui/utils/opacity';
import { useSwapsSearchStore } from '../resources/search/searchV2';
import { SearchInputButton } from './SearchInputButton';
import { LIGHT_SEPARATOR_COLOR, SEPARATOR_COLOR, THICK_BORDER_WIDTH } from '@/styles/constants';
import { TOKEN_SEARCH_INPUT_HORIZONTAL_PADDING } from '@/components/token-search/constants';

const AnimatedInput = Animated.createAnimatedComponent(Input);
import { useSwapsSearchStore } from '@/__swaps__/screens/Swap/resources/search/searchV2';
import { useUserAssetsStore } from '@/state/assets/userAssets';
import * as i18n from '@/languages';

const FIND_A_TOKEN_TO_BUY_LABEL = i18n.t(i18n.l.swap.find_a_token_to_buy);
const SEARCH_YOUR_TOKENS_LABEL = i18n.t(i18n.l.swap.search_your_tokens);

const onOutputSearchQueryChange = (text: string) => useSwapsSearchStore.setState({ searchQuery: text });

export const SearchInput = ({
handleExitSearchWorklet,
handleFocusSearchWorklet,
onCancelOrClosePressWorklet,
onSearchFocusWorklet,
output,
}: {
handleExitSearchWorklet: () => void;
handleFocusSearchWorklet: () => void;
onCancelOrClosePressWorklet: () => void;
onSearchFocusWorklet: () => void;
output: boolean;
}) => {
const { isDarkMode } = useColorMode();
const { inputProgress, inputSearchRef, outputProgress, outputSearchRef } = useSwapContext();
const animatedActiveRoute = useNavigationStore(state => state.animatedActiveRoute);

const fillTertiary = useForegroundColor('fillTertiary');
const label = useForegroundColor('label');
const labelQuaternary = useForegroundColor('labelQuaternary');
const {
inputProgress,
inputSearchRef,
outputProgress,
outputSearchRef,
internalSelectedInputAsset,
internalSelectedOutputAsset,
AnimatedSwapStyles,
} = useSwapContext();

const onInputSearchQueryChange = useUserAssetsStore(state => state.setSearchQuery);

const isSearchFocused = useDerivedValue(
() =>
(!output && inputProgress.value === NavigationSteps.SEARCH_FOCUSED) ||
(output && outputProgress.value === NavigationSteps.SEARCH_FOCUSED)
);

const pastedSearchInputValue = useSharedValue('');
const searchInputValue = useAnimatedProps(() => {
// Removing the value when the input is focused allows the input to be reset to the correct value on blur
const query = isSearchFocused.value ? undefined : '';
return {
text: pastedSearchInputValue.value || query,
defaultValue: '',
};
});

const { inputStyles, placeholderTextColor } = useMemo(
() => ({
inputStyles: [styles.animatedInput, { color: label }],
placeholderTextColor: isDarkMode ? opacity(labelQuaternary, 0.3) : labelQuaternary,
}),
[isDarkMode, label, labelQuaternary]
);

const onFocus = useCallback(() => runOnUI(handleFocusSearchWorklet)(), [handleFocusSearchWorklet]);

const onCloseNetworkSelector = useCallback(() => {
if (output) outputSearchRef.current?.focus();
else inputSearchRef.current?.focus();
}, [inputSearchRef, output, outputSearchRef]);

const networkSelectorRoute = Routes.NETWORK_SELECTOR;

useAnimatedReaction(
() => ({
isFocused: isSearchFocused.value,
isNetworkSelectorOpen: animatedActiveRoute.value === networkSelectorRoute,
}),
(current, previous) => {
if (previous?.isFocused && !current.isFocused) {
pastedSearchInputValue.value = '';
if (output) runOnJS(onOutputSearchQueryChange)('');
else runOnJS(onInputSearchQueryChange)('');
}
if (previous?.isNetworkSelectorOpen && !current.isNetworkSelectorOpen && current.isFocused) {
runOnJS(onCloseNetworkSelector)();
}
},
[]
);
const progress = output ? outputProgress : inputProgress;
const isSearchFocused = useDerivedValue(() => progress.value === NavigationSteps.SEARCH_FOCUSED);
const isTokenListFocused = useDerivedValue(() => progress.value === NavigationSteps.TOKEN_LIST_FOCUSED);
const isAssetSelected = useDerivedValue(() => !!(output ? internalSelectedOutputAsset.value : internalSelectedInputAsset.value));

return (
<Box paddingHorizontal={{ custom: TOKEN_SEARCH_INPUT_HORIZONTAL_PADDING }} width="full">
<Columns alignHorizontal="justify" alignVertical="center" space="20px">
<Box>
<Bleed horizontal="8px" vertical="24px">
<Box paddingHorizontal="8px" paddingVertical="20px">
<Columns alignVertical="center" space="10px">
<Column width="content">
<Box
alignItems="center"
borderRadius={18}
height={{ custom: 36 }}
justifyContent="center"
style={{
backgroundColor: isDarkMode ? 'transparent' : opacity(fillTertiary, 0.03),
borderColor: isDarkMode ? SEPARATOR_COLOR : opacity(LIGHT_SEPARATOR_COLOR, 0.01),
borderWidth: THICK_BORDER_WIDTH,
}}
width={{ custom: 36 }}
>
<Text align="center" color="labelQuaternary" size="icon 17px" weight="bold">
􀊫
</Text>
</Box>
</Column>
<AnimatedInput
animatedProps={searchInputValue}
onChangeText={output ? onOutputSearchQueryChange : onInputSearchQueryChange}
onFocus={onFocus}
placeholder={output ? FIND_A_TOKEN_TO_BUY_LABEL : SEARCH_YOUR_TOKENS_LABEL}
placeholderTextColor={placeholderTextColor}
ref={output ? outputSearchRef : inputSearchRef}
spellCheck={false}
style={inputStyles}
/>
</Columns>
</Box>
</Bleed>
</Box>
<Column width="content">
<SearchInputButton
handleExitSearchWorklet={handleExitSearchWorklet}
isSearchFocused={isSearchFocused}
output={output}
pastedSearchInputValue={pastedSearchInputValue}
/>
</Column>
</Columns>
</Box>
<BaseSearchInput
onCancelOrClosePressWorklet={onCancelOrClosePressWorklet}
onSearchFocusWorklet={onSearchFocusWorklet}
onSearchQueryChange={output ? onOutputSearchQueryChange : onInputSearchQueryChange}
placeholder={output ? FIND_A_TOKEN_TO_BUY_LABEL : SEARCH_YOUR_TOKENS_LABEL}
enablePaste={output}
showButtonWhenNoAsset={output}
isSearchFocused={isSearchFocused}
isTokenListFocused={isTokenListFocused}
searchInputRef={output ? outputSearchRef : inputSearchRef}
isAssetSelected={isAssetSelected}
animatedButtonStyles={{
buttonWrapperStyle: output
? AnimatedSwapStyles.searchOutputAssetButtonWrapperStyle
: AnimatedSwapStyles.searchInputAssetButtonWrapperStyle,
buttonStyle: output ? AnimatedSwapStyles.searchOutputAssetButtonStyle : AnimatedSwapStyles.searchInputAssetButtonStyle,
}}
/>
);
};

const styles = StyleSheet.create({
animatedInput: {
fontSize: 17,
fontWeight: 'bold',
height: 44,
zIndex: 10,
},
});
4 changes: 2 additions & 2 deletions src/__swaps__/screens/Swap/components/SwapInputAsset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ export function SwapInputAsset() {
width={{ custom: INPUT_INNER_WIDTH }}
>
<TokenList
handleExitSearchWorklet={SwapNavigation.handleExitSearch}
handleFocusSearchWorklet={SwapNavigation.handleFocusInputSearch}
onCancelOrClosePressWorklet={SwapNavigation.handleExitSearch}
onSearchFocusWorklet={SwapNavigation.handleFocusInputSearch}
output={false}
/>
</Box>
Expand Down
4 changes: 2 additions & 2 deletions src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ export function SwapOutputAsset() {
width={{ custom: INPUT_INNER_WIDTH }}
>
<TokenList
handleExitSearchWorklet={SwapNavigation.handleExitSearch}
handleFocusSearchWorklet={SwapNavigation.handleFocusOutputSearch}
onCancelOrClosePressWorklet={SwapNavigation.handleExitSearch}
onSearchFocusWorklet={SwapNavigation.handleFocusOutputSearch}
output
/>
</Box>
Expand Down
7 changes: 5 additions & 2 deletions src/__swaps__/screens/Swap/components/TokenList/ListEmpty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { TIMING_CONFIGS } from '@/components/animations/animationConfigs';
import { Box, Stack, Text } from '@/design-system';
import * as i18n from '@/languages';
import { swapsStore } from '@/state/swaps/swapsStore';
import { EXPANDED_INPUT_HEIGHT, FOCUSED_INPUT_HEIGHT } from '../../constants';
import { EXPANDED_INPUT_HEIGHT } from '../../constants';
import { useSwapContext } from '../../providers/swap-provider';
import { BUY_LIST_HEADER_HEIGHT } from './TokenToBuyList';
import { SELL_LIST_HEADER_HEIGHT } from './TokenToSellList';
import { isL2Chain } from '@/handlers/web3';
import { TOKEN_SEARCH_FOCUSED_INPUT_HEIGHT } from '@/components/token-search/constants';

type ListEmptyProps = {
action?: 'swap' | 'bridge';
Expand All @@ -26,7 +27,9 @@ export const ListEmpty = memo(function ListEmpty({ action = 'swap', output = fal
const isFocused = output ? outputProgress.value === 2 : inputProgress.value === 2;
return {
height: withTiming(
(isFocused ? FOCUSED_INPUT_HEIGHT : EXPANDED_INPUT_HEIGHT) - 120 - (output ? BUY_LIST_HEADER_HEIGHT : SELL_LIST_HEADER_HEIGHT),
(isFocused ? TOKEN_SEARCH_FOCUSED_INPUT_HEIGHT : EXPANDED_INPUT_HEIGHT) -
120 -
(output ? BUY_LIST_HEADER_HEIGHT : SELL_LIST_HEADER_HEIGHT),
TIMING_CONFIGS.slowerFadeConfig
),
};
Expand Down
15 changes: 8 additions & 7 deletions src/__swaps__/screens/Swap/components/TokenList/TokenList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,28 @@ import { SearchInput } from '@/__swaps__/screens/Swap/components/SearchInput';
import { TokenToSellList } from '@/__swaps__/screens/Swap/components/TokenList/TokenToSellList';
import { TokenToBuyList } from '@/__swaps__/screens/Swap/components/TokenList/TokenToBuyList';
import { DEVICE_WIDTH } from '@/utils/deviceUtils';
import { NAVBAR_HEIGHT_WITH_PADDING } from '@/components/navbar/constants';

export const TokenList = ({
handleExitSearchWorklet,
handleFocusSearchWorklet,
onCancelOrClosePressWorklet,
onSearchFocusWorklet,
output,
}: {
handleExitSearchWorklet: () => void;
handleFocusSearchWorklet: () => void;
onCancelOrClosePressWorklet: () => void;
onSearchFocusWorklet: () => void;
output: boolean;
}) => {
return (
<Stack>
<Stack space="20px">
<SearchInput
handleExitSearchWorklet={handleExitSearchWorklet}
handleFocusSearchWorklet={handleFocusSearchWorklet}
onCancelOrClosePressWorklet={onCancelOrClosePressWorklet}
onSearchFocusWorklet={onSearchFocusWorklet}
output={output}
/>
<Separator color="separatorTertiary" thickness={1} />
</Stack>
<Box style={{ height: EXPANDED_INPUT_HEIGHT - 77, width: DEVICE_WIDTH - 24 }}>
<Box style={{ height: EXPANDED_INPUT_HEIGHT - NAVBAR_HEIGHT_WITH_PADDING, width: DEVICE_WIDTH - 24 }}>
{output ? <TokenToBuyList /> : <TokenToSellList />}
</Box>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ import { parseSearchAsset } from '@/__swaps__/utils/assets';
import { getChainColorWorklet } from '@/__swaps__/utils/swaps';
import { DEVICE_WIDTH } from '@/utils/deviceUtils';
import { getUniqueId } from '@/utils/ethereumUtils';
import { EXPANDED_INPUT_HEIGHT, FOCUSED_INPUT_HEIGHT } from '../../constants';
import { EXPANDED_INPUT_HEIGHT } from '../../constants';
import { useSwapsSearchStore } from '../../resources/search/searchV2';
import { ChainSelection } from './ChainSelection';
import { TOKEN_SEARCH_FOCUSED_INPUT_HEIGHT } from '@/components/token-search/constants';

export const BUY_LIST_HEADER_HEIGHT = 20 + 10 + 10; // paddingTop + height + paddingBottom

const SCROLL_INDICATOR_INSETS = {
focused: {
bottom: 28 + (EXPANDED_INPUT_HEIGHT - FOCUSED_INPUT_HEIGHT),
bottom: 28 + (EXPANDED_INPUT_HEIGHT - TOKEN_SEARCH_FOCUSED_INPUT_HEIGHT),
},
unfocused: {
bottom: 28,
Expand Down Expand Up @@ -164,7 +165,7 @@ export const TokenToBuyList = () => {

const animatedListPadding = useAnimatedStyle(() => {
const isFocused = outputProgress.value === 2;
const bottomPadding = isFocused ? EXPANDED_INPUT_HEIGHT - FOCUSED_INPUT_HEIGHT : 0;
const bottomPadding = isFocused ? EXPANDED_INPUT_HEIGHT - TOKEN_SEARCH_FOCUSED_INPUT_HEIGHT : 0;
return { height: bottomPadding };
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ import { ParsedSearchAsset, UniqueId } from '@/__swaps__/types/assets';
import { SwapAssetType } from '@/__swaps__/types/swap';
import { DEVICE_WIDTH } from '@/utils/deviceUtils';
import { getUniqueId } from '@/utils/ethereumUtils';
import { EXPANDED_INPUT_HEIGHT, FOCUSED_INPUT_HEIGHT } from '../../constants';
import { EXPANDED_INPUT_HEIGHT } from '../../constants';
import { ChainSelection } from './ChainSelection';
import { shallowEqual } from '@/worklets/comparisons';
import { NAVBAR_HEIGHT_WITH_PADDING } from '@/components/navbar/constants';
import { TOKEN_SEARCH_FOCUSED_INPUT_HEIGHT } from '@/components/token-search/constants';

export const SELL_LIST_HEADER_HEIGHT = 20 + 10 + 14; // paddingTop + height + paddingBottom

const SCROLL_INDICATOR_INSETS = {
focused: {
bottom: 28 + (EXPANDED_INPUT_HEIGHT - FOCUSED_INPUT_HEIGHT),
bottom: 28 + (EXPANDED_INPUT_HEIGHT - TOKEN_SEARCH_FOCUSED_INPUT_HEIGHT),
},
unfocused: {
bottom: 28,
Expand Down Expand Up @@ -96,7 +97,7 @@ const TokenToSellListComponent = () => {

const animatedListPadding = useAnimatedStyle(() => {
const isFocused = inputProgress.value === 2;
const bottomPadding = isFocused ? EXPANDED_INPUT_HEIGHT - FOCUSED_INPUT_HEIGHT : 0;
const bottomPadding = isFocused ? EXPANDED_INPUT_HEIGHT - TOKEN_SEARCH_FOCUSED_INPUT_HEIGHT : 0;
return { height: bottomPadding };
});

Expand Down Expand Up @@ -146,7 +147,7 @@ const styles = StyleSheet.create({
paddingBottom: 16,
},
list: {
height: EXPANDED_INPUT_HEIGHT - 77,
height: EXPANDED_INPUT_HEIGHT - NAVBAR_HEIGHT_WITH_PADDING,
width: DEVICE_WIDTH - 24,
},
listFooter: {
Expand Down
5 changes: 2 additions & 3 deletions src/__swaps__/screens/Swap/constants.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Easing } from 'react-native-reanimated';
import { IS_ANDROID, IS_IOS } from '@/env';
import { getDefaultKeyboardHeight } from '@/redux/keyboardHeight';
import { THICK_BORDER_WIDTH } from '@/styles/constants';
import deviceUtils, { DEVICE_HEIGHT, NAVIGATION_BAR_HEIGHT } from '@/utils/deviceUtils';
import safeAreaInsetValues from '@/utils/safeAreaInsetValues';
import { buildTestSafeConfig } from '@/components/animations/animationConfigs';
import { NAVBAR_HEIGHT_WITH_PADDING } from '@/components/navbar/constants';
import { getDefaultKeyboardHeight } from '@/utils/keyboardHeight';

// /---- 🔒 Constants 🔒 ----/ //
//
export const CUSTOM_KEYBOARD_HEIGHT = 209;
export const NAVBAR_HEIGHT_WITH_PADDING = 77;
export const SPACE_BETWEEN_SWAP_BUBBLES = 12;

// TODO: Need a more reliable way to get the keyboard height
Expand All @@ -35,7 +35,6 @@ export const EXPANDED_INPUT_HEIGHT =
BASE_INPUT_HEIGHT -
SPACE_BETWEEN_SWAP_BUBBLES -
Math.max(IS_IOS ? safeAreaInsetValues.bottom : NAVIGATION_BAR_HEIGHT, SPACE_BETWEEN_SWAP_BUBBLES);
export const FOCUSED_INPUT_HEIGHT = deviceUtils.dimensions.height - safeAreaInsetValues.top - 20 - NATIVE_KEYBOARD_HEIGHT;
export const INPUT_INNER_WIDTH = BASE_INPUT_WIDTH - THICK_BORDER_WIDTH * 2;

export const SLIDER_HEIGHT = 16;
Expand Down
Loading
Loading