diff --git a/.changeset/orange-chicken-cross.md b/.changeset/orange-chicken-cross.md new file mode 100644 index 000000000000..35f00e0c3856 --- /dev/null +++ b/.changeset/orange-chicken-cross.md @@ -0,0 +1,5 @@ +--- +"live-mobile": minor +--- + +Create flex upsell drawer under ff to switch from the old LNX upsell drawer diff --git a/apps/ledger-live-mobile/assets/videos/flex.mp4 b/apps/ledger-live-mobile/assets/videos/flex.mp4 new file mode 100644 index 000000000000..69aab14b4960 Binary files /dev/null and b/apps/ledger-live-mobile/assets/videos/flex.mp4 differ diff --git a/apps/ledger-live-mobile/assets/videos/index.ts b/apps/ledger-live-mobile/assets/videos/index.ts index de9012808394..cc58bd3412e1 100644 --- a/apps/ledger-live-mobile/assets/videos/index.ts +++ b/apps/ledger-live-mobile/assets/videos/index.ts @@ -10,4 +10,5 @@ export default { infinityPassPart02Light: require("./infinityPassLight/infinityPassPart02.mp4"), customLockScreenBannerLight: require("./customLockScreenBanner/customLockScreenBannerLight.mp4"), customLockScreenBannerDark: require("./customLockScreenBanner/customLockScreenBannerDark.mp4"), + flex: require("./flex.mp4"), }; diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/BaseNavigator.tsx b/apps/ledger-live-mobile/src/components/RootNavigator/BaseNavigator.tsx index dfec029aab31..f170b16cd882 100644 --- a/apps/ledger-live-mobile/src/components/RootNavigator/BaseNavigator.tsx +++ b/apps/ledger-live-mobile/src/components/RootNavigator/BaseNavigator.tsx @@ -209,7 +209,6 @@ export default function BaseNavigator() { options={{ headerStyle: styles.headerNoShadow, }} - {...noNanoBuyNanoWallScreenOptions} /> (); const BuyDeviceNavigator = () => { const { colors } = useTheme(); const buyDeviceFromLive = useFeature("buyDeviceFromLive"); + const upsellFlexFF = useFeature("llmRebornFlex"); const stackNavigationConfig = useMemo(() => getStackNavigatorConfig(colors, true), [colors]); return ( - + {buyDeviceFromLive?.enabled && ( )} diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index 49b7e3e03f97..8e97bb691a6f 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -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", diff --git a/apps/ledger-live-mobile/src/newArch/features/Reborn/screens/UpsellFlex/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Reborn/screens/UpsellFlex/index.tsx new file mode 100644 index 000000000000..98ad2e00d8de --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/Reborn/screens/UpsellFlex/index.tsx @@ -0,0 +1,191 @@ +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; + +function View({ + t, + handleBack, + setupDevice, + buyLedger, + colors, + readOnlyModeEnabled, + hasCompletedOnboarding, + videoMounted, +}: ViewProps) { + return ( + + {readOnlyModeEnabled ? : null} + + {hasCompletedOnboarding ? ( + + + + ) : ( + + )} + + + + {videoMounted && ( + + + + {t("buyDevice.title")} + + + + {t("buyDevice.desc")} + + + ({ + Icon: , + title: t(item.title), + description: ( + + {t(item.desc)} + + ), + }))} + /> + + + + + + + + + + ); +} + +const UpsellFlex = () => ; + +export default UpsellFlex; diff --git a/apps/ledger-live-mobile/src/newArch/features/Reborn/screens/UpsellFlex/useUpsellFlexModel.ts b/apps/ledger-live-mobile/src/newArch/features/Reborn/screens/UpsellFlex/useUpsellFlexModel.ts new file mode 100644 index 000000000000..06a75f7589a2 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/Reborn/screens/UpsellFlex/useUpsellFlexModel.ts @@ -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 { hasCompletedOnboardingSelector, readOnlyModeEnabledSelector } from "~/reducers/settings"; +import { useNavigationInterceptor } from "~/screens/Onboarding/onboardingContext"; +import { urls } from "~/utils/urls"; + +type NavigationProp = BaseNavigationComposite< + | StackNavigatorNavigation + | StackNavigatorNavigation +>; + +const useUpsellFlexModel = () => { + const { t } = useTranslation(); + const navigation = useNavigation(); + const { colors } = useTheme(); + const { setShowWelcome, setFirstTimeOnboarding } = useNavigationInterceptor(); + const buyDeviceFromLive = useFeature("buyDeviceFromLive"); + const hasCompletedOnboarding = useSelector(hasCompletedOnboardingSelector); + 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) { + navigation.navigate(ScreenName.PurchaseDevice as never); + } else { + Linking.openURL(urls.buyFlex); + } + }, [buyDeviceFromLive?.enabled, navigation]); + + const videoMounted = !useIsAppInBackground(); + + return { + t, + handleBack, + setupDevice, + buyLedger, + colors, + readOnlyModeEnabled, + hasCompletedOnboarding, + videoMounted, + }; +}; + +export default useUpsellFlexModel; diff --git a/apps/ledger-live-mobile/src/screens/Platform/v2/hooks.ts b/apps/ledger-live-mobile/src/screens/Platform/v2/hooks.ts index 903934f1c686..4b4bffe6cf64 100644 --- a/apps/ledger-live-mobile/src/screens/Platform/v2/hooks.ts +++ b/apps/ledger-live-mobile/src/screens/Platform/v2/hooks.ts @@ -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"; @@ -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; } diff --git a/apps/ledger-live-mobile/src/screens/PurchaseDevice/index.tsx b/apps/ledger-live-mobile/src/screens/PurchaseDevice/index.tsx index af5c0b99273e..8e7d99584066 100644 --- a/apps/ledger-live-mobile/src/screens/PurchaseDevice/index.tsx +++ b/apps/ledger-live-mobile/src/screens/PurchaseDevice/index.tsx @@ -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); diff --git a/apps/ledger-live-mobile/src/utils/urls.tsx b/apps/ledger-live-mobile/src/utils/urls.tsx index 0fd754e885e0..a3a6777ccbba 100644 --- a/apps/ledger-live-mobile/src/utils/urls.tsx +++ b/apps/ledger-live-mobile/src/utils/urls.tsx @@ -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: diff --git a/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts b/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts index 1ccfe0d48a4a..680123f5828c 100644 --- a/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts +++ b/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts @@ -506,6 +506,7 @@ export const DEFAULT_FEATURES: Features = { }, }, llmRebornLP: { ...DEFAULT_FEATURE, params: { variant: ABTestingVariants.variantA } }, + llmRebornFlex: DEFAULT_FEATURE, llmAccountListUI: DEFAULT_FEATURE, }; diff --git a/libs/ledger-live-common/src/wallet-api/constants.ts b/libs/ledger-live-common/src/wallet-api/constants.ts index 915d5a04ed33..e7f54510f263 100644 --- a/libs/ledger-live-common/src/wallet-api/constants.ts +++ b/libs/ledger-live-common/src/wallet-api/constants.ts @@ -52,4 +52,6 @@ export const CARD_APP_ID = "card-program"; export const WC_ID = "ledger-wallet-connect"; +export const LEDGER_SHOP_ID = "ledger-shop"; + export const INTERNAL_APP_IDS = [DEFAULT_MULTIBUY_APP_ID, BUY_SELL_UI_APP_ID, CARD_APP_ID]; diff --git a/libs/ledgerjs/packages/types-live/src/feature.ts b/libs/ledgerjs/packages/types-live/src/feature.ts index 8f750b10130f..1b818477f826 100644 --- a/libs/ledgerjs/packages/types-live/src/feature.ts +++ b/libs/ledgerjs/packages/types-live/src/feature.ts @@ -201,6 +201,7 @@ export type Features = CurrencyFeatures & { llmNetworkBasedAddAccountFlow: DefaultFeature; llCounterValueGranularitiesRates: Feature_LlCounterValueGranularitiesRates; llmRebornLP: Feature_LlmRebornLP; + llmRebornFlex: DefaultFeature; llmAccountListUI: DefaultFeature; };