Skip to content

Commit 23e59ea

Browse files
authored
migrate remote cards over to zustand <> react-query sync (#5796)
* migrate remote cards over to zustand <> react-query sync * try to fix placement * bring in promo sheets too * fix lint * more cleanup * write migration to prevent showing old sheets to users * bring back discover search * fix e2e * Update src/resources/cards/cardCollectionQuery.ts * move sync components and memo them * wrap in IM * cleanup discover home and revert scroll throttle change * rm reactive cards in remote card * change refetch interval
1 parent 0e8bf36 commit 23e59ea

33 files changed

+623
-295
lines changed

src/App.js

+8-14
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ import branch from 'react-native-branch';
5353
import { initializeReservoirClient } from '@/resources/reservoir/client';
5454
import { ReviewPromptAction } from '@/storage/schema';
5555
import { handleReviewPromptAction } from '@/utils/reviewAlert';
56-
import { RemotePromoSheetProvider } from '@/components/remote-promo-sheet/RemotePromoSheetProvider';
57-
import { RemoteCardProvider } from '@/components/cards/remote-cards';
5856
import { initializeRemoteConfig } from '@/model/remoteConfig';
5957
import { IS_DEV } from './env';
6058
import { checkIdentifierOnLaunch } from './model/backup';
@@ -147,11 +145,11 @@ class OldApp extends Component {
147145
const address = await loadAddress();
148146

149147
if (address) {
150-
InteractionManager.runAfterInteractions(() => {
151-
setTimeout(() => {
148+
setTimeout(() => {
149+
InteractionManager.runAfterInteractions(() => {
152150
handleReviewPromptAction(ReviewPromptAction.TimesLaunchedSinceInstall);
153-
}, 10_000);
154-
});
151+
});
152+
}, 10_000);
155153

156154
checkIdentifierOnLaunch();
157155
}
@@ -221,14 +219,10 @@ class OldApp extends Component {
221219
<Portal>
222220
<View style={containerStyle}>
223221
{this.state.initialRoute && (
224-
<RemotePromoSheetProvider isWalletReady={this.props.walletReady}>
225-
<RemoteCardProvider>
226-
<InitialRouteContext.Provider value={this.state.initialRoute}>
227-
<RoutesComponent ref={this.handleNavigatorRef} />
228-
<PortalConsumer />
229-
</InitialRouteContext.Provider>
230-
</RemoteCardProvider>
231-
</RemotePromoSheetProvider>
222+
<InitialRouteContext.Provider value={this.state.initialRoute}>
223+
<RoutesComponent ref={this.handleNavigatorRef} />
224+
<PortalConsumer />
225+
</InitialRouteContext.Provider>
232226
)}
233227
<OfflineToast />
234228
</View>

src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx

+7-6
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ import getLayoutProvider from './getLayoutProvider';
1515
import useLayoutItemAnimator from './useLayoutItemAnimator';
1616
import { UniqueAsset } from '@/entities';
1717
import { useRecyclerListViewScrollToTopContext } from '@/navigation/RecyclerListViewScrollToTopContext';
18-
import { useAccountProfile, useAccountSettings, useCoinListEdited, useCoinListEditOptions, useWallets } from '@/hooks';
18+
import { useAccountSettings, useCoinListEdited, useCoinListEditOptions, useWallets } from '@/hooks';
1919
import { useNavigation } from '@/navigation';
2020
import { useTheme } from '@/theme';
21-
import { useRemoteCardContext } from '@/components/cards/remote-cards';
21+
import { remoteCardsStore } from '@/state/remoteCards/remoteCards';
2222
import { useRoute } from '@react-navigation/native';
23+
import Routes from '@/navigation/routesNames';
2324

2425
const dataProvider = new DataProvider((r1, r2) => {
2526
return r1.uid !== r2.uid;
@@ -58,14 +59,14 @@ const RawMemoRecyclerAssetList = React.memo(function RawRecyclerAssetList({
5859
const y = useRecyclerAssetListPosition()!;
5960

6061
const { name } = useRoute();
61-
const { getCardsForPlacement } = useRemoteCardContext();
62+
const getCardIdsForScreen = remoteCardsStore(state => state.getCardIdsForScreen);
6263
const { isReadOnlyWallet } = useWallets();
6364

64-
const cards = useMemo(() => getCardsForPlacement(name as string), [getCardsForPlacement, name]);
65+
const cardIds = useMemo(() => getCardIdsForScreen(name as keyof typeof Routes), [getCardIdsForScreen, name]);
6566

6667
const layoutProvider = useMemo(
67-
() => getLayoutProvider(briefSectionsData, isCoinListEdited, cards, isReadOnlyWallet),
68-
[briefSectionsData, isCoinListEdited, cards, isReadOnlyWallet]
68+
() => getLayoutProvider(briefSectionsData, isCoinListEdited, cardIds, isReadOnlyWallet),
69+
[briefSectionsData, isCoinListEdited, cardIds, isReadOnlyWallet]
6970
);
7071

7172
const { accountAddress } = useAccountSettings();

src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx

+2-7
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,7 @@ class BetterLayoutProvider extends LayoutProvider {
3131
}
3232
}
3333

34-
const getLayoutProvider = (
35-
briefSectionsData: BaseCellType[],
36-
isCoinListEdited: boolean,
37-
cards: TrimmedCard[],
38-
isReadOnlyWallet: boolean
39-
) => {
34+
const getLayoutProvider = (briefSectionsData: BaseCellType[], isCoinListEdited: boolean, cardIds: string[], isReadOnlyWallet: boolean) => {
4035
const indicesToOverride = [];
4136
for (let i = 0; i < briefSectionsData.length; i++) {
4237
const val = briefSectionsData[i];
@@ -61,7 +56,7 @@ const getLayoutProvider = (
6156
dim.height = ViewDimensions[type].height;
6257
dim.width = ViewDimensions[type].width || dim.width;
6358

64-
if ((type === CellType.REMOTE_CARD_CAROUSEL && !cards.length) || (type === CellType.REMOTE_CARD_CAROUSEL && isReadOnlyWallet)) {
59+
if ((type === CellType.REMOTE_CARD_CAROUSEL && !cardIds.length) || (type === CellType.REMOTE_CARD_CAROUSEL && isReadOnlyWallet)) {
6560
dim.height = 0;
6661
}
6762
}

src/components/cards/remote-cards/RemoteCard.tsx

+13-16
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import ConditionalWrap from 'conditional-wrap';
55

66
import { Box, Cover, Stack, Text, useForegroundColor } from '@/design-system';
77
import { ButtonPressAnimation } from '@/components/animations';
8-
import { useRemoteCardContext } from './RemoteCardProvider';
98
import { IS_ANDROID, IS_IOS } from '@/env';
109
import { useNavigation } from '@/navigation';
1110
import { Language } from '@/languages';
@@ -20,6 +19,7 @@ import { FlashList } from '@shopify/flash-list';
2019
import { ButtonPressAnimationTouchEvent } from '@/components/animations/ButtonPressAnimation/types';
2120
import { TrimmedCard } from '@/resources/cards/cardCollectionQuery';
2221
import RemoteSvg from '@/components/svg/RemoteSvg';
22+
import { remoteCardsStore } from '@/state/remoteCards/remoteCards';
2323

2424
const ICON_SIZE = 40;
2525

@@ -58,19 +58,17 @@ const getColorFromString = (color: string | undefined | null) => {
5858
};
5959

6060
type RemoteCardProps = {
61-
card: TrimmedCard;
62-
cards: TrimmedCard[];
61+
id: string;
6362
gutterSize: number;
64-
carouselRef: React.RefObject<FlashList<TrimmedCard>> | null;
63+
carouselRef: React.RefObject<FlashList<string>> | null;
6564
};
6665

67-
export const RemoteCard: React.FC<RemoteCardProps> = ({ card = {} as TrimmedCard, cards, gutterSize, carouselRef }) => {
66+
export const RemoteCard: React.FC<RemoteCardProps> = ({ id, gutterSize, carouselRef }) => {
6867
const { isDarkMode } = useTheme();
6968
const { navigate } = useNavigation();
7069
const { language } = useAccountSettings();
7170
const { width } = useDimensions();
72-
const { dismissCard } = useRemoteCardContext();
73-
71+
const card = remoteCardsStore(state => state.getCard(id)) ?? ({} as TrimmedCard);
7472
const { cardKey, accentColor, backgroundColor, primaryButton, imageIcon } = card;
7573

7674
const accent = useForegroundColor(getColorFromString(accentColor));
@@ -95,26 +93,25 @@ export const RemoteCard: React.FC<RemoteCardProps> = ({ card = {} as TrimmedCard
9593
e.stopPropagation();
9694
}
9795
analyticsV2.track(analyticsV2.event.remoteCardDismissed, {
98-
cardKey: cardKey ?? 'unknown-backend-driven-card',
96+
cardKey: cardKey ?? card.sys.id ?? 'unknown-backend-driven-card',
9997
});
10098

101-
const isLastCard = cards.length === 1;
99+
const { cards } = remoteCardsStore.getState();
102100

103-
dismissCard(card.sys.id);
104-
if (carouselRef?.current) {
105-
const currentCardIdx = cards.findIndex(c => c.cardKey === cardKey);
106-
if (currentCardIdx === -1) return;
101+
const isLastCard = cards.size === 1;
107102

103+
remoteCardsStore.getState().dismissCard(card.sys.id);
104+
if (carouselRef?.current) {
108105
// check if this is the last card and don't scroll if so
109106
if (isLastCard) return;
110107

111108
carouselRef.current.scrollToIndex({
112-
index: currentCardIdx,
109+
index: Array.from(cards.values()).findIndex(c => c.sys.id === card.sys.id),
113110
animated: true,
114111
});
115112
}
116113
},
117-
[carouselRef, dismissCard, cards, cardKey, card.sys.id]
114+
[carouselRef, cardKey, card.sys.id]
118115
);
119116

120117
const imageForPlatform = () => {
@@ -143,7 +140,7 @@ export const RemoteCard: React.FC<RemoteCardProps> = ({ card = {} as TrimmedCard
143140
}
144141
};
145142

146-
if (!card) {
143+
if (!card || card.dismissed) {
147144
return null;
148145
}
149146

src/components/cards/remote-cards/RemoteCardCarousel.tsx

+13-14
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import React, { useMemo, useRef } from 'react';
1+
import React, { useRef } from 'react';
22
import { CarouselCard } from '../CarouselCard';
33
import { useRoute } from '@react-navigation/native';
44
import { IS_TEST } from '@/env';
55

6-
import { useRemoteCardContext, RemoteCard } from '@/components/cards/remote-cards';
6+
import { RemoteCard } from '@/components/cards/remote-cards';
77
import { REMOTE_CARDS, getExperimetalFlag } from '@/config';
88
import { useDimensions, useWallets } from '@/hooks';
99
import { useRemoteConfig } from '@/model/remoteConfig';
1010
import { FlashList } from '@shopify/flash-list';
11-
import { TrimmedCard } from '@/resources/cards/cardCollectionQuery';
11+
import { remoteCardsStore } from '@/state/remoteCards/remoteCards';
12+
import Routes from '@/navigation/routesNames';
1213

1314
type RenderItemProps = {
14-
item: TrimmedCard;
15+
item: string;
1516
index: number;
1617
};
1718

@@ -24,35 +25,33 @@ export const getGutterSizeForCardAmount = (amount: number) => {
2425
};
2526

2627
export const RemoteCardCarousel = () => {
27-
const carouselRef = useRef<FlashList<TrimmedCard>>(null);
28+
const carouselRef = useRef<FlashList<string>>(null);
2829
const { name } = useRoute();
2930
const config = useRemoteConfig();
3031
const { isReadOnlyWallet } = useWallets();
31-
32-
const remoteCardsEnabled = getExperimetalFlag(REMOTE_CARDS) || config.remote_cards_enabled;
33-
const { getCardsForPlacement } = useRemoteCardContext();
3432
const { width } = useDimensions();
3533

36-
const data = useMemo(() => getCardsForPlacement(name as string), [getCardsForPlacement, name]);
34+
const remoteCardsEnabled = getExperimetalFlag(REMOTE_CARDS) || config.remote_cards_enabled;
35+
const cardIds = remoteCardsStore(state => state.getCardIdsForScreen(name as keyof typeof Routes));
3736

38-
const gutterSize = getGutterSizeForCardAmount(data.length);
37+
const gutterSize = getGutterSizeForCardAmount(cardIds.length);
3938

4039
const _renderItem = ({ item }: RenderItemProps) => {
41-
return <RemoteCard card={item} cards={data} gutterSize={gutterSize} carouselRef={carouselRef} />;
40+
return <RemoteCard id={item} gutterSize={gutterSize} carouselRef={carouselRef} />;
4241
};
4342

44-
if (isReadOnlyWallet || IS_TEST || !remoteCardsEnabled || !data.length) {
43+
if (isReadOnlyWallet || IS_TEST || !remoteCardsEnabled || !cardIds.length) {
4544
return null;
4645
}
4746

4847
return (
4948
<CarouselCard
5049
key={name as string}
51-
data={data}
50+
data={cardIds}
5251
carouselItem={{
5352
carouselRef,
5453
renderItem: _renderItem,
55-
keyExtractor: item => item.cardKey!,
54+
keyExtractor: item => item,
5655
placeholder: null,
5756
width: width - gutterSize,
5857
height: 88,

src/components/cards/remote-cards/RemoteCardProvider.tsx

-54
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
export * from './RemoteCardProvider';
21
export * from './RemoteCard';
32
export * from './RemoteCardCarousel';

src/components/remote-promo-sheet/RemotePromoSheet.tsx

+11-11
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,19 @@ import { get } from 'lodash';
55
import { useNavigation } from '@/navigation/Navigation';
66
import { PromoSheet } from '@/components/PromoSheet';
77
import { useTheme } from '@/theme';
8-
import { CampaignCheckResult } from './checkForCampaign';
98
import { usePromoSheetQuery } from '@/resources/promoSheet/promoSheetQuery';
109
import { maybeSignUri } from '@/handlers/imgix';
11-
import { campaigns } from '@/storage';
1210
import { delay } from '@/utils/delay';
1311
import { Linking } from 'react-native';
1412
import Routes from '@/navigation/routesNames';
1513
import { Language } from '@/languages';
1614
import { useAccountSettings } from '@/hooks';
15+
import { remotePromoSheetsStore } from '@/state/remotePromoSheets/remotePromoSheets';
16+
import { RootStackParamList } from '@/navigation/types';
1717

1818
const DEFAULT_HEADER_HEIGHT = 285;
1919
const DEFAULT_HEADER_WIDTH = 390;
2020

21-
type RootStackParamList = {
22-
RemotePromoSheet: CampaignCheckResult;
23-
};
24-
2521
type Item = {
2622
title: Record<keyof Language, string>;
2723
description: Record<keyof Language, string>;
@@ -59,8 +55,15 @@ export function RemotePromoSheet() {
5955
const { language } = useAccountSettings();
6056

6157
useEffect(() => {
58+
remotePromoSheetsStore.setState({
59+
isShown: true,
60+
lastShownTimestamp: Date.now(),
61+
});
62+
6263
return () => {
63-
campaigns.set(['isCurrentlyShown'], false);
64+
remotePromoSheetsStore.setState({
65+
isShown: false,
66+
});
6467
};
6568
}, []);
6669

@@ -85,7 +88,7 @@ export function RemotePromoSheet() {
8588

8689
const externalNavigation = useCallback(() => {
8790
Linking.openURL(data?.promoSheet?.primaryButtonProps.props.url);
88-
}, []);
91+
}, [data?.promoSheet?.primaryButtonProps.props.url]);
8992

9093
const internalNavigation = useCallback(() => {
9194
goBack();
@@ -114,13 +117,10 @@ export function RemotePromoSheet() {
114117
} = data.promoSheet;
115118

116119
const accentColor = (colors as { [key: string]: any })[accentColorString as string] ?? accentColorString;
117-
118120
const backgroundColor = (colors as { [key: string]: any })[backgroundColorString as string] ?? backgroundColorString;
119-
120121
const sheetHandleColor = (colors as { [key: string]: any })[sheetHandleColorString as string] ?? sheetHandleColorString;
121122

122123
const backgroundSignedImageUrl = backgroundImage?.url ? maybeSignUri(backgroundImage.url) : undefined;
123-
124124
const headerSignedImageUrl = headerImage?.url ? maybeSignUri(headerImage.url) : undefined;
125125

126126
return (

0 commit comments

Comments
 (0)