Skip to content

Commit

Permalink
feat 💄(llm): update LNX drawer in Reborn
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasWerey committed Jan 15, 2025
1 parent 38f7d5d commit 27fce69
Show file tree
Hide file tree
Showing 14 changed files with 308 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/orange-chicken-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"live-mobile": minor
---

Create flex upsell drawer under ff to switch from the old LNX upsell drawer
Binary file not shown.
1 change: 1 addition & 0 deletions apps/ledger-live-mobile/assets/videos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export default {
infinityPassPart02Light: require("./infinityPassLight/infinityPassPart02.mp4"),
customLockScreenBannerLight: require("./customLockScreenBanner/customLockScreenBannerLight.mp4"),
customLockScreenBannerDark: require("./customLockScreenBanner/customLockScreenBannerDark.mp4"),
flex: require("./flex/BasicFlex.mp4"),
};
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,6 @@ export default function BaseNavigator() {
options={{
headerStyle: styles.headerNoShadow,
}}
{...noNanoBuyNanoWallScreenOptions}
/>
<Stack.Screen
name={ScreenName.Recover}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import React, { useMemo } from "react";
import { createStackNavigator } from "@react-navigation/stack";
import { useTheme } from "styled-components/native";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";

import { ScreenName } from "~/const";
import { getStackNavigatorConfig } from "~/navigation/navigatorConfig";
import GetDevice from "~/screens/GetDeviceScreen";
import GetFlex from "LLM/features/Reborn/screens/UpsellFlex";
import PurchaseDevice from "~/screens/PurchaseDevice";
import { BuyDeviceNavigatorParamList } from "./types/BuyDeviceNavigator";

Expand All @@ -14,11 +14,15 @@ const Stack = createStackNavigator<BuyDeviceNavigatorParamList>();
const BuyDeviceNavigator = () => {
const { colors } = useTheme();
const buyDeviceFromLive = useFeature("buyDeviceFromLive");
const upsellFlexFF = useFeature("llmRebornFlex");
const stackNavigationConfig = useMemo(() => getStackNavigatorConfig(colors, true), [colors]);

return (
<Stack.Navigator screenOptions={{ ...stackNavigationConfig, headerShown: false }}>
<Stack.Screen name={ScreenName.GetDevice} component={GetDevice} />
<Stack.Screen
name={ScreenName.GetDevice}
component={upsellFlexFF?.enabled ? GetFlex : GetDevice}
/>
{buyDeviceFromLive?.enabled && (
<Stack.Screen name={ScreenName.PurchaseDevice} component={PurchaseDevice} />
)}
Expand Down
2 changes: 1 addition & 1 deletion apps/ledger-live-mobile/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -1303,7 +1303,7 @@
"desc": "Our products are the only hardware wallets certified for their security by national cyber security agencies."
},
"title": "You need a Ledger",
"desc": "For your security, Ledger Live only works with a device. You need a device in order to continue.",
"desc": "For your security,\n Ledger Live only works with a Ledger.",
"cta": "Buy your Ledger now",
"footer": "I already have a Ledger, set it up",
"bannerTitle": "Top-notch security for your crypto and NFTs",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import React from "react";
import useUpsellFlexModel from "./useUpsellFlexModel";
import {
Box,
Button,
Flex,
IconBoxList,
Icons,
ScrollListContainer,
Text,
} from "@ledgerhq/native-ui";
import { TouchableOpacity } from "react-native";
import styled from "styled-components/native";
import videoSources from "../../../../../../assets/videos";
import Video from "react-native-video";
import GradientContainer from "~/components/GradientContainer";
import { TrackScreen } from "~/analytics";

const videoSource = videoSources.flex;

const hitSlop = {
bottom: 10,
left: 24,
right: 24,
top: 10,
};

const StyledSafeAreaView = styled(Box)`
flex: 1;
background-color: ${p => p.theme.colors.background.default};
padding-top: ${p => p.theme.space[10]}px;
`;

const CloseButton = styled(TouchableOpacity)`
background-color: ${p => p.theme.colors.neutral.c30};
padding: 8px;
border-radius: 32px;
`;

const items = [
{
title: "buyDevice.0.title",
desc: "buyDevice.0.desc",
Icon: Icons.Coins,
},
{
title: "buyDevice.1.title",
desc: "buyDevice.1.desc",
Icon: Icons.GraphAsc,
},
{
title: "buyDevice.2.title",
desc: "buyDevice.2.desc",
Icon: Icons.Globe,
},
{
title: "buyDevice.3.title",
desc: "buyDevice.3.desc",
Icon: Icons.Flex,
},
];

const videoStyle = {
height: "100%",
};

type ViewProps = ReturnType<typeof useUpsellFlexModel>;

function View({
t,
handleBack,
setupDevice,
buyLedger,
colors,
readOnlyModeEnabled,
videoMounted,
}: ViewProps) {
return (
<StyledSafeAreaView>
{readOnlyModeEnabled ? <TrackScreen category="ReadOnly" name="Upsell Flex" /> : null}
<Flex
flexDirection="row"
alignItems="center"
justifyContent="flex-end"
width="100%"
position="absolute"
zIndex={10}
p={6}
top={50}
>
<CloseButton onPress={handleBack} hitSlop={hitSlop}>
<Icons.Close size="S" />
</CloseButton>
</Flex>
<ScrollListContainer>
<Flex
height={320}
borderTopLeftRadius={32}
borderTopRightRadius={32}
width="100%"
overflow="hidden"
opacity={videoMounted ? 0.8 : 0}
>
{videoMounted && (
<Video
disableFocus
source={videoSource}
style={{
backgroundColor: colors.background.main,
transform: [{ scale: 1.4 }],
...(videoStyle as object),
}}
muted
repeat={true}
/>
)}
<GradientContainer
color={colors.background.drawer}
startOpacity={0}
endOpacity={1}
direction="top-to-bottom"
containerStyle={{
position: "absolute",
borderRadius: 0,
left: 0,
bottom: 0,
width: "100%",
height: "30%",
}}
/>
</Flex>
<Flex p={6}>
<Text variant="h4" textAlign="center" lineHeight="32.4px">
{t("buyDevice.title")}
</Text>
<Flex mt={6} mb={8} justifyContent="center" alignItems="stretch">
<Text px={6} textAlign="center" variant="body" color="neutral.c70">
{t("buyDevice.desc")}
</Text>
</Flex>
<IconBoxList
iconShapes="circle"
itemContainerProps={{ pr: 6 }}
items={items.map(item => ({
Icon: <item.Icon size="S" />,
title: t(item.title),
description: (
<Text variant="paragraphLineHeight" color="neutral.c70">
{t(item.desc)}
</Text>
),
}))}
/>
</Flex>
</ScrollListContainer>
<Flex>
<Button
mx={6}
my={6}
type="main"
outline={false}
testID="getDevice-buy-button"
onPress={buyLedger}
size="large"
>
{t("buyDevice.cta")}
</Button>
<Flex px={6} pt={0} mb={8}>
<Button
type="default"
border={1}
borderColor="neutral.c50"
onPress={setupDevice}
size="large"
>
{t("buyDevice.footer")}
</Button>
</Flex>
</Flex>
</StyledSafeAreaView>
);
}

const UpsellFlex = () => <View {...useUpsellFlexModel()} />;

export default UpsellFlex;
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
import { useNavigation } from "@react-navigation/native";
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { Linking } from "react-native";
import { useSelector, useDispatch } from "react-redux";
import { useTheme } from "styled-components/native";
import { setOnboardingHasDevice } from "~/actions/settings";
import { track } from "~/analytics";
import { BuyDeviceNavigatorParamList } from "~/components/RootNavigator/types/BuyDeviceNavigator";
import {
BaseNavigationComposite,
StackNavigatorNavigation,
} from "~/components/RootNavigator/types/helpers";
import { OnboardingNavigatorParamList } from "~/components/RootNavigator/types/OnboardingNavigator";
import useIsAppInBackground from "~/components/useIsAppInBackground";
import { ScreenName, NavigatorName } from "~/const";
import { readOnlyModeEnabledSelector } from "~/reducers/settings";
import { useNavigationInterceptor } from "~/screens/Onboarding/onboardingContext";
import { urls } from "~/utils/urls";

type NavigationProp = BaseNavigationComposite<
| StackNavigatorNavigation<BuyDeviceNavigatorParamList, ScreenName.GetDevice>
| StackNavigatorNavigation<OnboardingNavigatorParamList, ScreenName.GetDevice>
>;

const useUpsellFlexModel = () => {
const { t } = useTranslation();
const navigation = useNavigation<NavigationProp>();
const { colors } = useTheme();
const { setShowWelcome, setFirstTimeOnboarding } = useNavigationInterceptor();
const buyDeviceFromLive = useFeature("buyDeviceFromLive");
const readOnlyModeEnabled = useSelector(readOnlyModeEnabledSelector);
const dispatch = useDispatch();
const currentNavigation = navigation.getParent()?.getParent()?.getState().routes[0].name;
const isInOnboarding = currentNavigation === NavigatorName.BaseOnboarding;

const handleBack = useCallback(() => {
navigation.goBack();
if (readOnlyModeEnabled) {
track("button_clicked", {
button: "close",
page: "Upsell Flex",
});
}
}, [readOnlyModeEnabled, navigation]);

const setupDevice = useCallback(() => {
setShowWelcome(false);
setFirstTimeOnboarding(false);
if (isInOnboarding) dispatch(setOnboardingHasDevice(true));
navigation.navigate(NavigatorName.BaseOnboarding, {
screen: NavigatorName.Onboarding,
params: {
screen: ScreenName.OnboardingDeviceSelection,
},
});
if (readOnlyModeEnabled) {
track("message_clicked", {
message: "I already have a device, set it up now",
page: "Upsell Flex",
});
}
}, [
setShowWelcome,
setFirstTimeOnboarding,
isInOnboarding,
dispatch,
navigation,
readOnlyModeEnabled,
]);

const buyLedger = useCallback(() => {
if (buyDeviceFromLive?.enabled) {
// FIXME: ScreenName.PurchaseDevice does not exist when coming from the Onboarding navigator
// @ts-expect-error This seem very impossible to type because ts is right…
navigation.navigate(ScreenName.PurchaseDevice);
} else {
Linking.openURL(urls.buyFlex);
}
}, [buyDeviceFromLive?.enabled, navigation]);

const videoMounted = !useIsAppInBackground();

return {
t,
handleBack,
setupDevice,
buyLedger,
colors,
readOnlyModeEnabled,
videoMounted,
};
};

export default useUpsellFlexModel;
5 changes: 4 additions & 1 deletion apps/ledger-live-mobile/src/screens/Platform/v2/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
DISCOVER_STORE_KEY,
BROWSE_SEARCH_OPTIONS,
WC_ID,
LEDGER_SHOP_ID,
} from "@ledgerhq/live-common/wallet-api/constants";
import { DiscoverDB, AppManifest } from "@ledgerhq/live-common/wallet-api/types";
import { useNavigation, useRoute } from "@react-navigation/native";
Expand Down Expand Up @@ -128,7 +129,9 @@ function useDisclaimer(appendRecentlyUsed: (manifest: AppManifest) => void): Dis
return;
}

