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

Add install set of apps step in mobile sync onboarding #1667

Merged
merged 4 commits into from
Nov 3, 2022
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
5 changes: 5 additions & 0 deletions .changeset/tricky-mugs-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"live-mobile": minor
---

Add install set of apps step in sync onboarding
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useCallback, useState, useMemo } from "react";
import { Trans } from "react-i18next";
import { createAction } from "@ledgerhq/live-common/hw/actions/app";
import type { Device } from "@ledgerhq/live-common/hw/actions/types";
import withRemountableWrapper from "@ledgerhq/live-common/hoc/withRemountableWrapper";
import connectApp from "@ledgerhq/live-common/hw/connectApp";
import { Flex, Text } from "@ledgerhq/native-ui";
import { getDeviceModel } from "@ledgerhq/devices";
Expand All @@ -16,7 +17,7 @@ type Props = {
dependencies?: string[];
device: Device;
onResult: (done: boolean) => void;
onError: (error: Error) => void;
onError?: (error: Error) => void;
};

const action = createAction(connectApp);
Expand All @@ -33,7 +34,8 @@ const InstallSetOfApps = ({
device: selectedDevice,
onResult,
onError,
}: Props) => {
remountMe,
}: Props & { remountMe: () => void }) => {
const [userConfirmed, setUserConfirmed] = useState(false);
const productName = getDeviceModel(selectedDevice.modelId).productName;

Expand Down Expand Up @@ -63,10 +65,15 @@ const InstallSetOfApps = ({
} = status;

const onWrappedError = useCallback(() => {
if (onError && error) {
onError(error);
if (error) {
if (onError) {
onError(error);
}
// We force the component to remount for action.useHook to re-run from
// scratch and reset the status value
remountMe();
}
}, [error, onError]);
}, [remountMe, error, onError]);

if (opened) {
onResult(true);
Expand Down Expand Up @@ -134,4 +141,4 @@ const InstallSetOfApps = ({
);
};

export default InstallSetOfApps;
export default withRemountableWrapper(InstallSetOfApps);
3 changes: 3 additions & 0 deletions apps/ledger-live-mobile/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -1807,6 +1807,9 @@
}
}
},
"appsStep": {
"title": "{{productName}} applications"
},
"readyStep": {
"title": "{{productName}} is ready"
},
Expand Down
61 changes: 55 additions & 6 deletions apps/ledger-live-mobile/src/screens/SyncOnboarding/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useTranslation } from "react-i18next";
import { getDeviceModel } from "@ledgerhq/devices";
import { useDispatch } from "react-redux";
import { CompositeScreenProps } from "@react-navigation/native";
import useFeature from "@ledgerhq/live-common/featureFlags/useFeature";

import { addKnownDevice } from "../../actions/ble";
import { NavigatorName, ScreenName } from "../../const";
Expand All @@ -43,6 +44,7 @@ import {
} from "../../components/RootNavigator/types/BaseNavigator";
import { RootStackParamList } from "../../components/RootNavigator/types/RootNavigator";
import { SyncOnboardingStackParamList } from "../../components/RootNavigator/types/SyncOnboardingNavigator";
import InstallSetOfApps from "../../components/DeviceAction/InstallSetOfApps";

type StepStatus = "completed" | "active" | "inactive";

Expand Down Expand Up @@ -73,12 +75,15 @@ const normalResyncOverlayDisplayDelayMs = 10000;
const longResyncOverlayDisplayDelayMs = 60000;
const readyRedirectDelayMs = 2500;

const fallbackDefaultAppsToInstall = ["Bitcoin", "Ethereum", "Polygon"];

// Because of https://github.com/typescript-eslint/typescript-eslint/issues/1197
enum CompanionStepKey {
Paired = 0,
Pin,
Seed,
SoftwareCheck,
Apps,
Ready,
Exit,
}
Expand All @@ -96,21 +101,48 @@ export const SyncOnboarding = ({
}: SyncOnboardingCompanionProps) => {
const { t } = useTranslation();
const dispatchRedux = useDispatch();
const deviceInitialApps = useFeature("deviceInitialApps");
const { device } = route.params;

const productName =
getDeviceModel(device.modelId).productName || device.modelId;
const deviceName = device.deviceName || productName;

const initialAppsToInstall =
deviceInitialApps?.params?.apps || fallbackDefaultAppsToInstall;

const handleSoftwareCheckComplete = useCallback(() => {
setCompanionStepKey(nextStepKey(CompanionStepKey.SoftwareCheck));
}, []);

const handleInstallAppsComplete = useCallback(() => {
setCompanionStepKey(nextStepKey(CompanionStepKey.Apps));
}, []);

const formatEstimatedTime = (estimatedTime: number) =>
t("syncOnboarding.estimatedTimeFormat", {
estimatedTime: estimatedTime / 60,
});

const installSetOfAppsSteps: Step[] = useMemo(
() => [
{
key: CompanionStepKey.Apps,
title: t("syncOnboarding.appsStep.title", { productName }),
status: "inactive",
estimatedTime: 60,
renderBody: () => (
<InstallSetOfApps
device={device}
onResult={handleInstallAppsComplete}
dependencies={initialAppsToInstall}
/>
),
},
],
[productName, t, device, handleInstallAppsComplete, initialAppsToInstall],
);

const defaultCompanionSteps: Step[] = useMemo(
() => [
{
Expand Down Expand Up @@ -162,14 +194,31 @@ export const SyncOnboarding = ({
/>
),
},
],
[t, productName, device, handleSoftwareCheckComplete],
);

const getCompanionSteps = useCallback(() => {
let steps = defaultCompanionSteps;

if (deviceInitialApps?.enabled) {
steps = steps.concat(installSetOfAppsSteps);
}

return steps.concat([
{
key: CompanionStepKey.Ready,
title: t("syncOnboarding.readyStep.title", { productName }),
status: "inactive",
},
],
[t, productName, device, handleSoftwareCheckComplete],
);
]);
}, [
t,
productName,
defaultCompanionSteps,
installSetOfAppsSteps,
deviceInitialApps?.enabled,
]);

const [stopPolling, setStopPolling] = useState<boolean>(false);
const [pollingPeriodMs, setPollingPeriodMs] = useState<number>(
Expand All @@ -193,7 +242,7 @@ export const SyncOnboarding = ({
const [isHelpDrawerOpen, setHelpDrawerOpen] = useState<boolean>(false);

const [companionSteps, setCompanionSteps] = useState<Step[]>(
defaultCompanionSteps,
getCompanionSteps(),
);
const [companionStepKey, setCompanionStepKey] = useState<CompanionStepKey>(
CompanionStepKey.Paired,
Expand Down Expand Up @@ -398,7 +447,7 @@ export const SyncOnboarding = ({
}

setCompanionSteps(
defaultCompanionSteps.map(step => {
getCompanionSteps().map(step => {
const stepStatus =
step.key > companionStepKey
? "inactive"
Expand All @@ -419,7 +468,7 @@ export const SyncOnboarding = ({
readyRedirectTimerRef.current = null;
}
};
}, [companionStepKey, defaultCompanionSteps, handleDeviceReady]);
}, [companionStepKey, getCompanionSteps, handleDeviceReady]);

return (
<DeviceSetupView
Expand Down