Skip to content

Commit

Permalink
feat(mobile): update FW alert on homescreen
Browse files Browse the repository at this point in the history
  • Loading branch information
Nodonisko committed Jan 14, 2025
1 parent d56a56c commit 6529440
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 40 deletions.
56 changes: 31 additions & 25 deletions suite-native/atoms/src/Stack.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ReactNode } from 'react';
import React, { ReactNode } from 'react';
import Animated from 'react-native-reanimated';
import { View } from 'react-native';

import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { NativeSpacing } from '@trezor/theme';
Expand Down Expand Up @@ -27,30 +29,34 @@ const spacerStyle = prepareNativeStyle<SpacerStyleProps>((utils, { spacing, orie
};
});

export const Stack = ({
children,
style,
spacing,
orientation = 'vertical',
...rest
}: StackProps) => {
const { applyStyle } = useNativeStyles();

return (
<Box
style={[
applyStyle(spacerStyle, {
spacing,
orientation,
}),
style,
]}
{...rest}
>
{children}
</Box>
);
};
export const Stack = React.forwardRef<View, StackProps>(
({ children, style, spacing, orientation = 'vertical', ...rest }: StackProps, ref) => {
const { applyStyle } = useNativeStyles();

return (
<Box
ref={ref}
style={[
applyStyle(spacerStyle, {
spacing,
orientation,
}),
style,
]}
{...rest}
>
{children}
</Box>
);
},
);

export const VStack = Stack;
export const HStack = (props: StackProps) => <Stack {...props} orientation="horizontal" />;

const AnimatedStack = Animated.createAnimatedComponent(Stack);
AnimatedStack.displayName = 'AnimatedStack';
export const AnimatedVStack = AnimatedStack;
export const AnimatedHStack = (props: StackProps) => (
<AnimatedStack {...props} orientation="horizontal" />
);
8 changes: 8 additions & 0 deletions suite-native/intl/src/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ export const en = {
enable: 'Enable',
},
},
firmwareUpdateAlert: {
title: 'New Trezor firmware version available.',
version: 'Version {version}',
button: {
close: 'Close',
update: 'Update',
},
},
},
accounts: {
accountLabelFieldHint: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
selectDeviceState,
} from '@suite-common/wallet-core';
import { useAlert } from '@suite-native/alerts';
import { FeatureFlag, useFeatureFlag } from '@suite-native/feature-flags';

import { FirmwareUpdateVersionCard } from './FirmwareVersionCard';

