Skip to content

Commit

Permalink
feat(suite-native): show placeholder when standard wallet is not present
Browse files Browse the repository at this point in the history
  • Loading branch information
vytick committed Nov 11, 2024
1 parent 36fdc66 commit c214aa3
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 82 deletions.
1 change: 1 addition & 0 deletions suite-native/device-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"type-check": "yarn g:tsc --build"
},
"dependencies": {
"@mobily/ts-belt": "^3.13.1",
"@react-navigation/native": "6.1.18",
"@reduxjs/toolkit": "1.9.5",
"@suite-common/redux-utils": "workspace:*",
Expand Down
93 changes: 15 additions & 78 deletions suite-native/device-manager/src/components/WalletItem.tsx
Original file line number Diff line number Diff line change
@@ -1,106 +1,43 @@
import { Pressable } from 'react-native';
import { useSelector } from 'react-redux';

import { HStack, Radio, Text } from '@suite-native/atoms';
import { Translation } from '@suite-native/intl';
import { Icon } from '@suite-native/icons';
import { TrezorDevice } from '@suite-common/suite-types';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { selectDevice, selectDeviceByState } from '@suite-common/wallet-core';
import { FiatAmountFormatter } from '@suite-native/formatters';
import { selectDeviceTotalFiatBalanceNative } from '@suite-native/device';

import { WalletItemBase } from './WalletItemBase';

type WalletItemProps = {
deviceState: NonNullable<TrezorDevice['state']>;
onPress: () => void;
deviceState: NonNullable<TrezorDevice['state']>;
isSelectable?: boolean;
};

type WalletItemStyleProps = { isSelected: boolean; isSelectable: boolean };

const walletItemStyle = prepareNativeStyle<WalletItemStyleProps>(
(utils, { isSelected, isSelectable }) => ({
justifyContent: 'space-between',
alignItems: 'center',
height: 60,
gap: utils.spacings.sp12,
borderRadius: utils.borders.radii.r12,
borderColor: utils.colors.borderElevation1,
flex: 1,
extend: [
{
condition: isSelected,
style: {
borderWidth: utils.borders.widths.large,
borderColor: utils.colors.borderSecondary,
},
},
{
condition: isSelectable,
style: {
paddingHorizontal: utils.spacings.sp16,
backgroundColor: utils.colors.backgroundSurfaceElevation1,
borderWidth: utils.borders.widths.small,
},
},
],
}),
);

const labelStyle = prepareNativeStyle(() => ({
flex: 1,
}));

export const WalletItem = ({ deviceState, onPress, isSelectable = true }: WalletItemProps) => {
const { applyStyle } = useNativeStyles();
export const WalletItem = ({ onPress, deviceState, isSelectable = true }: WalletItemProps) => {
const device = useSelector((state: any) => selectDeviceByState(state, deviceState));
const selectedDevice = useSelector(selectDevice);
const fiatBalance = useSelector((state: any) =>
selectDeviceTotalFiatBalanceNative(state, deviceState.staticSessionId!),
device?.state?.staticSessionId
? String(selectDeviceTotalFiatBalanceNative(state, device?.state?.staticSessionId))
: undefined,
);

if (!device) {
return null;
}

const walletNameLabel = device.useEmptyPassphrase ? (
<Translation id="deviceManager.wallet.standard" />
) : (
<Translation
id="deviceManager.wallet.defaultPassphrase"
values={{ index: device.walletNumber }}
/>
);

const isSelected =
selectedDevice?.id === device.id && selectedDevice?.instance === device.instance;

const showAsSelected = isSelected && isSelectable;

return (
<Pressable onPress={onPress}>
<HStack
key={device.instance}
style={applyStyle(walletItemStyle, { isSelected: showAsSelected, isSelectable })}
>
<HStack alignItems="center" flex={1}>
<Icon
name={device.useEmptyPassphrase ? 'wallet' : 'password'}
size="mediumLarge"
/>
<Text variant="callout" numberOfLines={1} style={applyStyle(labelStyle)}>
{walletNameLabel}
</Text>
</HStack>
<HStack alignItems="center" spacing="sp12">
<FiatAmountFormatter
value={String(fiatBalance)}
variant="hint"
color="textSubdued"
/>
{isSelectable && <Radio value="" onPress={onPress} isChecked={isSelected} />}
</HStack>
</HStack>
</Pressable>
<WalletItemBase
variant={device.useEmptyPassphrase ? 'standard' : 'passphrase'}
onPress={onPress}
isSelectable={isSelectable}
isSelected={showAsSelected}
walletNumber={device.walletNumber}
fiatBalance={fiatBalance}
/>
);
};
94 changes: 94 additions & 0 deletions suite-native/device-manager/src/components/WalletItemBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Pressable } from 'react-native';

import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { HStack, Radio, Text } from '@suite-native/atoms';
import { FiatAmountFormatter } from '@suite-native/formatters';
import { Icon } from '@suite-native/icons';
import { Translation } from '@suite-native/intl';

type WalletItemBaseVariant = 'standard' | 'passphrase';

type WalletItemBaseProps = {
variant: WalletItemBaseVariant;
onPress: () => void;
isSelectable: boolean;
isSelected: boolean;
walletNumber?: number;
fiatBalance?: string;
};

type WalletItemBaseStyleProps = { isSelected: boolean; isSelectable: boolean };

const walletItemBaseStyle = prepareNativeStyle<WalletItemBaseStyleProps>(
(utils, { isSelected, isSelectable }) => ({
justifyContent: 'space-between',
alignItems: 'center',
height: 60,
gap: utils.spacings.sp12,
borderRadius: utils.borders.radii.r12,
borderColor: utils.colors.borderElevation1,
flex: 1,
extend: [
{
condition: isSelected,
style: {
borderWidth: utils.borders.widths.large,
borderColor: utils.colors.borderSecondary,
},
},
{
condition: isSelectable,
style: {
paddingHorizontal: utils.spacings.sp16,
backgroundColor: utils.colors.backgroundSurfaceElevation1,
borderWidth: utils.borders.widths.small,
},
},
],
}),
);

const labelStyle = prepareNativeStyle(() => ({
flex: 1,
}));

export const WalletItemBase = ({
variant,
onPress,
isSelected,
isSelectable,
walletNumber,
fiatBalance,
}: WalletItemBaseProps) => {
const { applyStyle } = useNativeStyles();
const isStandard = variant === 'standard';

const walletNameLabel = isStandard ? (
<Translation id="deviceManager.wallet.standard" />
) : (
<Translation id="deviceManager.wallet.defaultPassphrase" values={{ index: walletNumber }} />
);

return (
<Pressable onPress={onPress}>
<HStack style={applyStyle(walletItemBaseStyle, { isSelected, isSelectable })}>
<HStack alignItems="center" flex={1}>
<Icon name={isStandard ? 'wallet' : 'password'} size="mediumLarge" />
<Text variant="callout" numberOfLines={1} style={applyStyle(labelStyle)}>
{walletNameLabel}
</Text>
</HStack>
<HStack alignItems="center" spacing="sp12">
{fiatBalance && (
<FiatAmountFormatter
value={fiatBalance}
variant="hint"
color="textSubdued"
/>
)}
{isSelectable && <Radio value="" onPress={onPress} isChecked={isSelected} />}
</HStack>
</HStack>
</Pressable>
);
};
48 changes: 44 additions & 4 deletions suite-native/device-manager/src/components/WalletList.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,60 @@
import { useSelector } from 'react-redux';
import { useSelector, useDispatch } from 'react-redux';

import { A } from '@mobily/ts-belt';

import { selectDeviceInstances } from '@suite-common/wallet-core';
import { VStack } from '@suite-native/atoms';
import { TrezorDevice } from '@suite-common/suite-types';
import {
createDeviceInstanceThunk,
selectDevice,
selectDeviceInstances,
selectIsPortfolioTrackerDevice,
} from '@suite-common/wallet-core';
import { VStack } from '@suite-native/atoms';
import { selectHasNoDeviceWithEmptyPassphrase } from '@suite-native/device';

import { WalletItem } from './WalletItem';
import { WalletItemBase } from './WalletItemBase';

type WalletListProps = {
onSelectDevice: (device: TrezorDevice) => void;
};

export const WalletList = ({ onSelectDevice }: WalletListProps) => {
const dispatch = useDispatch();
const devices = useSelector(selectDeviceInstances);
const selectedDevice = useSelector(selectDevice);
const hasNoDeviceWithEmptyPassphrase = useSelector(selectHasNoDeviceWithEmptyPassphrase);
const isPortfolioTrackerDevice = useSelector(selectIsPortfolioTrackerDevice);
const isSelectable = devices.length > 1 || hasNoDeviceWithEmptyPassphrase;

// we want to show placeholder in case there are only passphrase wallets without standard and not portfolio
const showPlaceholder =
hasNoDeviceWithEmptyPassphrase && A.isNotEmpty(devices) && !isPortfolioTrackerDevice;

// on tap of placeholder we actually create device with empty passphrase and select it
const handlePlaceholderPress = async () => {
if (selectedDevice) {
await dispatch(
createDeviceInstanceThunk({
device: selectedDevice,
useEmptyPassphrase: true,
}),
)
.unwrap()
.then(result => onSelectDevice(result.device));
}
};

return (
<VStack spacing="sp12" paddingHorizontal="sp16">
{showPlaceholder && (
<WalletItemBase
variant="standard"
onPress={handlePlaceholderPress}
isSelectable
isSelected={false}
/>
)}
{devices.map(device => {
if (!device.state) {
return null;
Expand All @@ -24,7 +64,7 @@ export const WalletList = ({ onSelectDevice }: WalletListProps) => {
<WalletItem
key={device.state.staticSessionId}
deviceState={device.state}
isSelectable={devices.length > 1}
isSelectable={isSelectable}
onPress={() => onSelectDevice(device)}
/>
);
Expand Down
5 changes: 5 additions & 0 deletions suite-native/device/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
selectIsUnacquiredDevice,
PORTFOLIO_TRACKER_DEVICE_ID,
selectDevices,
selectDeviceInstances,
} from '@suite-common/wallet-core';
import { SettingsSliceRootState, selectFiatCurrencyCode } from '@suite-native/settings';
import { getTotalFiatBalance } from '@suite-common/wallet-utils';
Expand Down Expand Up @@ -99,3 +100,7 @@ export const selectViewOnlyDevicesAccountsNetworkSymbols = memoize(
);
},
);

export const selectHasNoDeviceWithEmptyPassphrase = memoize((state: DeviceRootState) =>
A.isEmpty(selectDeviceInstances(state).filter(d => d.useEmptyPassphrase)),
);
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9622,6 +9622,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@suite-native/device-manager@workspace:suite-native/device-manager"
dependencies:
"@mobily/ts-belt": "npm:^3.13.1"
"@react-navigation/native": "npm:6.1.18"
"@reduxjs/toolkit": "npm:1.9.5"
"@suite-common/redux-utils": "workspace:*"
Expand Down

0 comments on commit c214aa3

Please sign in to comment.