Skip to content
This repository was archived by the owner on Feb 8, 2025. It is now read-only.

Commit fe77c8e

Browse files
committed
feat(app): address sheet and pressable address label
1 parent 6a5ae28 commit fe77c8e

File tree

8 files changed

+150
-32
lines changed

8 files changed

+150
-32
lines changed
+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { useAddressLabel } from '#/address/AddressLabel';
2+
import { AddressIcon } from '#/Identicon/AddressIcon';
3+
import { PressableOpacity } from '#/PressableOpacity';
4+
import { Scrollable } from '#/Scrollable';
5+
import { Sheet } from '#/sheet/Sheet';
6+
import { ContactOutlineIcon, OutboundIcon, ShareIcon, WebIcon } from '@theme/icons';
7+
import { CORNER, ICON_SIZE } from '@theme/paper';
8+
import { createStyles, useStyles } from '@theme/styles';
9+
import { CHAINS } from 'chains';
10+
import { Link } from 'expo-router';
11+
import { asAddress, asChain } from 'lib';
12+
import { View } from 'react-native';
13+
import { Text } from 'react-native-paper';
14+
import { z } from 'zod';
15+
import { useLocalParams } from '~/hooks/useLocalParams';
16+
import { useSelectedAccount } from '~/hooks/useSelectedAccount';
17+
import { share } from '~/lib/share';
18+
import { zUAddress } from '~/lib/zod';
19+
20+
const Params = z.object({ address: zUAddress() });
21+
22+
export default function AddressSheet() {
23+
const { styles } = useStyles(stylesheet);
24+
const { address } = useLocalParams(Params);
25+
const account = useSelectedAccount();
26+
27+
const explorer = CHAINS[asChain(address)].blockExplorers?.native.url;
28+
29+
return (
30+
<Sheet contentContainerStyle={styles.container}>
31+
<View style={styles.headerContainer}>
32+
<AddressIcon address={address} size={ICON_SIZE.large} />
33+
34+
<Text variant="headlineSmall">{useAddressLabel(address)}</Text>
35+
36+
<Text variant="bodyMedium" style={styles.address}>
37+
{asAddress(address)}
38+
</Text>
39+
</View>
40+
41+
<Scrollable horizontal contentContainerStyle={styles.actions}>
42+
<PressableOpacity style={styles.action} onPress={() => share({ url: address })}>
43+
<ShareIcon />
44+
<Text variant="labelLarge">Share</Text>
45+
</PressableOpacity>
46+
47+
{explorer && (
48+
<Link href={`${explorer}address/${asAddress(address)}`} asChild>
49+
<PressableOpacity style={styles.action}>
50+
<WebIcon />
51+
<Text variant="labelLarge">Explorer</Text>
52+
</PressableOpacity>
53+
</Link>
54+
)}
55+
56+
<Link href={{ pathname: `/(nav)/contacts/[address]`, params: { address } }} asChild>
57+
<PressableOpacity style={styles.action}>
58+
<ContactOutlineIcon />
59+
<Text variant="labelLarge">Contact</Text>
60+
</PressableOpacity>
61+
</Link>
62+
63+
{account && (
64+
<Link
65+
href={{ pathname: `/(nav)/[account]/(home)/send`, params: { account, to: address } }}
66+
asChild
67+
>
68+
<PressableOpacity style={styles.action}>
69+
<OutboundIcon />
70+
<Text variant="labelLarge">Send</Text>
71+
</PressableOpacity>
72+
</Link>
73+
)}
74+
</Scrollable>
75+
</Sheet>
76+
);
77+
}
78+
79+
const stylesheet = createStyles(({ colors }) => ({
80+
container: {
81+
paddingBottom: 16,
82+
},
83+
headerContainer: {
84+
alignItems: 'center',
85+
gap: 8,
86+
marginHorizontal: 16,
87+
marginVertical: 8,
88+
},
89+
address: {
90+
color: colors.onSurfaceVariant,
91+
},
92+
actions: {
93+
justifyContent: 'center',
94+
gap: 16,
95+
paddingHorizontal: 16,
96+
marginVertical: 8,
97+
},
98+
action: {
99+
alignItems: 'center',
100+
gap: 8,
101+
width: 80,
102+
paddingVertical: 8,
103+
borderRadius: CORNER.m,
104+
},
105+
}));

