From ef24b41fbab61e1c59234bdabe5251fa976e0d0a Mon Sep 17 00:00:00 2001 From: neven-s Date: Fri, 9 Dec 2022 20:03:28 -0500 Subject: [PATCH] CP-4107 Use Flash or Flat list depending on feature flag (#524) * Fix ZeroStateNoWatchlistFavorites design * Remove unnecessary SafeAreaProvider * Select Flash or Flat list depending on feature flag * Fix merge conflicts * Remove unused style --- app/components/AvaFlashList.tsx | 99 +++++++++++++++++++ app/components/ZeroState.tsx | 23 +++-- app/contexts/PosthogContext/index.tsx | 15 ++- app/contexts/PosthogContext/types.ts | 3 +- .../shared/ActivityList/Transactions.tsx | 26 +++-- app/screens/watchlist/WatchlistView.tsx | 65 +++++------- .../watchlist/components/WatchList.tsx | 33 +++++-- 7 files changed, 199 insertions(+), 65 deletions(-) create mode 100644 app/components/AvaFlashList.tsx diff --git a/app/components/AvaFlashList.tsx b/app/components/AvaFlashList.tsx new file mode 100644 index 0000000000..9d7ad59f7c --- /dev/null +++ b/app/components/AvaFlashList.tsx @@ -0,0 +1,99 @@ +import { FlashList } from '@shopify/flash-list' +import React from 'react' +import { + ContentStyle, + ListRenderItem as FlashListRenderItem +} from '@shopify/flash-list/dist/FlashListProps' +import { usePosthogContext } from 'contexts/PosthogContext' +import { + FlatList, + ListRenderItem as FlatListRenderItem, + Platform, + RefreshControlProps +} from 'react-native' + +interface AvaFlashListProps { + data: ReadonlyArray | null | undefined + flashRenderItem: FlashListRenderItem | null | undefined + flatRenderItem: FlatListRenderItem | null | undefined + ItemSeparatorComponent?: React.ComponentType | null | undefined + ListEmptyComponent?: + | React.ComponentType + | React.ReactElement + | null + | undefined + refreshing?: boolean | null | undefined + onRefresh?: (() => void) | null | undefined + keyExtractor?: ((item: TItem, index: number) => string) | undefined + extraData?: unknown + contentContainerStyle?: ContentStyle + onEndReached?: (() => void) | null | undefined + onEndReachedThreshold?: number | null | undefined + refreshControl?: React.ReactElement | undefined + getItemType?: ( + item: TItem, + index: number, + extraData?: unknown + ) => string | number | undefined + estimatedItemSize?: number | undefined +} + +/** + * This component just selects between Flash and Flat list depending on feature flag + */ +const AvaFlashList = ({ + data, + flashRenderItem, + flatRenderItem, + ItemSeparatorComponent, + ListEmptyComponent, + refreshing, + onRefresh, + keyExtractor, + extraData, + contentContainerStyle, + onEndReached, + onEndReachedThreshold, + refreshControl, + getItemType, + estimatedItemSize +}: AvaFlashListProps) => { + const { useFlatListAndroid } = usePosthogContext() + + return useFlatListAndroid && Platform.OS === 'android' ? ( + + ) : ( + + ) +} + +export default AvaFlashList diff --git a/app/components/ZeroState.tsx b/app/components/ZeroState.tsx index 05362cf433..2f12b96e01 100644 --- a/app/components/ZeroState.tsx +++ b/app/components/ZeroState.tsx @@ -162,16 +162,27 @@ function ZeroStateNoTransactions() { return } -function ZeroStateNoWatchlistFavorites() { +function ZeroStateNoWatchlistFavorites({ + exploreAllTokens +}: { + exploreAllTokens?: () => void +}) { const title = 'No Favorites' const message = 'Click the star icon on any token to mark it as a favorite.' return ( - } - /> + + } + button={ + + Explore all tokens + + } + /> + ) } diff --git a/app/contexts/PosthogContext/index.tsx b/app/contexts/PosthogContext/index.tsx index ea03b070e4..18e7aaf5be 100644 --- a/app/contexts/PosthogContext/index.tsx +++ b/app/contexts/PosthogContext/index.tsx @@ -71,6 +71,7 @@ export interface PosthogContextState { sendNftBlockediOS: boolean sendNftBlockedAndroid: boolean sentrySampleRate: number + useFlatListAndroid: boolean } const DefaultFeatureFlagConfig = { @@ -83,7 +84,8 @@ const DefaultFeatureFlagConfig = { [FeatureGates.SEND]: true, [FeatureGates.SEND_NFT_IOS]: true, [FeatureGates.SEND_NFT_ANDROID]: true, - [FeatureVars.SENTRY_SAMPLE_RATE]: '10' // 10% of events/errors + [FeatureVars.SENTRY_SAMPLE_RATE]: '10', // 10% of events/errors + [FeatureGates.USE_FLATLIST_ANDROID]: false } const ONE_MINUTE = 60 * 1000 @@ -116,6 +118,8 @@ const processFlags = (flags: FeatureFlags) => { const sentrySampleRate = parseInt((flags[FeatureVars.SENTRY_SAMPLE_RATE] as string) ?? '0') / 100 + const useFlatListAndroid = !!flags[FeatureGates.USE_FLATLIST_ANDROID] + return { swapBlocked, bridgeBlocked, @@ -125,7 +129,8 @@ const processFlags = (flags: FeatureFlags) => { sendNftBlockediOS, sendNftBlockedAndroid, eventsBlocked, - sentrySampleRate + sentrySampleRate, + useFlatListAndroid } } @@ -159,7 +164,8 @@ export const PosthogContextProvider = ({ sendNftBlockediOS, sendNftBlockedAndroid, eventsBlocked, - sentrySampleRate + sentrySampleRate, + useFlatListAndroid } = useMemo(() => processFlags(flags), [flags]) useEffect( @@ -284,7 +290,8 @@ export const PosthogContextProvider = ({ sendBlocked, sendNftBlockediOS, sendNftBlockedAndroid, - sentrySampleRate + sentrySampleRate, + useFlatListAndroid }}> {children} diff --git a/app/contexts/PosthogContext/types.ts b/app/contexts/PosthogContext/types.ts index dcbc1ba961..cf55b54949 100644 --- a/app/contexts/PosthogContext/types.ts +++ b/app/contexts/PosthogContext/types.ts @@ -9,7 +9,8 @@ export enum FeatureGates { BRIDGE_ETH = 'bridge-feature-eth', SEND = 'send-feature', SEND_NFT_IOS = 'send-nft-ios-feature', - SEND_NFT_ANDROID = 'send-nft-android-feature' + SEND_NFT_ANDROID = 'send-nft-android-feature', + USE_FLATLIST_ANDROID = 'use-flatlist-android' } export enum FeatureVars { diff --git a/app/screens/shared/ActivityList/Transactions.tsx b/app/screens/shared/ActivityList/Transactions.tsx index 34cfc8db32..6f9c8dfc53 100644 --- a/app/screens/shared/ActivityList/Transactions.tsx +++ b/app/screens/shared/ActivityList/Transactions.tsx @@ -1,6 +1,11 @@ import React, { useMemo } from 'react' -import { Animated, View, StyleSheet } from 'react-native' -import { FlashList, ListRenderItemInfo } from '@shopify/flash-list' +import { + Animated, + View, + StyleSheet, + ListRenderItemInfo as FlatListRenderItemInfo +} from 'react-native' +import { ListRenderItemInfo as FlashListRenderItemInfo } from '@shopify/flash-list' import AvaText from 'components/AvaText' import ActivityListItem from 'screens/activity/ActivityListItem' import { @@ -20,6 +25,7 @@ import ZeroState from 'components/ZeroState' import { BridgeTransaction } from '@avalabs/bridge-sdk' import { UI, useIsUIDisabled } from 'hooks/useIsUIDisabled' import { RefreshControl } from 'components/RefreshControl' +import AvaFlashList from 'components/AvaFlashList' const yesterday = endOfYesterday() const today = endOfToday() @@ -125,7 +131,15 @@ const Transactions = ({ ) } - const renderTransaction = ({ item }: ListRenderItemInfo) => { + const flatRenderItem = ({ item }: FlatListRenderItemInfo) => { + return renderListItem(item) + } + + const flashRenderItem = ({ item }: FlashListRenderItemInfo) => { + return renderListItem(item) + } + + function renderListItem(item: Item) { // render section header if (typeof item === 'string') { return renderSectionHeader(item) @@ -173,10 +187,10 @@ const Transactions = ({ const renderTransactions = () => { return ( - = ({ ) return ( - - <> - - + + + + {showLoader ? ( + + ) : ( + <> + onTabIndexChanged?.(1)} /> - - {showLoader ? ( - - ) : ( - <> - - {showFavorites && sortedTokens.length === 0 && ( - onTabIndexChanged?.(1)} - style={{ marginBottom: 128, marginHorizontal: 16 }}> - Explore all tokens - - )} - - )} - - + + )} + ) } const styles = StyleSheet.create({ - container: { - flex: 1 - }, filterContainer: { flexDirection: 'row', justifyContent: 'space-between', diff --git a/app/screens/watchlist/components/WatchList.tsx b/app/screens/watchlist/components/WatchList.tsx index 7f0cc70861..c05b788e95 100644 --- a/app/screens/watchlist/components/WatchList.tsx +++ b/app/screens/watchlist/components/WatchList.tsx @@ -1,6 +1,9 @@ import React from 'react' -import { StyleSheet } from 'react-native' -import { FlashList, ListRenderItemInfo } from '@shopify/flash-list' +import { + StyleSheet, + ListRenderItemInfo as FlatListRenderItemInfo +} from 'react-native' +import { ListRenderItemInfo as FlashListRenderItemInfo } from '@shopify/flash-list' import WatchListItem from 'screens/watchlist/components/WatchListItem' import { useNavigation } from '@react-navigation/native' import AppNavigation from 'navigation/AppNavigation' @@ -19,6 +22,7 @@ import { Prices } from 'store/watchlist' import { formatLargeCurrency } from 'utils/Utils' +import AvaFlashList from 'components/AvaFlashList' import { WatchlistFilter } from '../types' const getDisplayValue = ( @@ -41,6 +45,7 @@ interface Props { filterBy: WatchlistFilter isShowingFavorites?: boolean isSearching?: boolean + onExploreAllTokens?: () => void } type NavigationProp = TabsScreenProps< @@ -53,7 +58,8 @@ const WatchList: React.FC = ({ charts, filterBy, isShowingFavorites, - isSearching + isSearching, + onExploreAllTokens }) => { const navigation = useNavigation() const { currencyFormatter } = useApplicationContext().appHook @@ -61,8 +67,15 @@ const WatchList: React.FC = ({ const keyExtractor = (item: MarketToken) => item.id - const renderItem = (item: ListRenderItemInfo) => { - const token = item.item + const flatListRenderItem = (item: FlatListRenderItemInfo) => { + return renderItem(item.item) + } + + const flashListRenderItem = (item: FlashListRenderItemInfo) => { + return renderItem(item.item) + } + + function renderItem(token: MarketToken) { const chartData = charts[token.id] ?? defaultChartData const price = prices[token.id] ?? defaultPrice const displayValue = getDisplayValue(price, currencyFormatter) @@ -83,13 +96,16 @@ const WatchList: React.FC = ({ } return ( - + ) : ( = ({ refreshing={false} onRefresh={() => dispatch(onWatchlistRefresh)} keyExtractor={keyExtractor} - indicatorStyle="white" estimatedItemSize={64} extraData={{ filterBy