Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Claimables: query + wallet screen ui #6071

Merged
merged 15 commits into from
Sep 12, 2024
66 changes: 66 additions & 0 deletions src/components/asset-list/RecyclerAssetList2/Claimable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import { Box, Inline, Stack, Text } from '@/design-system';
import { useAccountSettings, useDimensions } from '@/hooks';
import { useClaimables } from '@/resources/addys/claimables/query';
import { getIsHardhatConnected } from '@/handlers/web3';
import { FasterImageView } from '@candlefinance/faster-image';
import { ButtonPressAnimation } from '@/components/animations';

export default function Claimable({ uniqueId }: { uniqueId: string }) {
const { accountAddress, nativeCurrency } = useAccountSettings();
const { data } = useClaimables(
benisgold marked this conversation as resolved.
Show resolved Hide resolved
{
address: accountAddress,
currency: nativeCurrency,
testnetMode: getIsHardhatConnected(),
},
{
select: data => data?.filter(claimable => claimable.uniqueId === uniqueId),
}
);
const { width: deviceWidth } = useDimensions();
benisgold marked this conversation as resolved.
Show resolved Hide resolved

const claimable = data?.[0];
benisgold marked this conversation as resolved.
Show resolved Hide resolved

if (!claimable) return null;

return (
<Box
as={ButtonPressAnimation}
scaleTo={0.96}
paddingHorizontal="20px"
justifyContent="space-between"
alignItems="center"
flexDirection="row"
>
<Inline alignVertical="center" space="12px">
<FasterImageView
source={{ url: claimable.iconUrl }}
style={{ height: 40, width: 40, borderRadius: 11, borderWidth: 1, borderColor: 'rgba(0, 0, 0, 0.03)' }}
/>
<Stack space={{ custom: 11 }}>
<Text weight="semibold" color="label" size="17pt" ellipsizeMode="tail" numberOfLines={1} style={{ maxWidth: deviceWidth - 220 }}>
{claimable.name}
</Text>
<Text weight="semibold" color="labelTertiary" size="13pt">
{claimable.value.claimAsset.display}
</Text>
</Stack>
</Inline>
<Box
alignItems="center"
justifyContent="center"
height={{ custom: 28 }}
paddingHorizontal="8px"
borderRadius={12}
borderWidth={1.333}
borderColor={{ custom: 'rgba(7, 17, 32, 0.02)' }}
style={{ backgroundColor: 'rgba(7, 17, 32, 0.02)' }}
>
<Text weight="semibold" color="label" align="center" size="17pt">
{claimable.value.nativeAsset.display}
</Text>
</Box>
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Animated, Easing, Image } from 'react-native';
import CaretImageSource from '../../../assets/family-dropdown-arrow.png';
benisgold marked this conversation as resolved.
Show resolved Hide resolved
import { useTheme } from '../../../theme/ThemeContext';
benisgold marked this conversation as resolved.
Show resolved Hide resolved
import { ButtonPressAnimation } from '../../animations';
benisgold marked this conversation as resolved.
Show resolved Hide resolved
import { Box, Inline, Text } from '@/design-system';
import * as i18n from '@/languages';
import useOpenClaimables from '@/hooks/useOpenClaimables';

const AnimatedImgixImage = Animated.createAnimatedComponent(Image);

const TokenFamilyHeaderAnimationDuration = 200;
const TokenFamilyHeaderHeight = 48;

const ClaimablesListHeader = ({ total }: { total: string }) => {
benisgold marked this conversation as resolved.
Show resolved Hide resolved
const { colors } = useTheme();
const { isClaimablesOpen, toggleOpenClaimables } = useOpenClaimables();

const toValue = Number(!!isClaimablesOpen);

const [animation] = useState(() => new Animated.Value(toValue));

useEffect(() => {
Animated.timing(animation, {
duration: TokenFamilyHeaderAnimationDuration,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
toValue,
useNativeDriver: true,
}).start();
}, [toValue, animation]);

const imageAnimatedStyles = useMemo(
() => ({
height: 18,
marginBottom: 1,
right: 5,
transform: [
{
rotate: animation.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '90deg'],
}),
},
],
width: 8,
}),
[animation]
);

