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(mobile): add missing bottom sheet to FW update #15824

Merged
merged 1 commit into from
Dec 9, 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
33 changes: 33 additions & 0 deletions suite-native/atoms/src/NumberedListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ReactNode } from 'react';

import { Color, TypographyStyle } from '@trezor/theme';

import { Box } from './Box';
import { Text } from './Text';
import { HStack } from './Stack';

type NumberedListItemProps = {
children: ReactNode;
variant?: TypographyStyle;
color?: Color;
number: number;
};

const numberStyle = {
minWidth: 16,
};

export const NumberedListItem = ({ children, variant, color, number }: NumberedListItemProps) => (
<HStack>
<Box style={numberStyle}>
<Text variant={variant} color={color}>
{number}.
</Text>
</Box>
<Box flexShrink={1}>
<Text variant={variant} color={color}>
{children}
</Text>
</Box>
</HStack>
);
9 changes: 3 additions & 6 deletions suite-native/atoms/src/Sheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState, ReactNode } from 'react';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Animated from 'react-native-reanimated';
import Animated, { LinearTransition } from 'react-native-reanimated';
import { ScrollView, PanGestureHandler } from 'react-native-gesture-handler';
import { GestureResponderEvent, Pressable } from 'react-native';

Expand Down Expand Up @@ -77,18 +77,14 @@ export const BottomSheet = ({
}
}, [isVisible, openSheetAnimated]);

const handleCloseSheet = () => {
closeSheetAnimated();
};

const handlePressOutside = (event: GestureResponderEvent) => {
if (event.target === event.currentTarget) closeSheetAnimated();
};

const insetBottom = Math.max(insets.bottom, DEFAULT_INSET_BOTTOM);

return (
<BottomSheetContainer isVisible={isVisible} onClose={handleCloseSheet}>
<BottomSheetContainer isVisible={isVisible} onClose={closeSheetAnimated}>
<Animated.View
style={[animatedSheetWithOverlayStyle, applyStyle(sheetWithOverlayStyle)]}
>
Expand All @@ -107,6 +103,7 @@ export const BottomSheet = ({
insetBottom,
}),
]}
layout={LinearTransition}
>
<BottomSheetHeader
title={title}
Expand Down
1 change: 1 addition & 0 deletions suite-native/atoms/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export * from './Skeleton/BoxSkeleton';
export * from './Skeleton/ListItemSkeleton';
export * from './IconListItem';
export * from './BulletListItem';
export * from './NumberedListItem';
export * from './SelectableItem';
export * from './constants';
export * from './useIllustrationColors';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useState } from 'react';
import Animated, { FadeIn } from 'react-native-reanimated';

import { BottomSheet, Box, Button, NumberedListItem, Text, VStack } from '@suite-native/atoms';
import { Translation } from '@suite-native/intl';

type MayBeStuckedBottomSheetProps = {
isOpened: boolean;
onClose: () => void;
};

