From 3785e48412bb83c9cd26a2df8f9cbd7b14a068f4 Mon Sep 17 00:00:00 2001 From: iGroza Date: Mon, 3 Jul 2023 17:28:35 +0800 Subject: [PATCH] feat: tokens layout --- assets/images/none-nft.png | Bin 0 -> 1044 bytes assets/images/none-nft@2x.png | Bin 0 -> 1838 bytes assets/images/none-nft@3x.png | Bin 0 -> 2609 bytes src/components/home-feed.tsx | 143 +++++++++++++++------ src/components/nft-viewer/nft-viewer.tsx | 24 ++++ src/components/token-row.tsx | 75 +++++++++++ src/components/top-tab-navigator-large.tsx | 23 +++- src/helpers/is-feature-enabled.ts | 3 + src/i18n.ts | 6 + src/screens/home-feed.tsx | 47 ++++++- src/types.ts | 8 ++ 11 files changed, 286 insertions(+), 43 deletions(-) create mode 100644 assets/images/none-nft.png create mode 100644 assets/images/none-nft@2x.png create mode 100644 assets/images/none-nft@3x.png create mode 100644 src/components/token-row.tsx diff --git a/assets/images/none-nft.png b/assets/images/none-nft.png new file mode 100644 index 0000000000000000000000000000000000000000..1685cef54902fc4fe1c843bd162675d4cda0c8fa GIT binary patch literal 1044 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 literal 0 HcmV?d00001 diff --git a/assets/images/none-nft@3x.png b/assets/images/none-nft@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4e9645086f86a9efc6b77b669e04f883a5618286 GIT binary patch literal 2609 zcma)8c{r5s8lKIBD4(^+lF>qbdcZo_0Re5biV6)-}idH_qp%qd7t-wzUxaoZ$lCnl^2D`2-8l1aW@nzs55@O+a6$aaH01dc#E|v%G>m;DGXMT zwsZ4}5C~|tB$(Pq!skXV2lZ0q>EjDEO`gyF$tIzOcj1zgQ`eJZ^y(2*q4r&wWe*Z$ zB*-Qd_{^o^%+tjH>q7mlZlC!RCrh_7ZmaRG5A{*+-(SlpcHU=2n2a`Txn8S@00aK{ zvDA&vnT$GV_-3fbn(yw-=Y$4*BQ%FC-LlBe;1{XiDBNiG^iRMmY#RV+5}~A)pF?_5 zYgv>iCo~>VBR$f(F&7+k@`S{0iBK!~V8fpIq^xJK53OCTf|!bmrL|fy27{&IqSxg( z5PazrUkXy=BI9g`v{`ls)8 za@PiMV+yj5z9sC4?P%unx{p#*^xKlQ!b*yE7QZ&*`U%zo3OB6I&sv?`7;k4 zMrvL?dbXnWo^eq)Pq$rUS~4Ve=nz@3aBVo-y?z9h<;+7dH7+BADs(Ut3peSulX#Ti zxrgdOs=bIYHHQH0Vo_zqX?b+p1G#jEUO?*DKY7yZB%B+9m@|kV+Y+t0PbJ;C8XShm z9tSsZin$CH{^$%J{@e#5Ni?R)m_sD$sIvIqBsp}4PKe~OG+XpHNiTwI3Xx>GaW!fn z5<78<&~K6t@aI&B#1bU=oe7H8xB+R8jdX;``Q~XgE zib2?j!Xv53?-**0O8r|hB<2<|U~VM&XUtuCutI?0iz;?i&^h`p;q~%HRV|kF1^5Y-F~8II2A!^JiqTBDKhTLaMyAID zLqUtkZAT{#Qa0@AxrNxPh;n`YkgvOvwxN)t3ps2#>~#6g(k4#m&jyNIIL?}9Q}V<8 zOP^18L(2|)cX0xxnX?w(5uHX^u$NTVXjvKbzB=1w1@ClPXyG}Mt-m%GdMuz!4%L&s zF$;QA6Fo)5!}WMWGZnCPui~gygLDm5G&wu5R$EEMjVe^N84xiOyJ)=M?ZKmu@R;x& zN&r%EU6!rt_TYdrl1vb*9`olWF>W59?V*0D0U@7&OlB&xQ9jOf>%ubPOH+bw}r89}bjByTYGyx(aEdRjP5!;h#EE8QTDOld2S zJ)6v%(R#|LOe(M_q)JQ|UY7?j1}K^WfH{n! z$pM%>4MJtxsbJ;+Y80Whof~`PJJE=JPzif*!xx9I*S4_P`>rPLd`U;`GtDk-CGnE>OWm!?aC{m9S6&ws=n>Lj44?Si|K&5%G)(J0tZPq2gf1I<3ddYsHBim% z5sqhB`CQokb~r1)cI7r>{1k@dqJ+ff_lrBKf9aM2mHVJ=yJV>-B}HmR1VA2XEsG)J zDdqbflL%sEUQYN4K4}I372FOsbwN;aIC<9-a3}oDCH)6!S4KX?MXDKt%D&|o_j-79 z66NI@P2`za{YS=h^2gWulEBI`gUoxN%FXU=N0f~&^;M_Dsd(ljJlu9@h`lbqn2L6; z+4kpVaROH;k=0(iJz#vE$t`U-zTWgv;$9C~uv>b46xe{_pnw*xg1llVZttVaF7VEu z+hVSq<@2}~|I&WwX^YOqw9wX39aj z&8Y5#bnEd>0_qI)T+NQ>NybP zggOMVwS*x5cXt$A1ianmcXrb03*R-|W(A|Dr>5M#JB&zMeJb-O3nn96@`OG4pGF$7eBM)~T)%Y2fX+s+uR-IJlL+Qs3nN~hPE9}1<~Jt$*`|`OgB(3JXia-!G#@)paM{Ub>b`vaTB$)=ptIhh zrLV87XyAWuTGEoPK}=mSHski8?Zxk0)8@`X9ZU`a{U8q-YGRV(vX_qp!Ug;ih+WY2MM9we literal 0 HcmV?d00001 diff --git a/src/components/home-feed.tsx b/src/components/home-feed.tsx index d45ce628f..d4d4ef61d 100644 --- a/src/components/home-feed.tsx +++ b/src/components/home-feed.tsx @@ -11,18 +11,22 @@ import {Feature, isFeatureEnabled} from '@app/helpers/is-feature-enabled'; import {I18N} from '@app/i18n'; import {BannersWrapper} from '@app/screens/banners'; import {WalletsWrapper} from '@app/screens/wallets'; -import {TransactionList} from '@app/types'; +import {NftCollection, TokenItem, TransactionList} from '@app/types'; import {NftViewer} from './nft-viewer'; -import {nftCollections} from './nft-viewer/mock'; +import {TokenRow} from './token-row'; import {TopTabNavigator, TopTabNavigatorVariant} from './top-tab-navigator'; import {First, Spacer, Text} from './ui'; type HomeFeedProps = { refreshing: boolean; - onWalletsRefresh: () => void; transactionsList: TransactionList[]; - onPressRow: (hash: string) => void; + tokensList: TokenItem[]; + nftColletionsList: NftCollection[]; + islmPrice: number; + onWalletsRefresh: () => void; + onPressTransactionRow: (hash: string) => void; + onPressTokenRow: (tiker: string) => void; }; enum TabNames { @@ -32,11 +36,16 @@ enum TabNames { } const PAGE_ITEMS_COUNT = 15; + export const HomeFeed = ({ refreshing, - onWalletsRefresh, transactionsList, - onPressRow, + tokensList, + nftColletionsList, + islmPrice, + onWalletsRefresh, + onPressTokenRow, + onPressTransactionRow, }: HomeFeedProps) => { const [page, setPage] = useState(1); const transactionListData = useMemo( @@ -49,7 +58,7 @@ export const HomeFeed = ({ activeTab === TabNames.transactions ? !!transactionsList.length : true, [activeTab, transactionsList.length], ); - const data = useMemo( + const transactionsData = useMemo( () => (activeTab === TabNames.transactions ? transactionListData : []), [activeTab, transactionListData], ); @@ -64,34 +73,65 @@ export const HomeFeed = ({ <> - {isFeatureEnabled(Feature.nft) ? ( - - - - - ) : ( + + {isFeatureEnabled(Feature.tokens) && ( + <> + + + } + /> + + + + + )} + {isFeatureEnabled(Feature.nft) && ( + <> + + + + + + + )} - )} + ); }, [onTabChange]); - const renderItem: ListRenderItem = useCallback( - ({item}) => , - [onPressRow], + const transactionRenderItem: ListRenderItem = useCallback( + ({item}) => , + [onPressTransactionRow], ); const renderListEmptyComponent = useCallback( @@ -102,7 +142,7 @@ export const HomeFeed = ({ <> @@ -110,10 +150,41 @@ export const HomeFeed = ({ )} ), - [activeTab], + [activeTab, nftColletionsList], ); - const keyExtractor = useCallback((item: TransactionList) => item.hash, []); + const transactionKeyExtractor = useCallback( + (item: TransactionList) => item.hash, + [], + ); + + const tokensKeyExtractor = useCallback((item: TokenItem) => item.ticker, []); + const tokensRenderItem: ListRenderItem = useCallback( + ({item}) => ( + + ), + [islmPrice, onPressTokenRow], + ); + + if (activeTab === TabNames.tokens) { + return ( + + ); + } return ( diff --git a/src/components/nft-viewer/nft-viewer.tsx b/src/components/nft-viewer/nft-viewer.tsx index 95a863a20..bdb4c6604 100644 --- a/src/components/nft-viewer/nft-viewer.tsx +++ b/src/components/nft-viewer/nft-viewer.tsx @@ -2,6 +2,7 @@ import React, {useCallback, useMemo, useState} from 'react'; import {useActionSheet} from '@expo/react-native-action-sheet'; import { + Image, SectionList, SectionListData, SectionListRenderItem, @@ -163,6 +164,19 @@ export const NftViewer = ({ [], ); + if (!data?.length) { + return ( + + + + + + ); + } + return ( @@ -204,4 +218,14 @@ const styles = createTheme({ flexDirection: 'row', justifyContent: 'space-between', }, + empty: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + emptyImage: { + height: 80, + width: 80, + tintColor: Color.graphicSecond3, + }, }); diff --git a/src/components/token-row.tsx b/src/components/token-row.tsx new file mode 100644 index 000000000..898a262da --- /dev/null +++ b/src/components/token-row.tsx @@ -0,0 +1,75 @@ +import React, {useCallback, useMemo} from 'react'; + +import {Image, TouchableOpacity, View} from 'react-native'; + +import {Color} from '@app/colors'; +import {cleanNumber, createTheme} from '@app/helpers'; +import {TokenItem} from '@app/types'; + +import {Spacer, Text} from './ui'; + +export interface TokenRowProps { + item: TokenItem; + islmPrice: number; + onPress?(tiker: string): void; +} + +export function TokenRow({item, islmPrice, onPress}: TokenRowProps) { + const totalUsd = useMemo(() => item.priceUsd * item.count, [item]); + const totalUsdFormatted = useMemo(() => cleanNumber(totalUsd), [totalUsd]); + + const islmCount = useMemo( + () => cleanNumber(totalUsd / islmPrice), + [islmPrice, totalUsd], + ); + + const handlerPress = useCallback( + () => onPress?.(item?.ticker), + [onPress, item], + ); + return ( + + + + + + + {item.name} + + {islmCount} ISLM + + + + {item.ticker} + + + + ${totalUsdFormatted} + + + + + + ); +} + +const styles = createTheme({ + container: { + marginHorizontal: 20, + marginVertical: 8, + flex: 1, + }, + row: { + flexDirection: 'row', + }, + icon: { + width: 42, + height: 42, + borderRadius: 12, + backgroundColor: Color.graphicBase2, + }, + textContainer: { + flex: 1, + alignItems: 'center', + }, +}); diff --git a/src/components/top-tab-navigator-large.tsx b/src/components/top-tab-navigator-large.tsx index c993087e2..18c95e941 100644 --- a/src/components/top-tab-navigator-large.tsx +++ b/src/components/top-tab-navigator-large.tsx @@ -2,7 +2,11 @@ import React from 'react'; import {View} from 'react-native'; import {TouchableOpacity} from 'react-native-gesture-handler'; -import Animated, {useAnimatedStyle} from 'react-native-reanimated'; +import Animated, { + FadeIn, + FadeOut, + useAnimatedStyle, +} from 'react-native-reanimated'; import {useTiming} from 'react-native-redash'; import {Color} from '@app/colors'; @@ -59,7 +63,11 @@ export const TopTabNavigatorLarge = ({ const title = isI18N(tab.props.title) ? getText(tab.props.title) : tab.props.title; - const showSeparator = showSeparators && index !== tabList.length - 1; + const showSeparator = + showSeparators && + index !== tabList.length - 1 && + activeTabIndex !== index && + activeTabIndex - 1 !== index; return ( @@ -74,7 +82,13 @@ export const TopTabNavigatorLarge = ({ {title} - {showSeparator && } + {showSeparator && ( + + )} ); })} @@ -95,8 +109,10 @@ const styles = createTheme({ alignSelf: 'center', backgroundColor: Color.graphicSecond2, transform: [{translateX: -0.5}], + zIndex: 1, }, activeTabIndicator: { + zIndex: 2, backgroundColor: Color.bg1, position: 'absolute', borderRadius: 12, @@ -121,6 +137,7 @@ const styles = createTheme({ backgroundColor: Color.bg3, }, tab: { + zIndex: 2, flex: 1, alignItems: 'center', justifyContent: 'center', diff --git a/src/helpers/is-feature-enabled.ts b/src/helpers/is-feature-enabled.ts index 76d673e39..c4908df22 100644 --- a/src/helpers/is-feature-enabled.ts +++ b/src/helpers/is-feature-enabled.ts @@ -7,6 +7,7 @@ export enum Feature { earn, governanceAndStaking, nft, + tokens, abnews, } @@ -20,6 +21,8 @@ export const isFeatureEnabled = (feature: Feature): boolean => { return app.isDeveloper; case Feature.nft: return app.isDeveloper; + case Feature.tokens: + return app.isDeveloper; case Feature.abnews: return app.isWelcomeNewsEnabled; default: diff --git a/src/i18n.ts b/src/i18n.ts index f863e857d..1c5532af4 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -683,6 +683,9 @@ export enum I18N { jailbreakTitle, jailbreakDescription2, jailbreakDescription1, + homeFeedTokensTabTitle, + accountInfoTokensTabTitle, + nftViewerNoNFTs, } export function getText(key: I18N, params?: Record): string { @@ -1489,6 +1492,8 @@ const en: Record = { [I18N.accountInfoTransactionTabTitle]: 'Transactions', [I18N.homeFeedNftTabTitle]: 'NFTs', [I18N.accountInfoNftTabTitle]: 'NFTs', + [I18N.accountInfoTokensTabTitle]: 'Tokens', + [I18N.homeFeedTokensTabTitle]: 'Tokens', [I18N.homeEarnEmptyRaffleTitle]: 'Raffles will be here soon', [I18N.homeEarnEmptyRaffleDescription]: 'Soon you will be able to participate in raffles and receive prizes', @@ -1498,4 +1503,5 @@ const en: Record = { 'In accordance with our security policy, we do not allow use of the application on hacked devices. This is necessary to maintain safety of user funds', [I18N.jailbreakDescription2]: 'We recommend that you update your device to the original firmware provided by your manufacturer', + [I18N.nftViewerNoNFTs]: 'No NFTs', }; diff --git a/src/screens/home-feed.tsx b/src/screens/home-feed.tsx index 995219fa9..4181f3350 100644 --- a/src/screens/home-feed.tsx +++ b/src/screens/home-feed.tsx @@ -1,8 +1,9 @@ -import React, {useCallback, useEffect, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {Collection, CollectionChangeSet} from 'realm'; import {HomeFeed} from '@app/components/home-feed'; +import {nftCollections} from '@app/components/nft-viewer/mock'; import {app} from '@app/contexts'; import {Events} from '@app/events'; import {prepareTransactions} from '@app/helpers'; @@ -10,12 +11,42 @@ import {awaitForEventDone} from '@app/helpers/await-for-event-done'; import {useTypedNavigation} from '@app/hooks'; import {Transaction} from '@app/models/transaction'; import {Wallet} from '@app/models/wallet'; -import {TransactionList} from '@app/types'; +import {TokenItem, TransactionList} from '@app/types'; + +const MOCK_TOKENS: TokenItem[] = [ + { + name: 'Iclamic Coin', + ticker: 'ISLM', + icon: 'https://i.ibb.co/ZHvy09Y/Islamic-coin-ISLM.png', + count: 414, + priceUsd: 0.35, + }, + { + name: 'Etherium', + ticker: 'ETH', + icon: 'https://i.ibb.co/1v7WQDv/Ethereum-ETH.png', + count: 0.0013, + priceUsd: 1500, + }, + { + name: 'Bitcoin', + ticker: 'BTC', + icon: 'https://i.ibb.co/Z6ftjmX/Bitcoin-BTC.png', + count: 2.5, + priceUsd: 30000, + }, +]; export const HomeFeedScreen = () => { const navigation = useTypedNavigation(); const [refreshing, setRefreshing] = useState(false); + // TODO: + const islmPrice = useMemo( + () => MOCK_TOKENS.find(it => it.ticker === 'ISLM')?.priceUsd ?? 0, + [], + ); + const [transactionsList, setTransactionsList] = useState( prepareTransactions( Wallet.addressList(), @@ -37,7 +68,7 @@ export const HomeFeedScreen = () => { }); }, []); - const onPressRow = useCallback( + const onPressTransactionRow = useCallback( (hash: string) => { navigation.navigate('transactionDetail', { hash, @@ -46,6 +77,10 @@ export const HomeFeedScreen = () => { [navigation], ); + const onPressTokenRow = useCallback((tiker: string) => { + console.log('token row pressed', tiker); + }, []); + const updateTransactionsList = useCallback(() => { const transactions = Transaction.getAllByProviderId(app.providerId); setTransactionsList( @@ -88,10 +123,14 @@ export const HomeFeedScreen = () => { return ( ); }; diff --git a/src/types.ts b/src/types.ts index 6a6f0625a..2c17e4c17 100644 --- a/src/types.ts +++ b/src/types.ts @@ -919,3 +919,11 @@ export interface NftCollection { items: NftItem[]; created_at: number; } + +export interface TokenItem { + icon: string; + name: string; + ticker: string; + count: number; + priceUsd: number; +}