Skip to content

Commit

Permalink
feat(mobile): bottom sheet flashlist (#14681)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nodonisko authored and komret committed Nov 14, 2024
1 parent 662b1a7 commit 781d07a
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 34 deletions.
1 change: 0 additions & 1 deletion suite-native/accounts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"dependencies": {
"@mobily/ts-belt": "^3.13.1",
"@react-navigation/native": "6.1.18",
"@shopify/flash-list": "1.7.1",
"@suite-common/formatters": "workspace:*",
"@suite-common/icons-deprecated": "workspace:*",
"@suite-common/token-definitions": "workspace:*",
Expand Down
35 changes: 17 additions & 18 deletions suite-native/accounts/src/components/AccountSelectBottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import React, { useCallback } from 'react';

import { FlashList } from '@shopify/flash-list';

import { BottomSheet } from '@suite-native/atoms';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { useToast } from '@suite-native/toasts';
import { BottomSheetFlashList } from '@suite-native/atoms';
import { ToastRenderer, useToast } from '@suite-native/toasts';
import { Translation } from '@suite-native/intl';

import { AccountSelectBottomSheetSection, OnSelectAccount } from '../types';
import { AccountsListItem } from './AccountsList/AccountsListItem';
Expand All @@ -19,9 +17,7 @@ type AccountSelectBottomSheetProps = {
onClose: () => void;
};

const contentContainerStyle = prepareNativeStyle(utils => ({
paddingHorizontal: utils.spacings.sp16,
}));
const ESTIMATED_ITEM_SIZE = 76;

export const AccountSelectBottomSheet = React.memo(
({
Expand All @@ -30,7 +26,6 @@ export const AccountSelectBottomSheet = React.memo(
isStakingPressable = false,
onClose,
}: AccountSelectBottomSheetProps) => {
const { applyStyle } = useNativeStyles();
const { showToast } = useToast();

const renderItem = useCallback(
Expand Down Expand Up @@ -63,7 +58,9 @@ export const AccountSelectBottomSheet = React.memo(
} else {
showToast({
variant: 'warning',
message: 'Staking is not available in this context.',
message: (
<Translation id="accountList.stakingDisabled" />
),
});
}
}}
Expand Down Expand Up @@ -93,14 +90,16 @@ export const AccountSelectBottomSheet = React.memo(
);

return (
<BottomSheet isVisible isCloseDisplayed={false} onClose={onClose} isScrollable={false}>
<FlashList
data={data}
renderItem={renderItem}
contentContainerStyle={applyStyle(contentContainerStyle)}
estimatedItemSize={76}
/>
</BottomSheet>
<BottomSheetFlashList<AccountSelectBottomSheetSection>
isVisible
isCloseDisplayed={false}
onClose={onClose}
data={data}
renderItem={renderItem}
estimatedItemSize={ESTIMATED_ITEM_SIZE}
estimatedListHeight={ESTIMATED_ITEM_SIZE * data.length}
ExtraProvider={ToastRenderer}
/>
);
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const accountListItemStyle = prepareNativeStyle<{
style: {
borderBottomLeftRadius: utils.borders.radii.r16,
borderBottomRightRadius: utils.borders.radii.r16,
marginBottom: utils.spacings.sp32,
marginBottom: utils.spacings.sp16,
...utils.boxShadows.small,
},
},
Expand Down
2 changes: 1 addition & 1 deletion suite-native/accounts/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const selectAccountFiatBalance = (state: NativeAccountsRootState, account
return totalBalance;
};

const EMPTY_ARRAY: any[] = [];
const EMPTY_ARRAY: AccountSelectBottomSheetSection[] = [];

export const selectAccountListSections = memoizeWithArgs(
(state: NativeAccountsRootState, accountKey?: AccountKey | null, hideStaking?: boolean) => {
Expand Down
1 change: 1 addition & 0 deletions suite-native/atoms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"dependencies": {
"@mobily/ts-belt": "^3.13.1",
"@shopify/flash-list": "1.7.1",
"@shopify/react-native-skia": "1.3.11",
"@suite-common/icons-deprecated": "workspace:*",
"@suite-native/config": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion suite-native/atoms/src/Sheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const BottomSheet = ({
<Pressable style={applyStyle(sheetWithOverlayStyle)} onPress={handlePressOutside}>
<PanGestureHandler
enabled={isCloseScrollEnabled}
ref={panGestureRef.current}
ref={panGestureRef}
activeOffsetY={5}
failOffsetY={-5}
onGestureEvent={panGestureEvent}
Expand Down
28 changes: 19 additions & 9 deletions suite-native/atoms/src/Sheet/BottomSheetContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type SheetProps = {
children: ReactNode;
isVisible: boolean;
onClose: () => void;
ExtraProvider?: React.ComponentType;
};

const ContentWrapperStyle = prepareNativeStyle(_ => ({ flex: 1 }));
Expand All @@ -21,19 +22,28 @@ const BottomSheetGestureHandler = gestureHandlerRootHOC<{ children: ReactNode }>
<>{children}</>
));

export const BottomSheetContainer = ({ children, isVisible, onClose }: SheetProps) => {
export const BottomSheetContainer = ({
children,
isVisible,
onClose,
ExtraProvider,
}: SheetProps) => {
const { applyStyle } = useNativeStyles();

return (
<RNModal transparent visible={isVisible} onRequestClose={onClose}>
<BottomSheetGestureHandler>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={applyStyle(ContentWrapperStyle)}
>
{children}
</KeyboardAvoidingView>
</BottomSheetGestureHandler>
<>
<BottomSheetGestureHandler>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={applyStyle(ContentWrapperStyle)}
>
{children}
</KeyboardAvoidingView>
</BottomSheetGestureHandler>

{ExtraProvider && <ExtraProvider />}
</>
</RNModal>
);
};
145 changes: 145 additions & 0 deletions suite-native/atoms/src/Sheet/BottomSheetFlashList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { useEffect, useRef, ReactNode } from 'react';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Animated from 'react-native-reanimated';
import { PanGestureHandler } from 'react-native-gesture-handler';
import { GestureResponderEvent, Pressable, Dimensions } from 'react-native';

import { FlashList, FlashListProps } from '@shopify/flash-list';

import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';

import { Box } from '../Box';
import { BottomSheetContainer } from './BottomSheetContainer';
import { useBottomSheetAnimation } from './useBottomSheetAnimation';
import { BottomSheetHeader } from './BottomSheetHeader';

export type BottomSheetFlashListProps<TItem> = {
isVisible: boolean;
isCloseDisplayed?: boolean;
onClose: (isVisible: boolean) => void;
title?: ReactNode;
subtitle?: ReactNode;
ExtraProvider?: React.ComponentType;
estimatedListHeight?: number;
} & FlashListProps<TItem>;

const DEFAULT_INSET_BOTTOM = 50;

const sheetWrapperStyle = prepareNativeStyle(utils => ({
backgroundColor: utils.colors.backgroundSurfaceElevation0,
borderTopLeftRadius: utils.borders.radii.r20,
borderTopRightRadius: utils.borders.radii.r20,
maxHeight: '80%',
}));

const sheetContentContainerStyle = prepareNativeStyle<{
insetBottom: number;
}>((utils, { insetBottom }) => ({
paddingBottom: Math.max(insetBottom, utils.spacings.sp16),
paddingHorizontal: utils.spacings.sp16,
}));

const sheetWithOverlayStyle = prepareNativeStyle(_ => ({
flex: 1,
justifyContent: 'flex-end',
}));

export const BottomSheetFlashList = <TItem,>({
isVisible,
isCloseDisplayed = true,
onClose,
title,
subtitle,
ExtraProvider,
estimatedListHeight = 0,
...flashListProps
}: BottomSheetFlashListProps<TItem>) => {
const { applyStyle } = useNativeStyles();
const insets = useSafeAreaInsets();
const {
animatedSheetWithOverlayStyle,
animatedSheetWrapperStyle,
closeSheetAnimated,
openSheetAnimated,
panGestureEvent,
scrollEvent,
} = useBottomSheetAnimation({
onClose,
isVisible,
});
const panGestureRef = useRef(null);
const scrollViewRef = useRef(null);

useEffect(() => {
if (isVisible) {
openSheetAnimated();
}
}, [isVisible, openSheetAnimated]);

const handlePressOutside = (event: GestureResponderEvent) => {
if (event.target === event.currentTarget) closeSheetAnimated();
};

const insetBottom = Math.max(insets.bottom, DEFAULT_INSET_BOTTOM);

return (
<BottomSheetContainer
isVisible={isVisible}
onClose={closeSheetAnimated}
ExtraProvider={ExtraProvider}
>
<Animated.View
style={[animatedSheetWithOverlayStyle, applyStyle(sheetWithOverlayStyle)]}
>
<Pressable style={applyStyle(sheetWithOverlayStyle)} onPress={handlePressOutside}>
<PanGestureHandler
ref={panGestureRef}
activeOffsetY={5}
failOffsetY={-5}
onGestureEvent={panGestureEvent}
>
<Animated.View
style={[
animatedSheetWrapperStyle,
applyStyle(sheetWrapperStyle, {
insetBottom,
}),
]}
>
<BottomSheetHeader
title={title}
subtitle={subtitle}
isCloseDisplayed={isCloseDisplayed}
onCloseSheet={closeSheetAnimated}
/>

<Box
style={{
// only estimated height is used, so we need to add some buffer to prevent unnecessary scrolling, when list is shorter than estimated
height: Math.max(
estimatedListHeight * 1.1,
// We use this because minHeight doesn't work with `height set
Dimensions.get('window').height * 0.35,
),
maxHeight: '100%',
}}
>
<FlashList
{...flashListProps}
overrideProps={{
simultaneousHandlers: panGestureRef,
}}
contentContainerStyle={applyStyle(sheetContentContainerStyle, {
insetBottom,
})}
ref={scrollViewRef}
onScroll={scrollEvent}
/>
</Box>
</Animated.View>
</PanGestureHandler>
</Pressable>
</Animated.View>
</BottomSheetContainer>
);
};
1 change: 1 addition & 0 deletions suite-native/atoms/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from './DiscreetText/useDiscreetMode';
export * from './BottomSheetListItem';
export * from './OrderedListIcon';
export * from './Sheet/BottomSheet';
export * from './Sheet/BottomSheetFlashList';
export * from './Sheet/useBottomSheetAnimation';
export * from './Button/Button';
export * from './Button/IconButton';
Expand Down
1 change: 1 addition & 0 deletions suite-native/intl/src/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export const en = {
numberOfTokens: '+{numberOfTokens, plural, one{1 Token} other{# Tokens}}',
tokens: 'Tokens',
staking: 'Staking',
stakingDisabled: 'Staking is not available in this context.',
},
assets: {
dashboard: {
Expand Down
12 changes: 11 additions & 1 deletion suite-native/module-accounts-import/src/components/DevXpub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,17 @@ type DevXpubProps = {
};

const devXpubs: Partial<Record<NetworkSymbol, string | DevXpub[]>> = {
btc: 'zpub6rjNNddoAVvuYaD6WPdxiqFEToQHgrERjWMg7kM9gGGk6rhPMWNEmL5X745FGqBq8Wp136LfA3A7UjRGEYdJrf8dUfshzNrb5rvaryNfVJf',
btc: [
{
title: 'Use dev xPub',
address:
'zpub6rjNNddoAVvuYaD6WPdxiqFEToQHgrERjWMg7kM9gGGk6rhPMWNEmL5X745FGqBq8Wp136LfA3A7UjRGEYdJrf8dUfshzNrb5rvaryNfVJf',
},
{
title: 'Taproot (zero value)',
address: `tr([5c9e228d/86'/0'/0']xpub6Bw885JisRbcKmowfBvMmCxaFHodKn1VpmRmctmJJoM8D4DzyP4qJv8ZdD9V9r3SSGjmK2KJEDnvLH6f1Q4HrobEvnCeKydNvf1eir3RHZk/<0;1>/*)`,
},
],
test: 'vpub5ZjRPuuMiEQnbwEDi9jtH1FaJMajZW78uZ1t3RJXKhxyMoTnPraKwGxiDo9SguDYvSieqjoLJxW5n2t9156RR1oeqRnURuftNZTzejBc4pa',
regtest:
'vpub5ZjRPuuMiEQnbwEDi9jtH1FaJMajZW78uZ1t3RJXKhxyMoTnPraKwGxiDo9SguDYvSieqjoLJxW5n2t9156RR1oeqRnURuftNZTzejBc4pa',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ export const AccountsScreen = () => {

const [accountsFilterValue, setAccountsFilterValue] = useState<string>('');

const handleSelectAccount: OnSelectAccount = ({ account, tokenAddress }) => {
const handleSelectAccount: OnSelectAccount = ({ account, tokenAddress, isStaking }) => {
if (isStaking) {
navigation.navigate(RootStackRoutes.StakingDetail, {
accountKey: account.key,
});

return;
}
navigation.navigate(RootStackRoutes.AccountDetail, {
accountKey: account.key,
tokenContract: tokenAddress,
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9472,7 +9472,6 @@ __metadata:
dependencies:
"@mobily/ts-belt": "npm:^3.13.1"
"@react-navigation/native": "npm:6.1.18"
"@shopify/flash-list": "npm:1.7.1"
"@suite-common/formatters": "workspace:*"
"@suite-common/icons-deprecated": "workspace:*"
"@suite-common/token-definitions": "workspace:*"
Expand Down Expand Up @@ -9685,6 +9684,7 @@ __metadata:
resolution: "@suite-native/atoms@workspace:suite-native/atoms"
dependencies:
"@mobily/ts-belt": "npm:^3.13.1"
"@shopify/flash-list": "npm:1.7.1"
"@shopify/react-native-skia": "npm:1.3.11"
"@suite-common/icons-deprecated": "workspace:*"
"@suite-native/config": "workspace:*"
Expand Down

0 comments on commit 781d07a

Please sign in to comment.