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

feat: collectibles widget #481

Merged
merged 1 commit into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this as a way to just get only the data we need to display from the mocks.

Later I found createBestInSlotInscription which is actually giving us this data and more.

I will investigate more when hooking up APIs but maybe it could be good to have a BE proxy for this type of stuff - data we need for App Home for example.


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 CollectiblesWidgetLayoutProps {
balance?: React.ReactNode;
children: React.ReactNode;
header?: React.ReactNode;
sheetRef?: React.RefObject<SheetRef>;
}

export function CollectiblesWidgetLayout({ children, header }: CollectiblesWidgetLayoutProps) {
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
Loading