From 71e17b0a203a383f0588ff0ca656fe980bc877f0 Mon Sep 17 00:00:00 2001 From: Lucas Werey Date: Thu, 8 Aug 2024 09:30:25 +0200 Subject: [PATCH] :sparkles:feat(llm): trustchain integration to qr code scan --- .changeset/real-apricots-rush.md | 5 ++ .../AddAccount/components/StepFlow.tsx | 24 +++++- .../AddAccount/useAddAccountViewModel.ts | 8 +- .../components/Activation/ActivationFlow.tsx | 23 +++++- .../WalletSync/hooks/useInstanceName.ts | 12 +++ .../WalletSync/hooks/useSyncWithQrCode.ts | 75 +++++++++++++++++++ .../WalletSync/hooks/useTrustchainSdk.ts | 13 +--- .../screens/Activation/ActivationDrawer.tsx | 2 + .../Activation/useActivationDrawerModel.ts | 8 +- .../screens/Synchronize/PinCodeInput.tsx | 14 +++- 10 files changed, 154 insertions(+), 30 deletions(-) create mode 100644 .changeset/real-apricots-rush.md create mode 100644 apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useInstanceName.ts create mode 100644 apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useSyncWithQrCode.ts diff --git a/.changeset/real-apricots-rush.md b/.changeset/real-apricots-rush.md new file mode 100644 index 000000000000..0d9dd54104c4 --- /dev/null +++ b/.changeset/real-apricots-rush.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +Trustchain integration on LLM scan qr code. We can now join a trustchain scanning a qr code diff --git a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/components/StepFlow.tsx b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/components/StepFlow.tsx index 149fba5c38ef..aff96d6ad81c 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/components/StepFlow.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/components/StepFlow.tsx @@ -2,13 +2,15 @@ import React 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 { AnalyticsPage } from "LLM/features/WalletSync/hooks/useLedgerSyncAnalytics"; import { Options, Steps } from "LLM/features/WalletSync/types/Activation"; import SyncError from "LLM/features/WalletSync/screens/Synchronize/SyncError"; import PinCodeDisplay from "LLM/features/WalletSync/screens/Synchronize/PinCodeDisplay"; +import PinCodeInput from "LLM/features/WalletSync/screens/Synchronize/PinCodeInput"; +import { useInitMemberCredentials } from "~/newArch/features/WalletSync/hooks/useInitMemberCredentials"; +import { useSyncWithQrCode } from "~/newArch/features/WalletSync/hooks/useSyncWithQrCode"; type Props = { currentStep: Steps; @@ -19,7 +21,7 @@ type Props = { currentOption: Options; navigateToChooseSyncMethod: () => void; navigateToQrCodeMethod: () => void; - onQrCodeScanned: (data: string) => void; + onQrCodeScanned: () => void; qrProcess: { url: string | null; error: Error | null; @@ -38,7 +40,21 @@ const StepFlow = ({ navigateToQrCodeMethod, onQrCodeScanned, qrProcess, + setCurrentStep, }: Props) => { + const { memberCredentials } = useInitMemberCredentials(); + + const { handleStart, handleSendDigits, inputCallback, digits } = useSyncWithQrCode(); + + const handleQrCodeScanned = (data: string) => { + onQrCodeScanned(); + if (memberCredentials) handleStart(data, memberCredentials, setCurrentStep); + }; + + const handlePinCodeSubmit = (input: string) => { + if (input && inputCallback && digits === input.length) handleSendDigits(inputCallback, input); + }; + const getScene = () => { switch (currentStep) { case Steps.AddAccountMethod: @@ -62,7 +78,7 @@ const StepFlow = ({ case Steps.QrCodeMethod: return ( @@ -72,7 +88,7 @@ const StepFlow = ({ return qrProcess.pinCode ? : null; case Steps.PinInput: - return ; + return ; case Steps.SyncError: return ; diff --git a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/useAddAccountViewModel.ts b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/useAddAccountViewModel.ts index 06a6c49721fe..8e1673f00894 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/useAddAccountViewModel.ts +++ b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AddAccount/useAddAccountViewModel.ts @@ -54,11 +54,7 @@ const useAddAccountViewModel = ({ isOpened, onClose }: AddAccountDrawerProps) => currentOption, }); - const onQrCodeScanned = (data: string) => { - // eslint-disable-next-line no-console - console.log(data); - //setCurrentStep(Steps.PinCodeInput); - }; + const onQrCodeScanned = () => setCurrentStep(Steps.PinInput); return { isAddAccountDrawerVisible: isOpened, @@ -69,8 +65,8 @@ const useAddAccountViewModel = ({ isOpened, onClose }: AddAccountDrawerProps) => setCurrentOption, currentOption, setCurrentStep, - onGoBack, onQrCodeScanned, + onGoBack, qrProcess: { url, error, diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Activation/ActivationFlow.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Activation/ActivationFlow.tsx index ff01fde20e7c..2cd1fd66f8d7 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Activation/ActivationFlow.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/components/Activation/ActivationFlow.tsx @@ -8,6 +8,8 @@ import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; import PinCodeDisplay from "../../screens/Synchronize/PinCodeDisplay"; import PinCodeInput from "../../screens/Synchronize/PinCodeInput"; import SyncError from "../../screens/Synchronize/SyncError"; +import { useInitMemberCredentials } from "../../hooks/useInitMemberCredentials"; +import { useSyncWithQrCode } from "../../hooks/useSyncWithQrCode"; type Props = { currentStep: Steps; @@ -19,9 +21,10 @@ type Props = { isLoading: boolean; pinCode: string | null; }; - onQrCodeScanned: (data: string) => void; + onQrCodeScanned: () => void; currentOption: Options; setOption: (option: Options) => void; + setCurrentStep: (step: Steps) => void; }; const ActivationFlow = ({ @@ -32,7 +35,21 @@ const ActivationFlow = ({ currentOption, setOption, onQrCodeScanned, + setCurrentStep, }: Props) => { + const { memberCredentials } = useInitMemberCredentials(); + + const { handleStart, handleSendDigits, inputCallback, digits } = useSyncWithQrCode(); + + const handleQrCodeScanned = (data: string) => { + onQrCodeScanned(); + if (memberCredentials) handleStart(data, memberCredentials, setCurrentStep); + }; + + const handlePinCodeSubmit = (input: string) => { + if (input && inputCallback && digits === input.length) handleSendDigits(inputCallback, input); + }; + const getScene = () => { switch (currentStep) { case Steps.Activation: @@ -52,7 +69,7 @@ const ActivationFlow = ({ case Steps.QrCodeMethod: return ( @@ -62,7 +79,7 @@ const ActivationFlow = ({ return qrProcess.pinCode ? : null; case Steps.PinInput: - return ; + return ; case Steps.SyncError: return ; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useInstanceName.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useInstanceName.ts new file mode 100644 index 000000000000..a0100935a0f5 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useInstanceName.ts @@ -0,0 +1,12 @@ +import { getEnv } from "@ledgerhq/live-env"; +import { Platform } from "react-native"; + +const platformMap: Record = { + ios: "iOS", + android: "Android", +}; + +export function useInstanceName(): string { + const hash = getEnv("USER_ID").slice(0, 5); + return `${platformMap[Platform.OS] ?? Platform.OS} ${Platform.Version} ${hash ? " " + hash : ""}`; +} diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useSyncWithQrCode.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useSyncWithQrCode.ts new file mode 100644 index 000000000000..7c6579407269 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useSyncWithQrCode.ts @@ -0,0 +1,75 @@ +import { useCallback, useState } from "react"; +import { MemberCredentials } from "@ledgerhq/trustchain/types"; +import { createQRCodeCandidateInstance } from "@ledgerhq/trustchain/qrcode/index"; +import { InvalidDigitsError } from "@ledgerhq/trustchain/errors"; +import { setTrustchain } from "@ledgerhq/trustchain/store"; +import { useDispatch } from "react-redux"; +import { useNavigation } from "@react-navigation/native"; +import { Steps } from "../types/Activation"; +import { NavigatorName, ScreenName } from "~/const"; +import { useInstanceName } from "./useInstanceName"; + +export const useSyncWithQrCode = () => { + const [digits, setDigits] = useState(null); + const [input, setInput] = useState(null); + const instanceName = useInstanceName(); + + const navigation = useNavigation(); + + const [inputCallback, setInputCallback] = useState<((input: string) => void) | null>(null); + const dispatch = useDispatch(); + + const onRequestQRCodeInput = useCallback( + (config: { digits: number }, callback: (input: string) => void) => { + setDigits(config.digits); + setInputCallback(() => callback); + }, + [], + ); + + const onSyncFinished = useCallback(() => { + setDigits(null); + setInput(null); + setInputCallback(null); + navigation.navigate(NavigatorName.WalletSync, { + screen: ScreenName.WalletSyncSuccess, + params: { + created: false, + }, + }); + }, [navigation]); + + const handleStart = useCallback( + async ( + url: string, + memberCredentials: MemberCredentials, + setCurrentStep: (step: Steps) => void, + ) => { + try { + const trustchain = await createQRCodeCandidateInstance({ + memberCredentials, + scannedUrl: url, + memberName: instanceName, + onRequestQRCodeInput, + }); + dispatch(setTrustchain(trustchain)); + onSyncFinished(); + return true; + } catch (e) { + if (e instanceof InvalidDigitsError) { + setCurrentStep(Steps.SyncError); + return; + } + throw e; + } + }, + [instanceName, onRequestQRCodeInput, onSyncFinished, dispatch], + ); + + const handleSendDigits = useCallback( + (inputCallback: (_: string) => void, input: string) => (inputCallback(input), true), + [], + ); + + return { digits, input, handleStart, handleSendDigits, setInput, inputCallback }; +}; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useTrustchainSdk.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useTrustchainSdk.ts index d395d019f7ac..5bbfa3f859c3 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useTrustchainSdk.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useTrustchainSdk.ts @@ -1,16 +1,11 @@ import { useMemo } from "react"; import { getEnv } from "@ledgerhq/live-env"; import { getSdk } from "@ledgerhq/trustchain/index"; -import { Platform } from "react-native"; import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; import { TrustchainSDK } from "@ledgerhq/trustchain/types"; import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; import getWalletSyncEnvironmentParams from "@ledgerhq/live-common/walletSync/getEnvironmentParams"; - -const platformMap: Record = { - ios: "iOS", - android: "Android", -}; +import { useInstanceName } from "./useInstanceName"; let sdkInstance: TrustchainSDK | null = null; @@ -19,14 +14,14 @@ export function useTrustchainSdk() { const environment = featureWalletSync?.params?.environment; const { trustchainApiBaseUrl } = getWalletSyncEnvironmentParams(environment); const isMockEnv = !!getEnv("MOCK"); + const instanceName = useInstanceName(); const defaultContext = useMemo(() => { const applicationId = 16; - const hash = getEnv("USER_ID").slice(0, 5); - const name = `${platformMap[Platform.OS] ?? Platform.OS} ${Platform.Version} ${hash ? " " + hash : ""}`; + const name = instanceName; return { applicationId, name, apiBaseUrl: trustchainApiBaseUrl }; - }, [trustchainApiBaseUrl]); + }, [trustchainApiBaseUrl, instanceName]); if (sdkInstance === null) { sdkInstance = getSdk(isMockEnv, defaultContext, withDevice); diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationDrawer.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationDrawer.tsx index 6bc0afbfc201..545e26869dbd 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationDrawer.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/ActivationDrawer.tsx @@ -29,6 +29,7 @@ function View({ qrProcess, currentOption, setCurrentOption, + setCurrentStep, }: ViewProps) { const CustomDrawerHeader = () => ; @@ -51,6 +52,7 @@ function View({ currentOption={currentOption} setOption={setCurrentOption} onQrCodeScanned={onQrCodeScanned} + setCurrentStep={setCurrentStep} /> diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/useActivationDrawerModel.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/useActivationDrawerModel.ts index e118245d5ed4..f5763a62b96e 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/useActivationDrawerModel.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Activation/useActivationDrawerModel.ts @@ -49,12 +49,7 @@ const useActivationDrawerModel = ({ isOpen, startingStep, handleClose }: Props) setCurrentStep(Steps.QrCodeMethod); }; - // That means the url as be stored in the store - const onQrCodeScanned = (data: string) => { - // eslint-disable-next-line no-console - console.log(data); - //setCurrentStep(Steps.PinCodeInput); - }; + const onQrCodeScanned = () => setCurrentStep(Steps.PinInput); const resetStep = () => setCurrentStep(startingStep); const resetOption = () => setCurrentOption(Options.SCAN); @@ -86,6 +81,7 @@ const useActivationDrawerModel = ({ isOpen, startingStep, handleClose }: Props) qrProcess: { url, error, isLoading, pinCode }, currentOption, setCurrentOption, + setCurrentStep, }; }; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/PinCodeInput.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/PinCodeInput.tsx index 7f5ad7f9facc..008695e20373 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/PinCodeInput.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Synchronize/PinCodeInput.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useImperativeHandle, useRef, useState } from "react"; +import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Flex, Text } from "@ledgerhq/native-ui"; import styled from "styled-components/native"; @@ -6,11 +6,21 @@ import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics"; import TrackScreen from "~/analytics/TrackScreen"; import { NativeSyntheticEvent, TextInput, TextInputKeyPressEventData } from "react-native"; -export default function PinCodeInput() { +type Props = { + handleSendDigits: (input: string) => void; +}; + +export default function PinCodeInput({ handleSendDigits }: Props) { const { t } = useTranslation(); const inputRefs = [useRef(null), useRef(null), useRef(null)]; const [digits, setDigits] = useState(["", "", ""]); + useEffect(() => { + if (digits.every(digit => digit)) { + handleSendDigits(digits.join("")); + } + }, [digits, handleSendDigits]); + const handleChange = (value: string, index: number) => { const newDigits = [...digits]; newDigits[index] = value;