Skip to content

Commit

Permalink
✨feat(llm): add scan WS qr code
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasWerey committed Aug 6, 2024
1 parent f5f105d commit 53ec55b
Show file tree
Hide file tree
Showing 14 changed files with 222 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Props = {
buttonTitle: string;
onPress: () => void;
event: string;
hasNotBackground?: boolean;
};

const IconSettings = () => <Icon name="settings" size={16} color="neutral.c100" />;
Expand All @@ -22,11 +23,12 @@ const FallbackCameraBody: React.FC<Props> = ({
buttonTitle,
onPress,
event,
hasNotBackground,
}: Props) => {
const { colors } = useTheme();

return (
<Flex flex={1} bg="background.main" px={6}>
<Flex flex={1} bg={hasNotBackground ? null : "background.main"} px={6}>
<Flex flex={1} alignItems="center" justifyContent="center">
<FallbackCamera color={colors.constant.white} />
<Text variant="paragraph" mt={9} mb={3} fontSize={6}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Props = {
* context, rather than directly showing an error screen.
* */
optimisticallyMountChildren?: boolean;
fallBackHasNoBackground?: boolean;
};

/**
Expand All @@ -38,6 +39,7 @@ type Props = {
const RequiresCameraPermissions: React.FC<Props> = ({
children,
optimisticallyMountChildren = false,
fallBackHasNoBackground = false,
}) => {
const { t } = useTranslation();
const {
Expand All @@ -59,6 +61,7 @@ const RequiresCameraPermissions: React.FC<Props> = ({
<Fallback
event="CameraPressAuthorize"
onPress={requestPermission}
hasNotBackground={fallBackHasNoBackground}
title={t("permissions.camera.title")}
description={t("permissions.camera.authorizeDescription")}
buttonTitle={t("permissions.camera.authorizeButtonTitle")}
Expand All @@ -68,6 +71,7 @@ const RequiresCameraPermissions: React.FC<Props> = ({
<Fallback
event="CameraOpenSettings"
onPress={openAppSettings}
hasNotBackground={fallBackHasNoBackground}
title={t("permissions.camera.title")}
description={t("permissions.camera.goToSettingsDescription")}
buttonTitle={t("permissions.camera.goToSettingsButtonTitle")}
Expand Down
11 changes: 10 additions & 1 deletion apps/ledger-live-mobile/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -6805,7 +6805,16 @@
}
},
"scan": {
"title": "Scan"
"title": "Scan",
"description": "Scan QR code",
"explanation": {
"title": "Scan and synchronize your accounts using another Ledger Live app",
"steps": {
"step1": "Open the Ledger Live app you want to sync",
"step2": "Go to <0>Settings</0> <1>></1> <0>General</0> <1>></1> <0>Ledger Sync</0> <1>></1> <0>Synchronize</0> <1>></1> <0>Show QR</0>",
"step3": "Scan QR code until loader hits 100%."
}
}
},
"pinCode": {
"title": "Enter your code",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useState } from "react";
import SelectAddAccountMethod from "./SelectAddAccountMethod";
import ChooseSyncMethod from "LLM/features/WalletSync/screens/Synchronize/ChooseMethod";
import QrCodeMethod from "LLM/features/WalletSync/screens/Synchronize/QrCodeMethod";
import PinCodeInput from "LLM/features/WalletSync/screens/Synchronize/PinCodeInput";
import { TrackScreen } from "~/analytics";
import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets";
import { Steps } from "../../../types/enum/addAccount";
Expand Down Expand Up @@ -49,6 +50,12 @@ const StepFlow = ({
if (onGoBack) onGoBack(() => setCurrentStep(prevStep => getPreviousStep(prevStep)));
}, [getPreviousStep, onGoBack]);

// Here we retrieve the url of the trustchain from the scanned QR code on LLD
const onQrCodeScanned = (data: string) => {
console.log(data);
// setCurrentStep(Steps.PinCodeInput);
};

const getScene = () => {
switch (currentStep) {
case Steps.AddAccountMethod:
Expand All @@ -70,7 +77,9 @@ const StepFlow = ({
</>
);
case Steps.QrCodeMethod:
return <QrCodeMethod />;
return <QrCodeMethod onQrCodeScanned={onQrCodeScanned} />;
case Steps.PinCodeInput:
return <PinCodeInput />;
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export enum Steps {
AddAccountMethod = "AddAccountMethod",
ChooseSyncMethod = "ChooseSyncMethod",
QrCodeMethod = "QrCodeMethod",
PinCodeInput = "PinCodeInput",
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ import Activation from ".";
import { TrackScreen } from "~/analytics";
import ChooseSyncMethod from "../../screens/Synchronize/ChooseMethod";
import QrCodeMethod from "../../screens/Synchronize/QrCodeMethod";
import PinCodeInput from "../../screens/Synchronize/PinCodeInput";
import { Steps } from "../../types/Activation";
import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics";

type Props = {
currentStep: Steps;
navigateToChooseSyncMethod: () => void;
navigateToQrCodeMethod: () => void;
onQrCodeScanned: (data: string) => void;
};

const ActivationFlow = ({
currentStep,
navigateToChooseSyncMethod,
navigateToQrCodeMethod,
onQrCodeScanned,
}: Props) => {
const getScene = () => {
switch (currentStep) {
Expand All @@ -34,7 +37,9 @@ const ActivationFlow = ({
</>
);
case Steps.QrCodeMethod:
return <QrCodeMethod />;
return <QrCodeMethod onQrCodeScanned={onQrCodeScanned} />;
case Steps.PinCodeInput:
return <PinCodeInput />;
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from "react";
import { ScrollContainer, Text, Flex } from "@ledgerhq/native-ui";
import { useTranslation } from "react-i18next";
import styled, { useTheme } from "styled-components/native";

type Props = {
steps: {
description: React.JSX.Element;
}[];
};

const BulletPoint = styled(Flex)`
width: 16px;
height: 16px;
justify-content: center;
align-items: center;
background-color: ${({ theme }) => theme.colors.primary.c80};
border-radius: 100px;
`;

const FlexContainer = styled(Flex)`
margin-bottom: 16px;
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 12px;
`;

const BottomContainer = ({ steps }: Props) => {
const { colors } = useTheme();
const { t } = useTranslation();

return (
<ScrollContainer
px={16}
py={24}
background={colors.opacityDefault.c05}
borderRadius={24}
showsVerticalScrollIndicator={false}
>
<Text variant="h4" fontSize={18} color={colors.neutral.c100} mb={24}>
{t("walletSync.synchronize.qrCode.scan.explanation.title")}
</Text>
<Flex width={"100%"} mb={32}>
{steps.map((step, index) => (
<FlexContainer key={index}>
<BulletPoint>
<Text fontSize={11} color={colors.neutral.c00}>
{index + 1}
</Text>
</BulletPoint>
<Flex flex={1}>{step.description}</Flex>
</FlexContainer>
))}
</Flex>
</ScrollContainer>
);
};

export default BottomContainer;
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from "react";
import { Flex, Text, NumberedList, ScrollContainer } from "@ledgerhq/native-ui";
import { Flex, Text } from "@ledgerhq/native-ui";
import styled, { useTheme } from "styled-components/native";
import QRCode from "react-native-qrcode-svg";
import getWindowDimensions from "~/logic/getWindowDimensions";
import { Trans, useTranslation } from "react-i18next";
import BottomContainer from "./BottomContainer";

const Italic = styled(Text)`
font-style: italic;
Expand All @@ -26,14 +27,14 @@ const QrCode = ({ qrCodeValue }: Props) => {
const steps = [
{
description: (
<Text variant="body" flex={1} ml={12} fontSize={14} color={colors.opacityDefault.c70}>
<Text variant="body" flex={1} fontSize={14} color={colors.opacityDefault.c70}>
{t("walletSync.synchronize.qrCode.show.explanation.steps.step1")}
</Text>
),
},
{
description: (
<Text variant="body" flex={1} ml={12} fontSize={14} color={colors.opacityDefault.c70}>
<Text variant="body" flex={1} fontSize={14} color={colors.opacityDefault.c70}>
<Trans
i18nKey="walletSync.synchronize.qrCode.show.explanation.steps.step2"
components={[
Expand All @@ -46,7 +47,7 @@ const QrCode = ({ qrCodeValue }: Props) => {
},
{
description: (
<Text variant="body" flex={1} ml={12} fontSize={14} color={colors.opacityDefault.c70}>
<Text variant="body" flex={1} fontSize={14} color={colors.opacityDefault.c70}>
{t("walletSync.synchronize.qrCode.show.explanation.steps.step3")}
</Text>
),
Expand Down Expand Up @@ -79,20 +80,7 @@ const QrCode = ({ qrCodeValue }: Props) => {
size={QRCodeSize}
/>
</Flex>
<ScrollContainer
px={16}
mb={10}
width={"100%"}
maxHeight={280}
background={colors.opacityDefault.c05}
borderRadius={24}
showsVerticalScrollIndicator={false}
>
<Text variant="h4" fontSize={18} color={colors.neutral.c100} my={24}>
{t("walletSync.synchronize.qrCode.show.explanation.title")}
</Text>
<NumberedList items={steps} />
</ScrollContainer>
<BottomContainer steps={steps} />
</Flex>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from "react";
import { Flex, Icons, Text } from "@ledgerhq/native-ui";
import { Trans, useTranslation } from "react-i18next";
import styled, { useTheme } from "styled-components/native";
import BottomContainer from "./BottomContainer";
import { BarCodeScanningResult, Camera, CameraType } from "expo-camera";
import { BarCodeScanner } from "expo-barcode-scanner";
import ScanTargetSvg from "./ScanTargetSvg";
import RequiresCameraPermissions from "~/components/RequiresCameraPermissions";

type Props = {
onQrCodeScanned: (data: string) => void;
};

const Italic = styled(Text)`
font-style: italic;
`;
// Won't work since we don't have inter italic font

const ScanQrCode = ({ onQrCodeScanned }: Props) => {
const { t } = useTranslation();
const { colors } = useTheme();

const onBarCodeScanned = ({ data }: BarCodeScanningResult) => onQrCodeScanned(data);

const steps = [
{
description: (
<Text variant="body" flex={1} fontSize={14} color={colors.opacityDefault.c70}>
{t("walletSync.synchronize.qrCode.scan.explanation.steps.step1")}
</Text>
),
},
{
description: (
<Text variant="body" flex={1} fontSize={14} color={colors.opacityDefault.c70}>
<Trans
i18nKey="walletSync.synchronize.qrCode.scan.explanation.steps.step2"
components={[
<Italic key={0} color={colors.opacityDefault.c70} />,
<Text key={1} flex={1} color={colors.opacityDefault.c30} />,
]}
/>
</Text>
),
},
{
description: (
<Text variant="body" flex={1} fontSize={14} color={colors.opacityDefault.c70}>
{t("walletSync.synchronize.qrCode.scan.explanation.steps.step3")}
</Text>
),
},
];

return (
<Flex minHeight={400} justifyContent={"center"} alignItems={"center"} rowGap={24}>
<RequiresCameraPermissions optimisticallyMountChildren fallBackHasNoBackground>
<Flex>
<Camera
style={{
backgroundColor: colors.neutral.c50,
borderRadius: 30, // Border Radius doesn't work on Android
overflow: "hidden",
}}
type={CameraType.back}
barCodeScannerSettings={{
barCodeTypes: [BarCodeScanner.Constants.BarCodeType.qr],
}}
onBarCodeScanned={onBarCodeScanned}
>
<ScanTargetSvg />
</Camera>
</Flex>
<Flex flexDirection={"row"} alignItems={"center"} columnGap={8}>
<Text variant="bodyLineHeight">
{t("walletSync.synchronize.qrCode.scan.description")}
</Text>
<Icons.QrCode />
</Flex>
<BottomContainer steps={steps} />
</RequiresCameraPermissions>
</Flex>
);
};

export default ScanQrCode;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as React from "react";
import Svg, { SvgProps, Path } from "react-native-svg";
const ScanTargetSvg = (props: SvgProps) => (
<Svg width={289} height={289} fill="none" {...props}>
<Path
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={7}
d="M4 74.25V55.2c0-17.922 0-26.883 3.488-33.728A32 32 0 0 1 21.472 7.488C28.317 4 37.278 4 55.2 4h19.05M4 214.75v19.05c0 17.922 0 26.883 3.488 33.728a32 32 0 0 0 13.984 13.984C28.317 285 37.278 285 55.2 285h19.05m140.5 0h19.05c17.922 0 26.883 0 33.728-3.488a32 32 0 0 0 13.984-13.984C285 260.683 285 251.722 285 233.8v-19.05m0-140.5V55.2c0-17.922 0-26.883-3.488-33.728a32 32 0 0 0-13.984-13.984C260.683 4 251.722 4 233.8 4h-19.05"
/>
</Svg>
);
export default ScanTargetSvg;
Loading

0 comments on commit 53ec55b

Please sign in to comment.