const sumNumberAnimatedStyles = useMemo(
() => ({
opacity: animation.interpolate({
inputRange: [0, 1],
outputRange: [1, 0],
}),
paddingRight: 4,
}),
[animation]
);

return (
<ButtonPressAnimation
key={`claimables_${isClaimablesOpen}`}
onPress={toggleOpenClaimables}
scaleTo={1.05}
testID={`claimables-list-header`}
>
<Box height={{ custom: TokenFamilyHeaderHeight }} paddingHorizontal={'19px (Deprecated)'} justifyContent="center">
<Inline alignHorizontal="justify" alignVertical="center">
<Text size="22pt" color={'label'} weight="heavy">
{i18n.t(i18n.l.account.tab_claimables)}
</Text>
<Inline horizontalSpace={'8px'} alignVertical="center">
{!isClaimablesOpen && (
<Animated.View style={sumNumberAnimatedStyles}>
<Text size="20pt" color={'label'} weight="regular">
{total}
</Text>
</Animated.View>
)}
<AnimatedImgixImage source={CaretImageSource} style={imageAnimatedStyles} tintColor={colors.dark} />
</Inline>
</Inline>
</Box>
</ButtonPressAnimation>
);
};

ClaimablesListHeader.animationDuration = TokenFamilyHeaderAnimationDuration;

ClaimablesListHeader.height = TokenFamilyHeaderHeight;

