Skip to content

Commit

Permalink
migrate remote cards over to zustand <> react-query sync (#5796)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
walmat authored Jun 17, 2024
1 parent 0e8bf36 commit 23e59ea
Show file tree
Hide file tree
Showing 33 changed files with 623 additions and 295 deletions.
22 changes: 8 additions & 14 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ import branch from 'react-native-branch';
import { initializeReservoirClient } from '@/resources/reservoir/client';
import { ReviewPromptAction } from '@/storage/schema';
import { handleReviewPromptAction } from '@/utils/reviewAlert';
import { RemotePromoSheetProvider } from '@/components/remote-promo-sheet/RemotePromoSheetProvider';
import { RemoteCardProvider } from '@/components/cards/remote-cards';
import { initializeRemoteConfig } from '@/model/remoteConfig';
import { IS_DEV } from './env';
import { checkIdentifierOnLaunch } from './model/backup';
Expand Down Expand Up @@ -147,11 +145,11 @@ class OldApp extends Component {
const address = await loadAddress();

if (address) {
InteractionManager.runAfterInteractions(() => {
setTimeout(() => {
setTimeout(() => {
InteractionManager.runAfterInteractions(() => {
handleReviewPromptAction(ReviewPromptAction.TimesLaunchedSinceInstall);
}, 10_000);
});
});
}, 10_000);

checkIdentifierOnLaunch();
}
Expand Down Expand Up @@ -221,14 +219,10 @@ class OldApp extends Component {
<Portal>
<View style={containerStyle}>
{this.state.initialRoute && (
<RemotePromoSheetProvider isWalletReady={this.props.walletReady}>
<RemoteCardProvider>
<InitialRouteContext.Provider value={this.state.initialRoute}>
<RoutesComponent ref={this.handleNavigatorRef} />
<PortalConsumer />
</InitialRouteContext.Provider>
</RemoteCardProvider>
</RemotePromoSheetProvider>
<InitialRouteContext.Provider value={this.state.initialRoute}>
<RoutesComponent ref={this.handleNavigatorRef} />
<PortalConsumer />
</InitialRouteContext.Provider>
)}
<OfflineToast />
</View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ import getLayoutProvider from './getLayoutProvider';
import useLayoutItemAnimator from './useLayoutItemAnimator';
import { UniqueAsset } from '@/entities';
import { useRecyclerListViewScrollToTopContext } from '@/navigation/RecyclerListViewScrollToTopContext';
import { useAccountProfile, useAccountSettings, useCoinListEdited, useCoinListEditOptions, useWallets } from '@/hooks';
import { useAccountSettings, useCoinListEdited, useCoinListEditOptions, useWallets } from '@/hooks';
import { useNavigation } from '@/navigation';
import { useTheme } from '@/theme';
import { useRemoteCardContext } from '@/components/cards/remote-cards';
import { remoteCardsStore } from '@/state/remoteCards/remoteCards';
import { useRoute } from '@react-navigation/native';
import Routes from '@/navigation/routesNames';

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

const { name } = useRoute();
const { getCardsForPlacement } = useRemoteCardContext();
const getCardIdsForScreen = remoteCardsStore(state => state.getCardIdsForScreen);
const { isReadOnlyWallet } = useWallets();

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

const layoutProvider = useMemo(
() => getLayoutProvider(briefSectionsData, isCoinListEdited, cards, isReadOnlyWallet),
[briefSectionsData, isCoinListEdited, cards, isReadOnlyWallet]
() => getLayoutProvider(briefSectionsData, isCoinListEdited, cardIds, isReadOnlyWallet),
[briefSectionsData, isCoinListEdited, cardIds, isReadOnlyWallet]
);

const { accountAddress } = useAccountSettings();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,7 @@ class BetterLayoutProvider extends LayoutProvider {
}
}

const getLayoutProvider = (
briefSectionsData: BaseCellType[],
isCoinListEdited: boolean,
cards: TrimmedCard[],
isReadOnlyWallet: boolean
) => {
const getLayoutProvider = (briefSectionsData: BaseCellType[], isCoinListEdited: boolean, cardIds: string[], isReadOnlyWallet: boolean) => {
const indicesToOverride = [];
for (let i = 0; i < briefSectionsData.length; i++) {
const val = briefSectionsData[i];
Expand All @@ -61,7 +56,7 @@ const getLayoutProvider = (
dim.height = ViewDimensions[type].height;
dim.width = ViewDimensions[type].width || dim.width;

if ((type === CellType.REMOTE_CARD_CAROUSEL && !cards.length) || (type === CellType.REMOTE_CARD_CAROUSEL && isReadOnlyWallet)) {
if ((type === CellType.REMOTE_CARD_CAROUSEL && !cardIds.length) || (type === CellType.REMOTE_CARD_CAROUSEL && isReadOnlyWallet)) {
dim.height = 0;
}
}
Expand Down
29 changes: 13 additions & 16 deletions src/components/cards/remote-cards/RemoteCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import ConditionalWrap from 'conditional-wrap';

import { Box, Cover, Stack, Text, useForegroundColor } from '@/design-system';
import { ButtonPressAnimation } from '@/components/animations';
import { useRemoteCardContext } from './RemoteCardProvider';
import { IS_ANDROID, IS_IOS } from '@/env';
import { useNavigation } from '@/navigation';
import { Language } from '@/languages';
Expand All @@ -20,6 +19,7 @@ import { FlashList } from '@shopify/flash-list';
import { ButtonPressAnimationTouchEvent } from '@/components/animations/ButtonPressAnimation/types';
import { TrimmedCard } from '@/resources/cards/cardCollectionQuery';
import RemoteSvg from '@/components/svg/RemoteSvg';
import { remoteCardsStore } from '@/state/remoteCards/remoteCards';

const ICON_SIZE = 40;

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

type RemoteCardProps = {
card: TrimmedCard;
cards: TrimmedCard[];
id: string;
gutterSize: number;
carouselRef: React.RefObject<FlashList<TrimmedCard>> | null;
carouselRef: React.RefObject<FlashList<string>> | null;
};

export const RemoteCard: React.FC<RemoteCardProps> = ({ card = {} as TrimmedCard, cards, gutterSize, carouselRef }) => {
export const RemoteCard: React.FC<RemoteCardProps> = ({ id, gutterSize, carouselRef }) => {
const { isDarkMode } = useTheme();
const { navigate } = useNavigation();
const { language } = useAccountSettings();
const { width } = useDimensions();
const { dismissCard } = useRemoteCardContext();

const card = remoteCardsStore(state => state.getCard(id)) ?? ({} as TrimmedCard);
const { cardKey, accentColor, backgroundColor, primaryButton, imageIcon } = card;

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

const isLastCard = cards.length === 1;
const { cards } = remoteCardsStore.getState();

dismissCard(card.sys.id);
if (carouselRef?.current) {
const currentCardIdx = cards.findIndex(c => c.cardKey === cardKey);
if (currentCardIdx === -1) return;
const isLastCard = cards.size === 1;

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

carouselRef.current.scrollToIndex({
index: currentCardIdx,
index: Array.from(cards.values()).findIndex(c => c.sys.id === card.sys.id),
animated: true,
});
}
},
[carouselRef, dismissCard, cards, cardKey, card.sys.id]
[carouselRef, cardKey, card.sys.id]
);

const imageForPlatform = () => {
Expand Down Expand Up @@ -143,7 +140,7 @@ export const RemoteCard: React.FC<RemoteCardProps> = ({ card = {} as TrimmedCard
}
};

if (!card) {
if (!card || card.dismissed) {
return null;
}

Expand Down
27 changes: 13 additions & 14 deletions src/components/cards/remote-cards/RemoteCardCarousel.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import React, { useMemo, useRef } from 'react';
import React, { useRef } from 'react';
import { CarouselCard } from '../CarouselCard';
import { useRoute } from '@react-navigation/native';
import { IS_TEST } from '@/env';

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

type RenderItemProps = {
item: TrimmedCard;
item: string;
index: number;
};

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

export const RemoteCardCarousel = () => {
const carouselRef = useRef<FlashList<TrimmedCard>>(null);
const carouselRef = useRef<FlashList<string>>(null);
const { name } = useRoute();
const config = useRemoteConfig();
const { isReadOnlyWallet } = useWallets();

const remoteCardsEnabled = getExperimetalFlag(REMOTE_CARDS) || config.remote_cards_enabled;
const { getCardsForPlacement } = useRemoteCardContext();
const { width } = useDimensions();

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

const gutterSize = getGutterSizeForCardAmount(data.length);
const gutterSize = getGutterSizeForCardAmount(cardIds.length);

const _renderItem = ({ item }: RenderItemProps) => {
return <RemoteCard card={item} cards={data} gutterSize={gutterSize} carouselRef={carouselRef} />;
return <RemoteCard id={item} gutterSize={gutterSize} carouselRef={carouselRef} />;
};

if (isReadOnlyWallet || IS_TEST || !remoteCardsEnabled || !data.length) {
if (isReadOnlyWallet || IS_TEST || !remoteCardsEnabled || !cardIds.length) {
return null;
}

return (
<CarouselCard
key={name as string}
data={data}
data={cardIds}
carouselItem={{
carouselRef,
renderItem: _renderItem,
keyExtractor: item => item.cardKey!,
keyExtractor: item => item,
placeholder: null,
width: width - gutterSize,
height: 88,
Expand Down
54 changes: 0 additions & 54 deletions src/components/cards/remote-cards/RemoteCardProvider.tsx

This file was deleted.

1 change: 0 additions & 1 deletion src/components/cards/remote-cards/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './RemoteCardProvider';
export * from './RemoteCard';
export * from './RemoteCardCarousel';
22 changes: 11 additions & 11 deletions src/components/remote-promo-sheet/RemotePromoSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,19 @@ import { get } from 'lodash';
import { useNavigation } from '@/navigation/Navigation';
import { PromoSheet } from '@/components/PromoSheet';
import { useTheme } from '@/theme';
import { CampaignCheckResult } from './checkForCampaign';
import { usePromoSheetQuery } from '@/resources/promoSheet/promoSheetQuery';
import { maybeSignUri } from '@/handlers/imgix';
import { campaigns } from '@/storage';
import { delay } from '@/utils/delay';
import { Linking } from 'react-native';
import Routes from '@/navigation/routesNames';
import { Language } from '@/languages';
import { useAccountSettings } from '@/hooks';
import { remotePromoSheetsStore } from '@/state/remotePromoSheets/remotePromoSheets';
import { RootStackParamList } from '@/navigation/types';

const DEFAULT_HEADER_HEIGHT = 285;
const DEFAULT_HEADER_WIDTH = 390;

type RootStackParamList = {
RemotePromoSheet: CampaignCheckResult;
};

type Item = {
title: Record<keyof Language, string>;
description: Record<keyof Language, string>;
Expand Down Expand Up @@ -59,8 +55,15 @@ export function RemotePromoSheet() {
const { language } = useAccountSettings();

useEffect(() => {
remotePromoSheetsStore.setState({
isShown: true,
lastShownTimestamp: Date.now(),
});

return () => {
campaigns.set(['isCurrentlyShown'], false);
remotePromoSheetsStore.setState({
isShown: false,
});
};
}, []);

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

const externalNavigation = useCallback(() => {
Linking.openURL(data?.promoSheet?.primaryButtonProps.props.url);
}, []);
}, [data?.promoSheet?.primaryButtonProps.props.url]);

const internalNavigation = useCallback(() => {
goBack();
Expand Down Expand Up @@ -114,13 +117,10 @@ export function RemotePromoSheet() {
} = data.promoSheet;

const accentColor = (colors as { [key: string]: any })[accentColorString as string] ?? accentColorString;

const backgroundColor = (colors as { [key: string]: any })[backgroundColorString as string] ?? backgroundColorString;

const sheetHandleColor = (colors as { [key: string]: any })[sheetHandleColorString as string] ?? sheetHandleColorString;

const backgroundSignedImageUrl = backgroundImage?.url ? maybeSignUri(backgroundImage.url) : undefined;

const headerSignedImageUrl = headerImage?.url ? maybeSignUri(headerImage.url) : undefined;

return (
Expand Down
Loading

0 comments on commit 23e59ea

Please sign in to comment.