export const MayBeStuckedBottomSheet = ({ isOpened, onClose }: MayBeStuckedBottomSheetProps) => {
const [visiblePart, setVisiblePart] = useState<1 | 2>(1);

const handleClose = () => {
onClose();
setVisiblePart(1);
};

return (
<BottomSheet
isVisible={isOpened}
onClose={onClose}
isCloseDisplayed={false}
paddingHorizontal="sp24"
>
{visiblePart === 1 && (
<Animated.View>
<VStack spacing="sp24">
<VStack alignItems="center" spacing="sp8">
<Text textAlign="center" variant="titleSmall">
<Translation id="moduleDeviceSettings.firmware.stuckedBottomSheet.part1.title" />
</Text>
<Text textAlign="center" color="textSubdued">
<Translation id="moduleDeviceSettings.firmware.stuckedBottomSheet.part1.description" />
</Text>
</VStack>

<VStack spacing="sp16">
<Button onPress={() => setVisiblePart(2)} colorScheme="yellowBold">
<Translation id="moduleDeviceSettings.firmware.stuckedBottomSheet.part1.continueButton" />
</Button>
<Button onPress={handleClose} colorScheme="yellowElevation0">
<Translation id="moduleDeviceSettings.firmware.stuckedBottomSheet.part1.closeButton" />
</Button>
</VStack>
</VStack>
</Animated.View>
)}
{visiblePart === 2 && (
<Animated.View entering={FadeIn}>
<VStack spacing="sp24">
<VStack spacing="sp8">
<Text variant="titleSmall">
<Translation id="moduleDeviceSettings.firmware.stuckedBottomSheet.part2.title" />
</Text>
<Text color="textSubdued">
<Translation id="moduleDeviceSettings.firmware.stuckedBottomSheet.part2.subtitle" />
</Text>
</VStack>

<VStack spacing="sp2">
<NumberedListItem number={1}>
<Translation id="moduleDeviceSettings.firmware.stuckedBottomSheet.part2.tip1" />
</NumberedListItem>
<NumberedListItem number={2}>
<Translation id="moduleDeviceSettings.firmware.stuckedBottomSheet.part2.tip2" />
</NumberedListItem>
<NumberedListItem number={3}>
<Translation id="moduleDeviceSettings.firmware.stuckedBottomSheet.part2.tip3" />
</NumberedListItem>
</VStack>

<Box flex={1}>
<Button onPress={handleClose} colorScheme="primary">
<Translation id="moduleDeviceSettings.firmware.stuckedBottomSheet.part2.gotItButton" />
</Button>
</Box>
</VStack>
</Animated.View>
)}
</BottomSheet>
);
};
86 changes: 25 additions & 61 deletions suite-native/firmware/src/components/UpdateProgressIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable no-self-assign */
import { useEffect, useMemo } from 'react';
import { Platform } from 'react-native';
import { useEffect } from 'react';
import {
SharedValue,
useDerivedValue,
Expand All @@ -18,21 +17,19 @@ import {
ImageSVG,
MatrixColorFilterProps,
Paint,
Paragraph,
Path,
Shadow,
Skia,
TextAlign,
useSVG,
} from '@shopify/react-native-skia';

import { useNativeStyles } from '@trezor/styles';

const CANVAS_SIZE = 160;
const CIRCLE_DIAMETER = 128;
const CIRCLE_DIAMETER = 144;
// 1.25 is used to make sure that the circle is not cut off when animating using withSpring
const CANVAS_SIZE = CIRCLE_DIAMETER * 1.25;
const CIRCLE_CENTER = CANVAS_SIZE / 2;

const PROGRESS_STROKE_WIDTH = 5;
const PROGRESS_STROKE_WIDTH = 3;

const CHECKMARK_OFFSET_X = 10; // Adjust left/right position
const CHECKMARK_OFFSET_Y = 0; // Adjust up/down position
Expand All @@ -49,12 +46,6 @@ const progressCirclePath = Skia.Path.MakeFromSVGString(
`M ${CIRCLE_CENTER},${CIRCLE_CENTER - (CIRCLE_DIAMETER - PROGRESS_STROKE_WIDTH) / 2} A ${(CIRCLE_DIAMETER - PROGRESS_STROKE_WIDTH) / 2},${(CIRCLE_DIAMETER - PROGRESS_STROKE_WIDTH) / 2} 0 1,1 ${CIRCLE_CENTER},${CIRCLE_CENTER + (CIRCLE_DIAMETER - PROGRESS_STROKE_WIDTH) / 2} A ${(CIRCLE_DIAMETER - PROGRESS_STROKE_WIDTH) / 2},${(CIRCLE_DIAMETER - PROGRESS_STROKE_WIDTH) / 2} 0 1,1 ${CIRCLE_CENTER},${CIRCLE_CENTER - (CIRCLE_DIAMETER - PROGRESS_STROKE_WIDTH) / 2}`,
)!;

const fontStyle = {
fontFamily: Platform.select({ ios: 'Helvetica', default: 'serif' }),
fontSize: 34,
letterSpacing: -1.4,
};

// For some reason you can't easily animate opacity of SVG image, so we need to do it manually.
const AnimatedOpacity = ({
opacity,
Expand Down Expand Up @@ -152,7 +143,7 @@ export const UpdateProgressIndicator = ({
checkmarkAnimationProgress.value = 0;
progressEnd.value = withSpring(progress / 100);

trezorLogoOpacity.value = 0;
trezorLogoOpacity.value = withTiming(1, { duration: 600 });
errorSvgOpacity.value = 0;
paragraphOpacity.value = withTiming(1, { duration: 600 });
}
Expand Down Expand Up @@ -195,52 +186,17 @@ export const UpdateProgressIndicator = ({
paragraphOpacity,
]);

const paragraph = useMemo(() => {
if (!isInProgress) return null;

return Skia.ParagraphBuilder.Make({
textAlign: TextAlign.Center,
})
.pushStyle({ ...fontStyle, color: Skia.Color(utils.colors.textPrimaryDefault) })
.addText(`${progress}%`)
.build();
}, [progress, utils.colors.textPrimaryDefault, isInProgress]);

const isDone = isSuccess || isError;

return (
<Canvas style={{ width: CANVAS_SIZE, height: CANVAS_SIZE }}>
<Circle
cx={CIRCLE_CENTER}
cy={CIRCLE_CENTER}
r={CIRCLE_DIAMETER / 2}
color={utils.colors.backgroundSurfaceElevation1}
></Circle>

{isInProgress && (
<AnimatedOpacity opacity={paragraphOpacity}>
<Paragraph
paragraph={paragraph}
x={0}
y={CIRCLE_CENTER - fontStyle.fontSize / 2}
width={CANVAS_SIZE}
/>
</AnimatedOpacity>
)}

<AnimatedOpacity opacity={trezorLogoOpacity}>
<ImageSVG
svg={trezorLogoSvg}
x={CIRCLE_CENTER - CIRCLE_DIAMETER / 4}
y={CIRCLE_CENTER - CIRCLE_DIAMETER / 4}
color={utils.colors.textPrimaryDefault}
width={CIRCLE_DIAMETER / 2}
height={CIRCLE_DIAMETER / 2}
opacity={trezorLogoOpacity}
/>
</AnimatedOpacity>

<Group>
<Path
path={progressCirclePath}
color={utils.colors.backgroundPrimarySubtleOnElevationNegative}
strokeCap="round"
strokeJoin="round"
strokeWidth={PROGRESS_STROKE_WIDTH}
style="stroke"
/>
<Path
path={progressCirclePath}
start={0}
Expand All @@ -250,16 +206,24 @@ export const UpdateProgressIndicator = ({
strokeJoin="round"
strokeWidth={PROGRESS_STROKE_WIDTH}
style="stroke"
>
{!isDone && <Shadow dx={0} dy={2} blur={4} color="rgba(0,0,0,0.1)" />}
</Path>
/>
</Group>
<Circle
cx={CIRCLE_CENTER}
cy={CIRCLE_CENTER}
r={animatedBackgroundRadius}
color={backgroundColorFinished}
/>
<AnimatedOpacity opacity={trezorLogoOpacity}>
<ImageSVG
svg={trezorLogoSvg}
x={CIRCLE_CENTER - CIRCLE_DIAMETER / 4}
y={CIRCLE_CENTER - CIRCLE_DIAMETER / 4}
color={utils.colors.textPrimaryDefault}
width={CIRCLE_DIAMETER / 2}
height={CIRCLE_DIAMETER / 2}
/>
</AnimatedOpacity>
{isSuccess && (
<Path
path={checkmarkPath}
Expand Down
10 changes: 9 additions & 1 deletion suite-native/firmware/src/hooks/useFirmware.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,26 @@ export const useFirmware = (params: UseFirmwareInstallationParams) => {
} else if (isInitialState && !confirmOnDevice) {
text = {
title: 'moduleDeviceSettings.firmware.firmwareUpdateProgress.initializing.title',
subtitle:
'moduleDeviceSettings.firmware.firmwareUpdateProgress.dontCloseAppMessage',
};
} else if (isInitialState) {
text = {
title: 'moduleDeviceSettings.firmware.firmwareUpdateProgress.confirming.title',
subtitle:
'moduleDeviceSettings.firmware.firmwareUpdateProgress.confirmOnDeviceMessage',
};
} else if (operation === 'validating') {
text = {
title: 'moduleDeviceSettings.firmware.firmwareUpdateProgress.validating.title',
subtitle:
'moduleDeviceSettings.firmware.firmwareUpdateProgress.dontCloseAppMessage',
};
} else if (operation === 'restarting') {
text = {
title: 'moduleDeviceSettings.firmware.firmwareUpdateProgress.restarting.title',
subtitle:
'moduleDeviceSettings.firmware.firmwareUpdateProgress.dontCloseAppMessage',
};
} else if (operation === 'completed' || status === 'done') {
text = {
Expand All @@ -121,7 +129,7 @@ export const useFirmware = (params: UseFirmwareInstallationParams) => {
text = {
title: 'moduleDeviceSettings.firmware.firmwareUpdateProgress.installing.title',
subtitle:
'moduleDeviceSettings.firmware.firmwareUpdateProgress.installing.subtitle',
'moduleDeviceSettings.firmware.firmwareUpdateProgress.dontCloseAppMessage',
};
}

Expand Down
Loading
Loading