export default ClaimablesListHeader;
15 changes: 15 additions & 0 deletions src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { ExtendedState } from './RawRecyclerList';
import {
AssetsHeaderExtraData,
CellType,
ClaimableExtraData,
ClaimablesHeaderExtraData,
CoinDividerExtraData,
CoinExtraData,
NFTExtraData,
Expand All @@ -33,6 +35,8 @@ import { RemoteCardCarousel } from '@/components/cards/remote-cards';
import WrappedCollectiblesHeader from '../WrappedCollectiblesHeader';
import NFTLoadingSkeleton from '../NFTLoadingSkeleton';
import { NFTEmptyState } from '../NFTEmptyState';
import Claimable from '../Claimable';
import ClaimablesListHeader from '../ClaimablesListHeader';

function rowRenderer(type: CellType, { uid }: { uid: string }, _: unknown, extendedState: ExtendedState) {
const data = extendedState.additionalData[uid];
Expand All @@ -51,6 +55,8 @@ function rowRenderer(type: CellType, { uid }: { uid: string }, _: unknown, exten
case CellType.EMPTY_ROW:
case CellType.POSITIONS_SPACE_AFTER:
case CellType.POSITIONS_SPACE_BEFORE:
case CellType.CLAIMABLES_SPACE_AFTER:
case CellType.CLAIMABLES_SPACE_BEFORE:
return null;
case CellType.COIN_DIVIDER:
return (
Expand Down Expand Up @@ -162,6 +168,15 @@ function rowRenderer(type: CellType, { uid }: { uid: string }, _: unknown, exten

return <WrappedPosition placement={index % 2 === 0 ? 'left' : 'right'} uniqueId={uniqueId} />;
}
case CellType.CLAIMABLES_HEADER: {
const { total } = data as ClaimablesHeaderExtraData;
return <ClaimablesListHeader total={total} />;
}
case CellType.CLAIMABLE: {
const { uniqueId } = data as ClaimableExtraData;

return <Claimable uniqueId={uniqueId} />;
}

case CellType.LOADING_ASSETS:
return <AssetListItemSkeleton />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ const ViewDimensions: Record<CellType, Dim> = {
height: 130,
width: deviceUtils.dimensions.width / 2 - 0.1,
},
[CellType.CLAIMABLES_HEADER]: { height: AssetListHeaderHeight },
[CellType.CLAIMABLES_SPACE_BEFORE]: { height: 10 },
[CellType.CLAIMABLES_SPACE_AFTER]: { height: 3 },
[CellType.CLAIMABLE]: {
height: 60,
width: deviceUtils.dimensions.width,
},
[CellType.REMOTE_CARD_CAROUSEL]: { height: 112 },
};

Expand Down
15 changes: 14 additions & 1 deletion src/components/asset-list/RecyclerAssetList2/core/ViewTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export enum CellType {
POSITION = 'POSITION',
POSITIONS_SPACE_AFTER = 'POSITIONS_SPACE_AFTER',

CLAIMABLES_SPACE_BEFORE = 'CLAIMABLES_SPACE_BEFORE',
CLAIMABLES_HEADER = 'CLAIMABLES_HEADER',
CLAIMABLE = 'CLAIMABLE',
CLAIMABLES_SPACE_AFTER = 'CLAIMABLES_SPACE_AFTER',

LOADING_ASSETS = 'LOADING_ASSETS',
RECEIVE_CARD = 'RECEIVE_CARD',
ETH_CARD = 'ETH_CARD',
Expand Down Expand Up @@ -74,6 +79,12 @@ export type PositionExtraData = {
export type PositionHeaderExtraData = {
total: string;
};
export type ClaimableExtraData = {
uniqueId: string;
};
export type ClaimablesHeaderExtraData = {
total: string;
};
export type NFTFamilyExtraData = {
type: CellType.FAMILY_HEADER;
name: string;
Expand All @@ -90,6 +101,8 @@ export type CellExtraData =
| AssetListHeaderExtraData
| AssetsHeaderExtraData
| PositionExtraData
| PositionHeaderExtraData;
| PositionHeaderExtraData
| ClaimableExtraData
| ClaimablesHeaderExtraData;

export type CellTypes = BaseCellType & CellExtraData;
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@/hooks';
import useOpenPositionCards from '@/hooks/useOpenPositionCards';
import * as ls from '@/storage';
import useOpenClaimables from '@/hooks/useOpenClaimables';

const FILTER_TYPES = {
'ens-profile': [CellType.NFT_SPACE_AFTER, CellType.NFT, CellType.FAMILY_HEADER],
Expand Down Expand Up @@ -47,6 +48,7 @@ export default function useMemoBriefSectionData({

const { isSmallBalancesOpen } = useOpenSmallBalances();
const { isPositionCardsOpen } = useOpenPositionCards();
const { isClaimablesOpen } = useOpenClaimables();
const { isCoinListEdited } = useCoinListEdited();
const { hiddenCoinsObj } = useCoinListEditOptions();
const { openFamilies } = useOpenFamilies();
Expand Down Expand Up @@ -107,14 +109,18 @@ export default function useMemoBriefSectionData({
return false;
}

if (data.type === CellType.CLAIMABLE && !isClaimablesOpen) {
return false;
}

index++;
return true;
})
.map(({ uid, type: cellType }) => {
return { type: cellType, uid };
});
return briefSectionsDataFiltered;
}, [sectionsDataToUse, type, isCoinListEdited, isSmallBalancesOpen, hiddenCoinsObj, isPositionCardsOpen, openFamilies]);
}, [sectionsDataToUse, type, isCoinListEdited, isSmallBalancesOpen, hiddenCoinsObj, isPositionCardsOpen, isClaimablesOpen, openFamilies]);
const memoizedResult = useDeepCompareMemo(() => result, [result]);
const additionalData = useDeepCompareMemo(
() =>
Expand Down
2 changes: 2 additions & 0 deletions src/config/experimental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const DAPP_BROWSER = 'Dapp Browser';
export const ETH_REWARDS = 'ETH Rewards';
export const DEGEN_MODE = 'Degen Mode';
export const FEATURED_RESULTS = 'Featured Results';
export const CLAIMABLES = 'Claimables';

/**
* A developer setting that pushes log lines to an array in-memory so that
Expand Down Expand Up @@ -66,6 +67,7 @@ export const defaultConfig: Record<string, ExperimentalValue> = {
[ETH_REWARDS]: { settings: true, value: false },
[DEGEN_MODE]: { settings: true, value: false },
[FEATURED_RESULTS]: { settings: true, value: false },
[CLAIMABLES]: { settings: true, value: false },
};

const storageKey = 'config';
Expand Down
57 changes: 54 additions & 3 deletions src/helpers/buildWalletSections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities';
import { queryClient } from '@/react-query';
import { positionsQueryKey } from '@/resources/defi/PositionsQuery';
import store from '@/redux/store';
import { PositionExtraData } from '@/components/asset-list/RecyclerAssetList2/core/ViewTypes';
import { getExperimetalFlag, DEFI_POSITIONS } from '@/config/experimental';
import { ClaimableExtraData, PositionExtraData } from '@/components/asset-list/RecyclerAssetList2/core/ViewTypes';
import { getExperimetalFlag, DEFI_POSITIONS, CLAIMABLES } from '@/config/experimental';
import { RainbowPositions } from '@/resources/defi/types';
import { claimablesQueryKey } from '@/resources/addys/claimables/query';
import { getIsHardhatConnected } from '@/handlers/web3';
import { Claimable } from '@/resources/addys/claimables/types';
import { add, convertAmountToNativeDisplay } from './utilities';
import { getRemoteConfig } from '@/model/remoteConfig';

const CONTENT_PLACEHOLDER = [
{ type: 'LOADING_ASSETS', uid: 'loadings-asset-1' },
Expand Down Expand Up @@ -57,7 +62,8 @@ const listTypeSelector = (state: any) => state.listType;
const buildBriefWalletSections = (balanceSectionData: any, uniqueTokenFamiliesSection: any) => {
const { balanceSection, isEmpty, isLoadingUserAssets } = balanceSectionData;
const positionSection = withPositionsSection(isLoadingUserAssets);
const sections = [balanceSection, positionSection, uniqueTokenFamiliesSection];
const claimablesSection = withClaimablesSection(isLoadingUserAssets);
const sections = [balanceSection, claimablesSection, positionSection, uniqueTokenFamiliesSection];

const filteredSections = sections.filter(section => section.length !== 0).flat(1);

Expand Down Expand Up @@ -105,6 +111,51 @@ const withPositionsSection = (isLoadingUserAssets: boolean) => {
return [];
};

const withClaimablesSection = (isLoadingUserAssets: boolean) => {
// check if the feature is enabled
const claimablesEnabled = getExperimetalFlag(CLAIMABLES) || getRemoteConfig().claimables;
benisgold marked this conversation as resolved.
Show resolved Hide resolved
if (!claimablesEnabled) return [];

const { accountAddress: address, nativeCurrency: currency } = store.getState().settings;
const claimables: Claimable[] | undefined = queryClient.getQueryData(
claimablesQueryKey({ address, currency, testnetMode: getIsHardhatConnected() })
);

const result: ClaimableExtraData[] = [];
let totalNativeValue = '0';
claimables?.forEach(claimable => {
totalNativeValue = add(totalNativeValue, claimable.value.nativeAsset.amount);
benisgold marked this conversation as resolved.
Show resolved Hide resolved
const listData = {
type: 'CLAIMABLE',
uniqueId: claimable.uniqueId,
uid: `claimable-${claimable.uniqueId}`,
};
result.push(listData);
});
const totalNativeDisplay = convertAmountToNativeDisplay(totalNativeValue, currency);
if (result.length && !isLoadingUserAssets) {
const res = [
{
type: 'CLAIMABLES_SPACE_BEFORE',
uid: 'claimables-header-space-before',
},
{
type: 'CLAIMABLES_HEADER',
uid: 'claimables-header',
total: totalNativeDisplay,
},
{
type: 'CLAIMABLES_SPACE_AFTER',
uid: 'claimables-header-space-before',
},
...result,
];

return res;
}
return [];
};

const withBriefBalanceSection = (
sortedAssets: ParsedAddressAsset[],
isLoadingUserAssets: boolean,
Expand Down
15 changes: 15 additions & 0 deletions src/hooks/useOpenClaimables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useCallback } from 'react';
import { useMMKVBoolean } from 'react-native-mmkv';
import useAccountSettings from './useAccountSettings';

export default function useOpenClaimables() {
const { accountAddress } = useAccountSettings();
const [isClaimablesOpen, setIsClaimablesOpen] = useMMKVBoolean('claimables-open-' + accountAddress);

const toggleOpenClaimables = useCallback(() => setIsClaimablesOpen(prev => !prev), [setIsClaimablesOpen]);

return {
isClaimablesOpen,
toggleOpenClaimables,
};
}
Loading