Skip to content

Commit

Permalink
feat: add collectibles widget, ref leather-io/issues#222
Browse files Browse the repository at this point in the history
  • Loading branch information
pete-watters committed Sep 27, 2024
1 parent 498a1db commit 432e828
Show file tree
Hide file tree
Showing 59 changed files with 1,764 additions and 546 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import React from 'react';

import { Box, Text, TouchableOpacity } from '@leather.io/ui/native';

interface AddWalletListItemProps {
Expand Down
18 changes: 16 additions & 2 deletions apps/mobile/src/components/home/home.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { AccountsWidget } from '@/components/widgets/accounts/accounts-widget';
import { TokensWidget } from '@/components/widgets/tokens/tokens-widget';
import { getMockTokens, mockTotalBalance } from '@/components/widgets/tokens/tokens.mocks';
import {
CollectiblesWidget,
mockCollectibles,
serializeCollectibles,
} from '@/components/widgets/collectibles';
import { TokensWidget, getMockTokens } from '@/components/widgets/tokens';
import { useAccounts } from '@/store/accounts/accounts.read';
import { useWallets } from '@/store/wallets/wallets.read';
import { useLingui } from '@lingui/react';

import { HomeLayout } from './home.layout';

const mockTotalBalance = {
totalUsdBalance: '$126.74',
totalBtcBalance: '0.00215005',
totalStxBalance: '0.0024',
};

export function Home() {
useLingui();
const wallets = useWallets();
Expand All @@ -16,6 +26,10 @@ export function Home() {
<HomeLayout>
<AccountsWidget accounts={accounts.list} wallets={wallets.list} />
<TokensWidget tokens={getMockTokens()} totalBalance={mockTotalBalance.totalUsdBalance} />
<CollectiblesWidget
collectibles={serializeCollectibles(mockCollectibles)}
totalBalance={mockTotalBalance.totalUsdBalance}
/>
</HomeLayout>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import React from 'react';

import { AccountSelectorSheet } from '@/features/account-selector-sheet';
import { t } from '@lingui/macro';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import { ScrollView } from 'react-native-gesture-handler';

import { useTheme } from '@shopify/restyle';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { t } from '@lingui/macro';

import { Box, ChevronRightIcon, Chip, SheetRef, Text } from '@leather.io/ui/native';

import { FiatBalance } from '../components/balance/fiat-balance';

interface CollectiblesHeaderProps {
collectibleCount: number;
sheetRef: React.RefObject<SheetRef>;
totalBalance: string;
}

function CollectiblesHeaderText() {
return <Text variant="heading05">{t`My collectibles`}</Text>;
}

export function CollectiblesHeader({ collectibleCount, totalBalance }: CollectiblesHeaderProps) {
const hasCollectibles = collectibleCount > 0;
if (!hasCollectibles) return <CollectiblesHeaderText />;
return (
<Box flexDirection="row" gap="1" alignItems="center" marginHorizontal="5">
<CollectiblesHeaderText />
<Chip label={collectibleCount} />
<ChevronRightIcon variant="small" />
<Box flex={1} justifyContent="flex-end" alignItems="flex-end">
<FiatBalance balance={totalBalance} color="ink.text-subdued" />
</Box>
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { mockCollectibles } from '@leather.io/ui/native';

import { serializeCollectibles } from './collectibles-serializer';

describe('serializeCollectibles', () => {
it('should correctly serialize collectibles', () => {
const serializedCollectibles = serializeCollectibles(mockCollectibles);

expect(serializedCollectibles).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
title: expect.any(String),
imageUrl: expect.any(String),
collection: expect.any(String),
type: expect.stringMatching(/^(ordinal|stacks)$/),
}),
])
);
});

it('should handle empty input', () => {
const serializedCollectibles = serializeCollectibles([]);
expect(serializedCollectibles).toEqual([]);
});

it('should correctly serialize Ordinals', () => {
const ordinals = mockCollectibles.filter(c => 'name' in c && 'mimeType' in c);
const serializedOrdinals = serializeCollectibles(ordinals);

serializedOrdinals.forEach(ordinal => {
expect(ordinal).toEqual(
expect.objectContaining({
type: 'inscription',
id: expect.any(String),
title: expect.any(String),
imageUrl: expect.any(String),
collection: expect.any(String),
})
);
});
});

it('should correctly serialize StacksNfts', () => {
const stacksNfts = mockCollectibles.filter(c => 'metadata' in c);
const serializedStacksNfts = serializeCollectibles(stacksNfts);

serializedStacksNfts.forEach(nft => {
expect(nft).toEqual(
expect.objectContaining({
type: 'stacks',
id: expect.any(String),
title: expect.any(String),
imageUrl: expect.any(String),
collection: expect.any(String),
})
);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { type Collectible, type CollectibleCardProps } from '@leather.io/ui/native';

export function serializeCollectibles(collectibles: Collectible[]): CollectibleCardProps[] {
return collectibles.map(collectible => {
const isOrdinal = 'name' in collectible && 'mimeType' in collectible;
if (isOrdinal) {
return {
type: 'inscription',
name: collectible.title,
src: collectible.src,
mimeType: collectible.mimeType,
};
}
return {
type: 'stacks',
name: collectible.metadata.name,
src: collectible.metadata.cached_image,
mimeType: null,
};
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ScrollView } from 'react-native-gesture-handler';

import { useTheme } from '@shopify/restyle';

import { Box, SheetRef, Theme } from '@leather.io/ui/native';

import { Widget } from '../widget';

interface CollectiblesWidgetProps {
balance?: React.ReactNode;
children: React.ReactNode;
header?: React.ReactNode;
sheetRef?: React.RefObject<SheetRef>;
}

export function CollectiblesWidgetLayout({ children, header }: CollectiblesWidgetProps) {
const theme = useTheme<Theme>();
return (
<Widget>
<Box>{header}</Box>
<ScrollView
showsHorizontalScrollIndicator={false}
horizontal
contentContainerStyle={{ gap: theme.spacing['3'], marginHorizontal: theme.spacing['5'] }}
>
{children}
</ScrollView>
</Widget>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useRef } from 'react';

import { CollectibleCard, CollectibleCardProps, SheetRef } from '@leather.io/ui/native';

import { TokenBalance } from '../components/balance/token-balance';
import { CollectiblesHeader } from './collectibles-header';
import { CollectiblesWidgetLayout } from './collectibles-widget.layout';

interface CollectiblesWidgetProps {
collectibles: CollectibleCardProps[];
totalBalance: string;
}

export function CollectiblesWidget({ collectibles, totalBalance }: CollectiblesWidgetProps) {
const sheetRef = useRef<SheetRef>(null);

return (
<CollectiblesWidgetLayout
header={
<CollectiblesHeader
collectibleCount={collectibles.length}
totalBalance={totalBalance}
sheetRef={sheetRef}
/>
}
balance={collectibles.length > 0 && <TokenBalance balance={totalBalance} />}
>
{collectibles.map((collectible: CollectibleCardProps, index) => (
<CollectibleCard key={index} {...collectible} />
))}
</CollectiblesWidgetLayout>
);
}
3 changes: 3 additions & 0 deletions apps/mobile/src/components/widgets/collectibles/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { mockCollectibles } from '@leather.io/ui/native';
export { CollectiblesWidget } from './collectibles-widget';
export { serializeCollectibles } from './collectibles-serializer';
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import React from 'react';

import { t } from '@lingui/macro';

import { BulletSeparator, Text } from '@leather.io/ui/native';
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/src/components/widgets/tokens/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { TokensWidget } from './tokens-widget';
export { getMockTokens } from './tokens.mocks';
2 changes: 0 additions & 2 deletions apps/mobile/src/components/widgets/tokens/tokens-header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import React from 'react';

import { t } from '@lingui/macro';

import { Box, ChevronRightIcon, Chip, SheetRef, Text } from '@leather.io/ui/native';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import { ScrollView } from 'react-native-gesture-handler';

import { useTheme } from '@shopify/restyle';
Expand Down
8 changes: 0 additions & 8 deletions apps/mobile/src/components/widgets/tokens/tokens.mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@ import BigNumber from 'bignumber.js';
import { Money } from '@leather.io/models';
import { BtcAvatarIcon, StxAvatarIcon } from '@leather.io/ui/native';

// provided by useTotalBalance hook in extension/src/app/common/hooks/balance/use-total-balance.tsx

export const mockTotalBalance = {
totalUsdBalance: '$126.74',
totalBtcBalance: '0.00215005',
totalStxBalance: '0.0024',
};

export interface Token {
availableBalance: Record<string, Money>;
formattedBalance: { isAbbreviated: boolean; value: string };
Expand Down
2 changes: 0 additions & 2 deletions apps/mobile/src/components/widgets/widget.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import React from 'react';

import { Box } from '@leather.io/ui/native';

interface WidgetProps {
Expand Down
20 changes: 12 additions & 8 deletions apps/mobile/src/locales/en/messages.po
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2024-09-23 11:25-0400\n"
"POT-Creation-Date: 2024-09-25 06:31+0100\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
Expand Down Expand Up @@ -194,11 +194,11 @@ msgstr "Awaiting verification"
msgid "BIP39 passphrase"
msgstr "BIP39 passphrase"

#: src/components/widgets/tokens/tokens.mocks.tsx:50
#: src/components/widgets/tokens/tokens.mocks.tsx:42
msgid "Bitcoin"
msgstr "Bitcoin"

#: src/components/widgets/tokens/tokens.mocks.tsx:27
#: src/components/widgets/tokens/tokens.mocks.tsx:19
msgid "Bitcoin blockchain"
msgstr "Bitcoin blockchain"

Expand Down Expand Up @@ -490,7 +490,7 @@ msgid "Lock app"
msgstr "Lock app"

#: src/components/widgets/components/balance/fiat-balance.tsx:45
#: src/components/widgets/components/balance/token-balance.tsx:21
#: src/components/widgets/components/balance/token-balance.tsx:19
msgid "locked"
msgstr "locked"

Expand All @@ -507,11 +507,15 @@ msgstr "More options"
msgid "Must use physical device for Push Notifications"
msgstr "Must use physical device for Push Notifications"

#: src/components/widgets/accounts/accounts-header.tsx:14
#: src/components/widgets/accounts/accounts-header.tsx:12
msgid "My accounts"
msgstr "My accounts"

#: src/components/widgets/tokens/tokens-header.tsx:18
#: src/components/widgets/collectibles/collectibles-header.tsx:14
msgid "My collectibles"
msgstr "My collectibles"

#: src/components/widgets/tokens/tokens-header.tsx:16
msgid "My tokens"
msgstr "My tokens"

Expand Down Expand Up @@ -704,11 +708,11 @@ msgstr "Something is wrong!"
msgid "Something went wrong"
msgstr "Something went wrong"

#: src/components/widgets/tokens/tokens.mocks.tsx:113
#: src/components/widgets/tokens/tokens.mocks.tsx:105
msgid "Stacks"
msgstr "Stacks"

#: src/components/widgets/tokens/tokens.mocks.tsx:59
#: src/components/widgets/tokens/tokens.mocks.tsx:51
msgid "Stacks blockchain"
msgstr "Stacks blockchain"

Expand Down
Loading

0 comments on commit 432e828

Please sign in to comment.