if (isReadOnly && !hasOrderedNano) {
const isLedgerShopApp = manifest.id === LEDGER_SHOP_ID;

if (isReadOnly && !hasOrderedNano && !isLedgerShopApp) {
navigateToRebornFlow();
return;
}
Expand Down
5 changes: 3 additions & 2 deletions apps/ledger-live-mobile/src/screens/PurchaseDevice/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import WebViewScreen from "~/components/WebViewScreen";
import { completeOnboarding, setReadOnlyMode } from "~/actions/settings";
import { urls } from "~/utils/urls";

const defaultURL = urls.buyNanoX;

const PurchaseDevice = () => {
const { t } = useTranslation();
const navigation = useNavigation();
const dispatch = useDispatch();
const buyDeviceFromLive = useFeature("buyDeviceFromLive");
const upsellFlexFF = useFeature("llmRebornFlex");

const defaultURL = upsellFlexFF?.enabled ? urls.buyFlex : urls.buyNanoX;

const [isURLDrawerOpen, setURLDrawerOpen] = useState(false);
const [isMessageDrawerOpen, setMessageDrawerOpen] = useState(false);
Expand Down
2 changes: 2 additions & 0 deletions apps/ledger-live-mobile/src/utils/urls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export const urls = {
ratingsContact: "https://support.ledger.com/article/4423020306705-zd",
buyNanoX:
"https://shop.ledger.com/products/ledger-nano-x?utm_source=ledger_live_mobile&utm_medium=self_referral&utm_content=onboarding",
buyFlex:
"https://shop.ledger.com/products/ledger-flex?utm_source=ledger_live_mobile&utm_medium=self_referral&utm_content=onboarding",
playstore: "https://play.google.com/store/apps/details?id=com.ledger.live",
applestoreRate: "https://apps.apple.com/app/id1361671700?action=write-review",
applestore:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ export const DEFAULT_FEATURES: Features = {
},
},
llmRebornLP: { ...DEFAULT_FEATURE, params: { variant: ABTestingVariants.variantA } },
llmRebornFlex: DEFAULT_FEATURE,
llmAccountListUI: DEFAULT_FEATURE,
};

Expand Down
Loading

0 comments on commit 27fce69

Please sign in to comment.