app/src/components/QrModal.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ export function QrModal({ address, actions }: QrModalProps) {
3535
<IconButton mode="contained-tonal" icon={CloseIcon} size={styles.iconButton.width} />
3636
</Link>
3737

38-
<Text variant="displaySmall" style={styles.name}>
39-
{isUAddress(address) && <AddressLabel address={address} />}
40-
</Text>
38+
{isUAddress(address) && (
39+
<AddressLabel address={address} variant="displaySmall" style={styles.name} />
40+
)}
4141

4242
<View style={styles.iconButton} />
4343
</View>

app/src/components/activity/IncomingTransferItem.tsx

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useAddressLabel } from '#/address/AddressLabel';
1+
import { AddressLabel } from '#/address/AddressLabel';
22
import { Timestamp } from '#/format/Timestamp';
33
import { ListItem, ListItemProps } from '#/list/ListItem';
44
import { ListItemSkeleton } from '#/list/ListItemSkeleton';
@@ -40,7 +40,6 @@ export interface IncomingTransferItemProps extends Partial<ListItemProps> {
4040

4141
function IncomingTransferItem_(props: IncomingTransferItemProps) {
4242
const transfer = useFragment(Transfer, props.transfer);
43-
const from = useAddressLabel(asUAddress(transfer.from, transfer.account.chain));
4443
const amount = useTokenAmount({ token: transfer.token, amount: transfer.amount });
4544

4645
return (
@@ -58,7 +57,12 @@ function IncomingTransferItem_(props: IncomingTransferItemProps) {
5857
<OverlayIcon icon={ReceiveIcon} />
5958
</View>
6059
}
61-
headline={`Received ${amount} from ${from}`}
60+
headline={
61+
<>
62+
{`Received ${amount} from `}
63+
<AddressLabel address={asUAddress(transfer.from, transfer.account.chain)} />
64+
</>
65+
}
6266
supporting={<Timestamp timestamp={transfer.timestamp} />}
6367
trailing={({ Text }) =>
6468
transfer.value !== null && transfer.value !== undefined ? (

app/src/components/address/AddressLabel.tsx

+25-13
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,38 @@ import { useLazyQuery } from '~/api';
33
import { graphql } from 'relay-runtime';
44
import { AddressLabelQuery } from '~/api/__generated__/AddressLabelQuery.graphql';
55
import { truncateAddr } from '~/util/format';
6+
import { Text, TextProps } from 'react-native-paper';
7+
import { useRouter } from 'expo-router';
68

79
const Query = graphql`
8-
query AddressLabelQuery($address: UAddress!, $skip: Boolean!) {
9-
label(address: $address) @skip(if: $skip)
10+
query AddressLabelQuery($address: UAddress!) {
11+
label(address: $address)
1012
}
1113
`;
1214

13-
export const useAddressLabel = <A extends UAddress | undefined>(address: A) => {
14-
const { label } = useLazyQuery<AddressLabelQuery>(Query, {
15-
address: address!,
16-
skip: !address,
17-
});
15+
export function useAddressLabel(address: UAddress) {
16+
const { label } = useLazyQuery<AddressLabelQuery>(
17+
Query,
18+
{ address },
19+
{ fetchPolicy: 'store-or-network' },
20+
);
1821

19-
return (address ? label || truncateAddr(address) : undefined) as A extends undefined
20-
? string | undefined
21-
: string;
22-
};
22+
return label || truncateAddr(address);
23+
}
2324

24-
export interface AddressLabelProps {
25+
export interface AddressLabelProps extends Omit<TextProps<string>, 'children'> {
2526
address: UAddress;
2627
}
2728

28-
export const AddressLabel = ({ address }: AddressLabelProps) => useAddressLabel(address);
29+
export function AddressLabel({ address, ...textProps }: AddressLabelProps) {
30+
const router = useRouter();
31+
32+
return (
33+
<Text
34+
{...textProps}
35+
onPress={() => router.push({ pathname: `/address/[address]`, params: { address } })}
36+
>
37+
{useAddressLabel(address)}
38+
</Text>
39+
);
40+
}

app/src/components/policy/ApproverItem.tsx

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { UAddress } from 'lib';
2-
import { useAddressLabel } from '#/address/AddressLabel';
2+
import { AddressLabel } from '#/address/AddressLabel';
33
import { ListItem, ListItemProps } from '#/list/ListItem';
4-
import { truncateAddr } from '~/util/format';
54
import { I18nManager } from 'react-native';
65
import { RectButton, Swipeable } from 'react-native-gesture-handler';
76
import { CloseIcon, DeleteIcon } from '@theme/icons';
@@ -17,8 +16,6 @@ export interface ApproverItemProps extends Partial<ListItemProps> {
1716

1817
export function ApproverItem({ address, remove, ...props }: ApproverItemProps) {
1918
const { styles } = useStyles(stylesheet);
20-
const label = useAddressLabel(address);
21-
const truncated = truncateAddr(address);
2219

2320
const ref = useRef<Swipeable | null>(null);
2421

@@ -46,8 +43,7 @@ export function ApproverItem({ address, remove, ...props }: ApproverItemProps) {
4643
>
4744
<ListItem
4845
leading={<AddressIcon address={address} />}
49-
headline={label}
50-
supporting={label !== truncated ? truncated : undefined}
46+
headline={<AddressLabel address={address} />}
5147
trailing={<CloseIcon onPress={remove} />}
5248
containerStyle={styles.itemContainer}
5349
{...props}

app/src/components/transaction/OperationDetails.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ClockOutlineIcon } from '@theme/icons';
22
import { UAddress, asChain, asUAddress } from 'lib';
33
import { DateTime } from 'luxon';
44
import { match } from 'ts-pattern';
5-
import { AddressLabel, useAddressLabel } from '#/address/AddressLabel';
5+
import { AddressLabel } from '#/address/AddressLabel';
66
import { useTimestamp } from '#/format/Timestamp';
77
import { ListItem } from '#/list/ListItem';
88
import { AddressIcon } from '#/Identicon/AddressIcon';
@@ -85,7 +85,7 @@ function TransferOp({ f, chain }: PropsFor<'TransferOp'>) {
8585
<ListItem
8686
leading={<AddressIcon address={f.to} />}
8787
overline="To"
88-
headline={useAddressLabel(asUAddress(f.to, chain))}
88+
headline={<AddressLabel address={asUAddress(f.to, chain)} />}
8989
/>
9090
<ListItem
9191
leading={<LazyTokenIcon token={asUAddress(f.token, chain)} />}
@@ -136,7 +136,7 @@ function TransferApprovalOp({ f, chain }: PropsFor<'TransferApprovalOp'>) {
136136
<ListItem
137137
leading={<AddressIcon address={f.to} />}
138138
overline="Spender"
139-
headline={useAddressLabel(asUAddress(f.to, chain))}
139+
headline={<AddressLabel address={asUAddress(f.to, chain)} />}
140140
/>
141141
<ListItem
142142
leading={<LazyTokenIcon token={asUAddress(f.token, chain)} />}
@@ -186,7 +186,7 @@ function Other({ op, chain }: OtherProps) {
186186
<ListItem
187187
leading={<AddressIcon address={op.to} />}
188188
overline={op.data ? 'Contract' : 'To'}
189-
headline={useAddressLabel(asUAddress(op.to, chain))}
189+
headline={<AddressLabel address={asUAddress(op.to, chain)} />}
190190
/>
191191
);
192192
}

app/src/components/transaction/PendingApprovalItem.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { useFragment } from 'react-relay';
88
import { PendingApprovalItem_user$key } from '~/api/__generated__/PendingApprovalItem_user.graphql';
99
import { PendingApprovalItem_approver$key } from '~/api/__generated__/PendingApprovalItem_approver.graphql';
1010
import { PendingApprovalItem_proposal$key } from '~/api/__generated__/PendingApprovalItem_proposal.graphql';
11-
import { truncateAddr } from '~/util/format';
11+
import { AddressLabel } from '#/address/AddressLabel';
12+
import { asUAddress } from 'lib';
1213

1314
const User = graphql`
1415
fragment PendingApprovalItem_user on User {
@@ -50,7 +51,7 @@ export function PendingApprovalItem(props: PendingApprovalItemProps) {
5051
return (
5152
<ListItem
5253
leading={<AddressIcon address={approver.address} />}
53-
headline={approver.label || truncateAddr(approver.address)}
54+
headline={<AddressLabel address={asUAddress(approver.address, proposal.account.chain)} />}
5455
{...(approve && {
5556
trailing: ({ size }) => (
5657
<IconButton mode="contained-tonal" icon={CheckIcon} size={size} onPress={approve} />

app/src/util/theme/icons.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const materialCommunityIcon = icon('materialCommunity');
3939

4040
export const HomeIcon = materialIcon('home');
4141
export const OutboundIcon = materialCommunityIcon('arrow-top-right');
42-
export const UserOutlineIcon = materialIcon('person-outline');
42+
export const ContactOutlineIcon = materialIcon('person-outline');
4343
export const ContactsIcon = materialIcon('people');
4444
export const ContactsOutlineIcon = materialIcon('people-outline');
4545
export const AddIcon = materialCommunityIcon('plus');

0 commit comments

Comments
 (0)