Expand All @@ -42,6 +43,7 @@ export const FirmwareUpdateScreen = () => {
const isDiscoveryRunning = useSelector((state: DiscoveryRootState & DeviceRootState) =>
selectIsDiscoveryActiveByDeviceState(state, deviceState),
);
const isFirmwareUpdateEnabled = useFeatureFlag(FeatureFlag.IsFirmwareUpdateEnabled);

const handleShowSeedBottomSheet = useCallback(() => {
showAlert({
Expand All @@ -66,7 +68,7 @@ export const FirmwareUpdateScreen = () => {
<Button
onPress={handleShowSeedBottomSheet}
style={applyStyle(firmwareUpdateButtonStyle)}
isDisabled={isDiscoveryRunning}
isDisabled={isDiscoveryRunning || isFirmwareUpdateEnabled}
isLoading={isDiscoveryRunning}
>
<Translation id="moduleDeviceSettings.firmware.firmwareUpdateScreen.updateButton" />
Expand Down
1 change: 1 addition & 0 deletions suite-native/module-home/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"jotai": "1.9.1",
"react": "18.2.0",
"react-native": "0.76.1",
"react-native-reanimated": "3.16.1",
"react-native-svg": "15.9.0",
"react-redux": "8.0.7"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
import { useSelector } from 'react-redux';
import { useMemo } from 'react';

import { useNavigation } from '@react-navigation/native';
import { atom, useAtomValue, useSetAtom } from 'jotai';

import { Box, Button, HStack, VStack, Text } from '@suite-native/atoms';
import { Icon } from '@suite-native/icons';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import {
DeviceRootState,
DiscoveryRootState,
selectDeviceId,
selectDeviceReleaseInfo,
selectDeviceState,
selectDeviceUpdateFirmwareVersion,
selectIsDeviceConnectedAndAuthorized,
selectIsDiscoveryActiveByDeviceState,
selectIsPortfolioTrackerDevice,
} from '@suite-common/wallet-core';
import {
RootStackParamList,
RootStackRoutes,
StackNavigationProps,
DeviceStackRoutes,
} from '@suite-native/navigation';
import { Translation } from '@suite-native/intl';
import { FeatureFlag, useFeatureFlag } from '@suite-native/feature-flags';

const containerStyle = prepareNativeStyle(utils => ({
flexDirection: 'row',
alignItems: 'flex-start',
borderRadius: utils.borders.radii.r12,
borderWidth: 1,
borderColor: utils.colors.backgroundAlertBlueSubtleOnElevationNegative,
backgroundColor: utils.colors.backgroundAlertBlueSubtleOnElevation1,
padding: utils.spacings.sp16,
gap: utils.spacings.sp12,
marginHorizontal: utils.spacings.sp16,
}));

const flex1Style = {
flex: 1,
};

type CloseStateItem = {
deviceId: string;
version: string;
};
const closeStateAtom = atom<CloseStateItem[]>([]);

export const FirmwareUpdateAlert = () => {
const { applyStyle } = useNativeStyles();
const updateFirmwareVersion = useSelector(selectDeviceUpdateFirmwareVersion);
const deviceReleaseInfo = useSelector(selectDeviceReleaseInfo);
const isPortfolioTrackerDevice = useSelector(selectIsPortfolioTrackerDevice);
const deviceId = useSelector(selectDeviceId);
const isConnected = useSelector(selectIsDeviceConnectedAndAuthorized);
const deviceState = useSelector(selectDeviceState);
const isDiscoveryRunning = useSelector((state: DiscoveryRootState & DeviceRootState) =>
selectIsDiscoveryActiveByDeviceState(state, deviceState),
);
const navigation =
useNavigation<StackNavigationProps<RootStackParamList, RootStackRoutes.AppTabs>>();
const setCloseState = useSetAtom(closeStateAtom);

const isClosedAtom = useMemo(
() =>
atom(get =>
get(closeStateAtom).some(
item => item.deviceId === deviceId && item.version === updateFirmwareVersion,
),
),
[deviceId, updateFirmwareVersion],
);

const isClosed = useAtomValue(isClosedAtom);
const isFirmwareUpdateEnabled = useFeatureFlag(FeatureFlag.IsFirmwareUpdateEnabled);

const isUpgradable = deviceReleaseInfo?.isNewer;

const handleUpdateFirmware = () => {
navigation.navigate(RootStackRoutes.DeviceSettingsStack, {
screen: DeviceStackRoutes.FirmwareUpdate,
});
};

const handleClose = () => {
if (!deviceId || !updateFirmwareVersion) return;

setCloseState(prev => [...prev, { deviceId, version: updateFirmwareVersion }]);
};

if (!isFirmwareUpdateEnabled) {
return null;
}

if (
!isUpgradable ||
isPortfolioTrackerDevice ||
isDiscoveryRunning ||
!isConnected ||
isClosed
) {
return null;
}

return (
<Animated.View style={applyStyle(containerStyle)} entering={FadeIn} exiting={FadeOut}>
<Icon name="info" size="large" />
<VStack spacing="sp12" style={flex1Style}>
<Box>
<Text variant="highlight">
<Translation id="moduleHome.firmwareUpdateAlert.title" />
</Text>
<Text>
<Translation
id="moduleHome.firmwareUpdateAlert.version"
values={{ version: updateFirmwareVersion }}
/>
</Text>
</Box>
<HStack spacing="sp8" style={flex1Style}>
<Button colorScheme="blueElevation0" onPress={handleClose} style={flex1Style}>
<Translation id="moduleHome.firmwareUpdateAlert.button.close" />
</Button>
<Button
colorScheme="blueBold"
onPress={handleUpdateFirmware}
style={flex1Style}
>
<Translation id="moduleHome.firmwareUpdateAlert.button.update" />
</Button>
</HStack>
</VStack>
</Animated.View>
);
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { forwardRef } from 'react';
import { useSelector } from 'react-redux';
import { LinearTransition } from 'react-native-reanimated';

import { useNavigation } from '@react-navigation/native';

import { selectHasDeviceDiscovery, selectIsDeviceAuthorized } from '@suite-common/wallet-core';
import { Button, VStack } from '@suite-native/atoms';
import { AnimatedVStack, Button, VStack } from '@suite-native/atoms';
import { Assets } from '@suite-native/assets';
import {
RootStackParamList,
Expand All @@ -14,6 +15,7 @@ import {
import { Translation } from '@suite-native/intl';

import { PortfolioGraph, PortfolioGraphRef } from './PortfolioGraph';
import { FirmwareUpdateAlert } from './FirmwareUpdateAlert';

export const PortfolioContent = forwardRef<PortfolioGraphRef>((_props, ref) => {
const navigation = useNavigation<StackNavigationProps<RootStackParamList, RootStackRoutes>>();
Expand All @@ -28,19 +30,23 @@ export const PortfolioContent = forwardRef<PortfolioGraphRef>((_props, ref) => {

return (
<VStack spacing="sp32" marginTop="sp8">
<PortfolioGraph ref={ref} />
<VStack spacing="sp24" marginHorizontal="sp16">
{showReceiveButton && (
<Button
data-testID="@home/portolio/recieve-button"
onPress={handleReceive}
viewLeft="arrowLineDown"
>
<Translation id="moduleHome.buttons.receive" />
</Button>
)}
<Assets />
</VStack>
<FirmwareUpdateAlert />

<AnimatedVStack spacing="sp32" layout={LinearTransition}>
<PortfolioGraph ref={ref} />
<VStack spacing="sp24" marginHorizontal="sp16">
{showReceiveButton && (
<Button
data-testID="@home/portolio/recieve-button"
onPress={handleReceive}
viewLeft="arrowLineDown"
>
<Translation id="moduleHome.buttons.receive" />
</Button>
)}
<Assets />
</VStack>
</AnimatedVStack>
</VStack>
);
});
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10765,6 +10765,7 @@ __metadata:
jotai: "npm:1.9.1"
react: "npm:18.2.0"
react-native: "npm:0.76.1"
react-native-reanimated: "npm:3.16.1"
react-native-svg: "npm:15.9.0"
react-redux: "npm:8.0.7"
languageName: unknown
Expand Down

0 comments on commit 6529440

Please sign in to comment.