Repository: LedgerHQ/ledger-live
Pull request #10183: feat: Large Mover swipe card
Author: login: RobinVncnt
Labels: name: mobile
description: Has changes in LLM
Created: 2025-05-12T14:30:30Z | Merged: 2025-05-19T07:30:01Z

<!--
Thank you for your contribution! 👍
Please make sure to read CONTRIBUTING.md if you have not already. Pull Requests that do not comply with the rules will be arbitrarily closed.
-->

### ✅ Checklist

<!-- Pull Requests must pass the CI and be code reviewed. Set as Draft if the PR is not ready. -->

- [x] `npx changeset` was attached.
- [ ] **Covered by automatic tests.** <!-- if not, please explain. (Feature must be tested / Bug fix must bring non-regression) -->
- [ ] **Impact of the changes:** <!-- Please take some time to list the impact & what specific areas Quality Assurance (QA) should focus on -->
  - ...

### 📝 Description

feat: Large Mover swipe card

### ❓ Context

- **JIRA or GitHub link**: [LIVE-18310]<!-- Attach the relevant ticket number if applicable. (e.g., [JIRA-123] for Jira or #123 for a Github issue) -->


---

https://github.com/user-attachments/assets/13cf1909-5aff-4e3a-a9de-d83ec1901631



### 🧐 Checklist for the PR Reviewers

<!-- Please do not edit this if you are the PR author -->

- **The code aligns with the requirements** described in the linked JIRA or GitHub issue.
- **The PR description clearly documents the changes** made and explains any technical trade-offs or design decisions.
- **There are no undocumented trade-offs**, technical debt, or maintainability issues.
- **The PR has been tested** thoroughly, and any potential edge cases have been considered and handled.
- **Any new dependencies** have been justified and documented.
- **Performance** considerations have been taken into account. (changes have been profiled or benchmarked if necessary)


[LIVE-18310]: https://ledgerhq.atlassian.net/browse/LIVE-18310?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ


Diff:
diff --git a/.changeset/happy-peaches-fetch.md b/.changeset/happy-peaches-fetch.md
new file mode 100644
index 00000000000..842e0329ad2
--- /dev/null
+++ b/.changeset/happy-peaches-fetch.md
@@ -0,0 +1,6 @@
+---
+"live-mobile": minor
+"@ledgerhq/live-common": minor
+---
+
+feat: Large Mover swipe card
diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/types/BaseNavigator.ts b/apps/ledger-live-mobile/src/components/RootNavigator/types/BaseNavigator.ts
index 70dd38a1a78..c1772cd6a38 100644
--- a/apps/ledger-live-mobile/src/components/RootNavigator/types/BaseNavigator.ts
+++ b/apps/ledger-live-mobile/src/components/RootNavigator/types/BaseNavigator.ts
@@ -80,6 +80,7 @@ import type { SwapNavigatorParamList } from "./SwapNavigator";
 import type { UnfreezeNavigatorParamList } from "./UnfreezeNavigator";
 import type { WalletConnectLiveAppNavigatorParamList } from "./WalletConnectLiveAppNavigator";
 import type { WalletSyncNavigatorStackParamList } from "./WalletSyncNavigator";
+import { WalletTabNavigatorStackParamList } from "./WalletTabNavigator";
 
 export type NavigateInput<
   ParamList extends ParamListBase = ParamListBase,
@@ -324,6 +325,7 @@ export type BaseNavigatorStackParamList = {
     isBeforeOnboarding?: boolean;
   };
   [NavigatorName.LandingPages]: NavigatorScreenParams<LandingPagesNavigatorParamList>;
+  [NavigatorName.WalletTab]: NavigatorScreenParams<WalletTabNavigatorStackParamList>;
   [NavigatorName.DeviceSelection]?: Partial<
     NavigatorScreenParams<DeviceSelectionNavigatorParamsList>
   >;
diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/types/LandingPagesNavigator.ts b/apps/ledger-live-mobile/src/components/RootNavigator/types/LandingPagesNavigator.ts
index de9b2627823..4ad632d788d 100644
--- a/apps/ledger-live-mobile/src/components/RootNavigator/types/LandingPagesNavigator.ts
+++ b/apps/ledger-live-mobile/src/components/RootNavigator/types/LandingPagesNavigator.ts
@@ -14,4 +14,5 @@ export type LandingPagesNavigatorParamList = {
     currencyIds: string;
     initialRange?: InitialRange;
   };
+  [ScreenName.MarketList]: undefined;
 };
diff --git a/apps/ledger-live-mobile/src/newArch/components/Swiper/components/Swiper.tsx b/apps/ledger-live-mobile/src/newArch/components/Swiper/components/Swiper.tsx
index 51d8032c601..ebb0924a491 100644
--- a/apps/ledger-live-mobile/src/newArch/components/Swiper/components/Swiper.tsx
+++ b/apps/ledger-live-mobile/src/newArch/components/Swiper/components/Swiper.tsx
@@ -12,15 +12,17 @@ export function SwiperComponent<T extends CardWithId>({
   renderCard,
   cardContainerStyle,
   containerStyle,
+  currentIndex,
+  onIndexChange,
 }: SwiperComponentProps<T>) {
-  const { cards, gesture, swipeX, swipeY } = useSwiper(initialCards);
+  const { cards, gesture, swipeX, swipeY } = useSwiper(initialCards, currentIndex, onIndexChange);
 
   return (
     <GestureDetector gesture={gesture}>
       <View style={[styles.container, containerStyle]}>
         <Animated.View style={[styles.cardContainer, cardContainerStyle]}>
           {cards.map((card, index) => (
-            <SwipeableCard key={card.id} index={index} swipeX={swipeX} swipeY={swipeY}>
+            <SwipeableCard key={card.idCard} index={index} swipeX={swipeX} swipeY={swipeY}>
               {renderCard(card)}
             </SwipeableCard>
           ))}
diff --git a/apps/ledger-live-mobile/src/newArch/components/Swiper/hooks/useCardRotation.ts b/apps/ledger-live-mobile/src/newArch/components/Swiper/hooks/useCardRotation.ts
index 7daff28b37a..61846ad0e5d 100644
--- a/apps/ledger-live-mobile/src/newArch/components/Swiper/hooks/useCardRotation.ts
+++ b/apps/ledger-live-mobile/src/newArch/components/Swiper/hooks/useCardRotation.ts
@@ -1,9 +1,21 @@
 import { useMemo } from "react";
 
+const MAX_VISIBLE_CARDS = 3;
+
 export function useCardRotation<T>(cardIndex: number, cards: T[]) {
   return useMemo(() => {
-    const rotatedCards = [...cards];
-    const firstCards = rotatedCards.splice(0, cardIndex);
-    return [...rotatedCards, ...firstCards];
+    const visibleCards = [];
+    const totalCards = cards.length;
+
+    // 3 first cards to see the front animation and still smooth
+    for (let i = 0; i < MAX_VISIBLE_CARDS; i++) {
+      const index = (cardIndex + i) % totalCards;
+      visibleCards.push(cards[index]);
+    }
+    // Last card to see the back animation
+    const lastIndex = (cardIndex - 1 + totalCards) % totalCards;
+    visibleCards.push(cards[lastIndex]);
+
+    return visibleCards;
   }, [cardIndex, cards]);
 }
diff --git a/apps/ledger-live-mobile/src/newArch/components/Swiper/hooks/useSwiper.ts b/apps/ledger-live-mobile/src/newArch/components/Swiper/hooks/useSwiper.ts
index 40a604c4dec..2199d4ef91c 100644
--- a/apps/ledger-live-mobile/src/newArch/components/Swiper/hooks/useSwiper.ts
+++ b/apps/ledger-live-mobile/src/newArch/components/Swiper/hooks/useSwiper.ts
@@ -3,20 +3,26 @@ import { useSharedValue } from "react-native-reanimated";
 import { useCardRotation } from "./useCardRotation";
 import { createGesture } from "../utils/gesture";
 
-export function useSwiper<T>(initialCards: T[]) {
-  const [cardIndex, setCardIndex] = useState(0);
-  const activeIndex = useSharedValue(0);
+export function useSwiper<T>(
+  initialCards: T[],
+  currentIndex: number,
+  onIndexChange: (index: number) => void,
+) {
+  const [cardIndex, setCardIndex] = useState(currentIndex);
+  const activeIndex = useSharedValue(currentIndex);
   const swipeX = useSharedValue(0);
   const swipeY = useSharedValue(0);
 
   const cards = useCardRotation(cardIndex, initialCards);
 
   const handleSwipeComplete = useCallback(() => {
-    setCardIndex((prev: number) => (prev + 1) % initialCards.length);
-    activeIndex.value = (activeIndex.value + 1) % initialCards.length;
+    const nextIndex = (cardIndex + 1) % initialCards.length;
+    setCardIndex(nextIndex);
+    activeIndex.value = nextIndex;
     swipeX.value = 0;
     swipeY.value = 0;
-  }, [setCardIndex, activeIndex, initialCards.length, swipeX, swipeY]);
+    onIndexChange(nextIndex);
+  }, [cardIndex, initialCards.length, onIndexChange, activeIndex, swipeX, swipeY]);
 
   const gesture = createGesture(swipeX, swipeY, handleSwipeComplete);
 
diff --git a/apps/ledger-live-mobile/src/newArch/components/Swiper/types.ts b/apps/ledger-live-mobile/src/newArch/components/Swiper/types.ts
index 1bf93f1db8c..ae6763623d3 100644
--- a/apps/ledger-live-mobile/src/newArch/components/Swiper/types.ts
+++ b/apps/ledger-live-mobile/src/newArch/components/Swiper/types.ts
@@ -1,7 +1,7 @@
 import { StyleProp, ViewStyle } from "react-native";
 
 type CardWithId = {
-  id: number;
+  idCard: number;
 };
 
 type SwiperComponentProps<T> = {
@@ -9,6 +9,8 @@ type SwiperComponentProps<T> = {
   renderCard: (card: T) => React.ReactNode;
   containerStyle?: StyleProp<ViewStyle>;
   cardContainerStyle?: StyleProp<ViewStyle>;
+  currentIndex: number;
+  onIndexChange: (index: number) => void;
 };
 
 type SwipeValues = { value: number };
diff --git a/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/components/Card.tsx b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/components/Card.tsx
index 4f0a8660b44..9b8969dae20 100644
--- a/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/components/Card.tsx
+++ b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/components/Card.tsx
@@ -8,40 +8,46 @@ import { TimeFrame } from "./TimeFrame";
 import { Performance } from "./Performance";
 import { PriceAndVariation } from "./PriceAndVariation";
 import { Footer } from "./Footer";
-import { CurrencyData, KeysPriceChange } from "@ledgerhq/live-common/market/utils/types";
+import {
+  CurrencyData,
+  KeysPriceChange,
+  MarketCoinDataChart,
+} from "@ledgerhq/live-common/market/utils/types";
 import getWindowDimensions from "~/logic/getWindowDimensions";
 import { Informations } from "./Information";
 import { useTheme } from "styled-components/native";
 import { getCryptoCurrencyById, getCurrencyColor } from "@ledgerhq/live-common/currencies/index";
 
-type CardProps = CurrencyData & {
+type CardProps = {
+  data: CurrencyData;
+  chartData: MarketCoinDataChart;
   range: KeysPriceChange;
   setRange: (range: KeysPriceChange) => void;
+  height: number;
 };
 
 const { width } = getWindowDimensions();
 
-export const Card: React.FC<CardProps> = ({
-  id,
-  price,
-  priceChangePercentage,
-  chartData,
-  low24h,
-  high24h,
-  range,
-  setRange,
-  ticker,
-  marketcap,
-  totalVolume,
-  marketCapChangePercentage24h,
-  maxSupply,
-  circulatingSupply,
-  totalSupply,
-  ath,
-  atl,
-  athDate,
-  atlDate,
-}) => {
+export const Card: React.FC<CardProps> = ({ data, range, setRange, height, chartData }) => {
+  const {
+    id,
+    price,
+    priceChangePercentage,
+    low24h,
+    high24h,
+    ticker,
+    marketcap,
+    totalVolume,
+    marketCapChangePercentage24h,
+    maxSupply,
+    circulatingSupply,
+    totalSupply,
+    ath,
+    atl,
+    athDate,
+    atlDate,
+  } = data;
+
   const graphWidth = width * 0.86;
   const timeframehWidth = width * 0.96;
   const { colors } = useTheme();
@@ -56,7 +62,7 @@ export const Card: React.FC<CardProps> = ({
       marginRight={5}
       marginLeft={5}
       overflow="hidden"
-      height="88%"
+      height={height}
     >
       <Flex alignItems="center" zIndex={10} top={4}>
         <Ticker currencyId={id} />
diff --git a/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/components/Loading.tsx b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/components/Loading.tsx
new file mode 100644
index 00000000000..39d17e7cd05
--- /dev/null
+++ b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/components/Loading.tsx
@@ -0,0 +1,8 @@
+import { Flex, InfiniteLoader } from "@ledgerhq/native-ui";
+import React from "react";
+
+export const LoadingIndicator = ({ height }: { height: number }) => (
+  <Flex height={height} width="100%" justifyContent="center" alignItems="center">
+    <InfiniteLoader color="primary.c50" size={38} />
+  </Flex>
+);
diff --git a/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/components/__tests__/CardLargeMover.test.tsx b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/components/__tests__/CardLargeMover.test.tsx
index 898d30103db..5a1d4813e29 100644
--- a/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/components/__tests__/CardLargeMover.test.tsx
+++ b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/components/__tests__/CardLargeMover.test.tsx
@@ -29,7 +29,13 @@ describe("Card", () => {
 
   it("displays the ticker, price, and variation", () => {
     const { getByText } = render(
-      <Card {...mockCurrencyData} range={KeysPriceChange.day} setRange={mockSetRange} />,
+      <Card
+        data={mockCurrencyData}
+        chartData={mockCurrencyData.chartData}
+        range={KeysPriceChange.day}
+        setRange={mockSetRange}
+        height={100}
+      />,
     );
     expect(getByText("ETH")).toBeTruthy();
     expect(getByText(/188,350/)).toBeTruthy();
@@ -38,7 +44,13 @@ describe("Card", () => {
 
   it("displays market values: 24h low/high, ATH, ATL, dates", () => {
     const { getByText } = render(
-      <Card {...mockCurrencyData} range={KeysPriceChange.day} setRange={mockSetRange} />,
+      <Card
+        data={mockCurrencyData}
+        chartData={mockCurrencyData.chartData}
+        range={KeysPriceChange.day}
+        setRange={mockSetRange}
+        height={100}
+      />,
     );
     expect(getByText(/188,350/)).toBeTruthy();
     expect(getByText(/191,200/)).toBeTruthy();
@@ -50,7 +62,13 @@ describe("Card", () => {
 
   it("triggers setRange when selecting another range via TabSelector", async () => {
     const { getByText, user } = render(
-      <Card {...mockCurrencyData} range={KeysPriceChange.day} setRange={mockSetRange} />,
+      <Card
+        data={mockCurrencyData}
+        chartData={mockCurrencyData.chartData}
+        range={KeysPriceChange.day}
+        setRange={mockSetRange}
+        height={100}
+      />,
     );
     const tab = getByText("1W");
     await user.press(tab);
diff --git a/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/hooks/useLargeMover.ts b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/hooks/useLargeMover.ts
index 38fa63ba143..0b2fe347907 100644
--- a/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/hooks/useLargeMover.ts
+++ b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/hooks/useLargeMover.ts
@@ -24,11 +24,13 @@ export const useLargeMover = ({
   });
 
   const loading = currencies.some(currencies => currencies.isLoading);
+  const isError = currencies.every(currencies => currencies.isError);
 
   return {
     range,
     setRange,
     currencies,
     loading,
+    isError,
   };
 };
diff --git a/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
index 24623fbb1fc..5a879b22e29 100644
--- a/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
+++ b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
@@ -1,16 +1,20 @@
-import { Flex, InfiniteLoader, Text, Button } from "@ledgerhq/native-ui";
-import React, { useState } from "react";
+import { Flex } from "@ledgerhq/native-ui";
+import React, { useState, useMemo } from "react";
 import { useLargeMover } from "./hooks/useLargeMover";
 import getWindowDimensions from "~/logic/getWindowDimensions";
 import { Card } from "./components/Card";
 import { LandingPagesNavigatorParamList } from "~/components/RootNavigator/types/LandingPagesNavigator";
-import { ScreenName } from "~/const";
+import { NavigatorName, ScreenName } from "~/const";
 import { StackNavigatorProps } from "~/components/RootNavigator/types/helpers";
 import { StickyHeader } from "./components/StickyHeader";
 import { SafeAreaView } from "react-native";
 import { useTheme } from "styled-components/native";
 import { rangeMap } from "./utils";
-
+import { SwiperComponent } from "~/newArch/components/Swiper/components/Swiper";
+import { NavigationProp, useNavigation } from "@react-navigation/native";
+import { WalletTabNavigatorStackParamList } from "~/components/RootNavigator/types/WalletTabNavigator";
+import { LoadingIndicator } from "./components/Loading";
+import { CardType } from "./types";
 type LargeMoverLandingPageProps = StackNavigatorProps<
   LandingPagesNavigatorParamList,
   ScreenName.LargeMoverLandingPage
@@ -20,52 +24,67 @@ export const LargeMoverLandingPage = ({ route }: LargeMoverLandingPageProps) =>
   const { currencyIds, initialRange = "day" } = route.params;
   const currencyIdsArray = currencyIds.toUpperCase().split(",");
   const initialKeyRange = rangeMap[initialRange];
-  const { range, setRange, currencies, loading } = useLargeMover({
+  const { range, setRange, currencies, loading, isError } = useLargeMover({
     currencyIdsArray,
     initialKeyRange,
   });
 
+  const navigation = useNavigation<NavigationProp<WalletTabNavigatorStackParamList>>();
+
   const { colors } = useTheme();
   const height = getWindowDimensions().height * 0.75;
   const [currentIndex, setCurrentIndex] = useState(0);
 
-  const handleNext = () => {
-    setCurrentIndex(prevIndex => (prevIndex + 1) % currencies.length);
-  };
+  const currenciesWithId = useMemo(
+    () =>
+      currencies.map((currency, index) => ({
+        data: currency.data,
+        chartData: currency.chartData,
+        idCard: index,
+        range,
+        setRange,
+      })),
+    [currencies, range, setRange],
+  );
 
-  if (loading) {
-    return (
-      <Flex height={height} width="100%" justifyContent="center" alignItems="center">
-        <InfiniteLoader color="primary.c50" size={38} />
-      </Flex>
-    );
-  }
+  const renderCard = (card: CardType) => {
+    if (!card.data || !card.chartData) {
+      return null;
+    }
 
-  if (currencies.length === 0) {
     return (
-      <Flex>
-        <Text>{"No data available"}</Text>
-      </Flex>
+      <Card
+        data={card.data}
+        chartData={card.chartData}
+        range={range}
+        setRange={setRange}
+        height={height}
+      />
     );
+  };
+
+  if (isError) {
+    navigation.navigate(NavigatorName.Market, {
+      screen: ScreenName.MarketList,
+      params: { top100: true },
+    });
   }
 
   return (
     <SafeAreaView style={{ flex: 1, backgroundColor: colors.neutral.c00 }}>
-      <Flex width="100%" flex={1}>
-        <StickyHeader />
-        <Button onPress={handleNext} size="large">
-          {"Next"}
-        </Button>
-        <Flex padding={1}>
-          {currencies[currentIndex].data && currencies[currentIndex].chartData ? (
-            <Card
-              {...currencies[currentIndex].data!}
-              chartData={currencies[currentIndex].chartData!}
-              range={range}
-              setRange={setRange}
-            />
-          ) : null}
-        </Flex>
+      <StickyHeader />
+      <Flex paddingTop={40}>
+        {loading ? (
+          <LoadingIndicator height={height} />
+        ) : (
+          <SwiperComponent
+            cardContainerStyle={{ justifyContent: "flex-start" }}
+            currentIndex={currentIndex}
+            onIndexChange={setCurrentIndex}
+            initialCards={currenciesWithId}
+            renderCard={renderCard}
+          />
+        )}
       </Flex>
     </SafeAreaView>
   );
diff --git a/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/types.d.ts b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/types.d.ts
index fcee0a08c71..a0ef3942550 100644
--- a/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/types.d.ts
+++ b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/types.d.ts
@@ -13,4 +13,11 @@ type InformationsProps = {
   allTimeLowDate: Date;
 };
 
-export type { InformationsProps };
+type CardType = {
+  data?: CurrencyData | undefined;
+  chartData?: MarketCoinDataChart | undefined;
+  range: KeysPriceChange;
+  setRange: (range: KeysPriceChange) => void;
+};
+
+export type { InformationsProps, CardType };
diff --git a/apps/ledger-live-mobile/src/screens/Settings/Debug/Features/SwiperScreenDebug/index.tsx b/apps/ledger-live-mobile/src/screens/Settings/Debug/Features/SwiperScreenDebug/index.tsx
index 4f79fd0ca8f..db8350bf225 100644
--- a/apps/ledger-live-mobile/src/screens/Settings/Debug/Features/SwiperScreenDebug/index.tsx
+++ b/apps/ledger-live-mobile/src/screens/Settings/Debug/Features/SwiperScreenDebug/index.tsx
@@ -1,16 +1,15 @@
 import React from "react";
-import { useTheme } from "styled-components/native";
 import { INITIAL_CARDS } from "./__mocks__/reviews";
 import { ExampleCard } from "./cards/ExampleCard";
 import { SwiperComponent } from "LLM/components/Swiper/components/Swiper";
 
 export default function SwiperScreenDebug() {
-  const { colors } = useTheme();
   return (
     <SwiperComponent
-      initialCards={INITIAL_CARDS}
+      initialCards={INITIAL_CARDS.map((card, index) => ({ ...card, idCard: index + 1 }))}
       renderCard={card => <ExampleCard card={card} />}
-      containerStyle={{ backgroundColor: colors.opacityPurple.c50 }}
+      currentIndex={0}
+      onIndexChange={index => console.log("Current index changed to:", index)}
     />
   );
 }


Reviews:
user: login: themooneer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T12:49:43Z
user: login: mcayuelas-ledger
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T13:08:55Z
user: login: mcayuelas-ledger
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T07:14:55Z
user: login: mcayuelas-ledger
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T08:25:47Z
user: login: mcayuelas-ledger
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T08:28:23Z
user: login: mcayuelas-ledger
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T08:30:03Z
user: login: themooneer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T06:28:52Z
user: login: themooneer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T06:29:28Z
user: login: themooneer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T06:34:44Z
user: login: themooneer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T06:38:50Z
user: login: RobinVncnt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T08:07:13Z
user: login: themooneer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T10:18:22Z
user: login: themooneer
body: lgtm tested
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T12:58:20Z
user: login: themooneer
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T13:30:10Z
user: login: mcayuelas-ledger
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T14:46:23Z
user: login: mcayuelas-ledger
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:51:48Z

Review comments:
diff_hunk: @@ -12,15 +12,17 @@ export function SwiperComponent<T extends CardWithId>({
   renderCard,
   cardContainerStyle,
   containerStyle,
+  currentIndex,
+  onIndexChange,
 }: SwiperComponentProps<T>) {
-  const { cards, gesture, swipeX, swipeY } = useSwiper(initialCards);
+  const { cards, gesture, swipeX, swipeY } = useSwiper(initialCards, currentIndex, onIndexChange);
 
   return (
     <GestureDetector gesture={gesture}>
       <View style={[styles.container, containerStyle]}>
         <Animated.View style={[styles.cardContainer, cardContainerStyle]}>
           {cards.map((card, index) => (
path: apps/ledger-live-mobile/src/newArch/components/Swiper/components/Swiper.tsx
user: login: themooneer
body: `{cards.map(({idCard}, index) => (`
created_at: 2025-05-13T12:49:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9,6 +9,7 @@ import { StackNavigatorProps } from "~/components/RootNavigator/types/helpers";
 import { StickyHeader } from "./components/StickyHeader";
 import { SafeAreaView } from "react-native";
 import { useTheme } from "styled-components/native";
+import { SwiperComponent } from "~/newArch/components/Swiper/components/Swiper";
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
user: login: mcayuelas-ledger
body: ```suggestion
import { SwiperComponent } from "LLM/components/Swiper/components/Swiper";
```
created_at: 2025-05-13T13:05:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -49,21 +55,19 @@ export const LargeMoverLandingPage = ({ route }: LargeMoverLandingPageProps) =>
 
   return (
     <SafeAreaView style={{ flex: 1, backgroundColor: colors.neutral.c00 }}>
-      <Flex width="100%" flex={1}>
-        <StickyHeader />
-        <Button onPress={handleNext} size="large">
-          {"Next"}
-        </Button>
-        <Flex padding={1}>
-          {currencies[currentIndex].data && currencies[currentIndex].chartData ? (
-            <Card
-              {...currencies[currentIndex].data!}
-              chartData={currencies[currentIndex].chartData!}
-              range={range}
-              setRange={setRange}
-            />
-          ) : null}
-        </Flex>
+      <StickyHeader />
+      <Flex paddingTop={20}>
+        {currencies[0].data && currencies[0].chartData ? (
+          <SwiperComponent
+            cardContainerStyle={{ justifyContent: "flex-start" }}
+            currentIndex={currentIndex}
+            onIndexChange={setCurrentIndex}
+            initialCards={currenciesWithId}
+            renderCard={card => (
+              <Card {...card.data!} chartData={card.chartData!} range={range} setRange={setRange} />
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
user: login: mcayuelas-ledger
body: why do we need to force with `!` ?
created_at: 2025-05-13T13:06:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -49,21 +55,19 @@ export const LargeMoverLandingPage = ({ route }: LargeMoverLandingPageProps) =>
 
   return (
     <SafeAreaView style={{ flex: 1, backgroundColor: colors.neutral.c00 }}>
-      <Flex width="100%" flex={1}>
-        <StickyHeader />
-        <Button onPress={handleNext} size="large">
-          {"Next"}
-        </Button>
-        <Flex padding={1}>
-          {currencies[currentIndex].data && currencies[currentIndex].chartData ? (
-            <Card
-              {...currencies[currentIndex].data!}
-              chartData={currencies[currentIndex].chartData!}
-              range={range}
-              setRange={setRange}
-            />
-          ) : null}
-        </Flex>
+      <StickyHeader />
+      <Flex paddingTop={20}>
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
user: login: mcayuelas-ledger
body: IMHO, We should handle Loading/ error / display inside this Flex
created_at: 2025-05-13T13:07:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -49,21 +55,19 @@ export const LargeMoverLandingPage = ({ route }: LargeMoverLandingPageProps) =>
 
   return (
     <SafeAreaView style={{ flex: 1, backgroundColor: colors.neutral.c00 }}>
-      <Flex width="100%" flex={1}>
-        <StickyHeader />
-        <Button onPress={handleNext} size="large">
-          {"Next"}
-        </Button>
-        <Flex padding={1}>
-          {currencies[currentIndex].data && currencies[currentIndex].chartData ? (
-            <Card
-              {...currencies[currentIndex].data!}
-              chartData={currencies[currentIndex].chartData!}
-              range={range}
-              setRange={setRange}
-            />
-          ) : null}
-        </Flex>
+      <StickyHeader />
+      <Flex paddingTop={20}>
+        {currencies[0].data && currencies[0].chartData ? (
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
user: login: mcayuelas-ledger
body: are we sure about this condition?
created_at: 2025-05-13T13:07:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,43 +50,49 @@ export const LargeMoverLandingPage = ({ route }: LargeMoverLandingPageProps) =>
 
   const [currentIndex, setCurrentIndex] = useState(0);
 
-  const handleNext = () => {
-    setCurrentIndex(prevIndex => (prevIndex + 1) % currencies.length);
-  };
+  const navigation = useNavigation<NavigationProp<WalletTabNavigatorStackParamList>>();
 
-  if (loading) {
-    return (
-      <Flex height={height} width="100%" justifyContent="center" alignItems="center">
-        <InfiniteLoader color="primary.c50" size={38} />
-      </Flex>
-    );
-  }
+  const currenciesWithId = useMemo(
+    () =>
+      currencies.map((currency, index) => ({
+        ...currency,
+        idCard: index,
+        range,
+        setRange,
+      })),
+    [currencies, range, setRange],
+  );
+
+  const renderCard = (card: Card) => {
+    if (!card.data || !card.chartData) {
+      return null;
+    }
+    return <Card {...card.data} chartData={card.chartData} range={range} setRange={setRange} />;
+  };
 
   if (currencies.length === 0) {
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
user: login: mcayuelas-ledger
body: How can it be equal to 0 ? 

It will always be defined even if all queries are in error no? I think it's not the good assertion here
created_at: 2025-05-14T07:14:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -12,15 +12,17 @@ export function SwiperComponent<T extends CardWithId>({
   renderCard,
   cardContainerStyle,
   containerStyle,
+  currentIndex,
+  onIndexChange,
 }: SwiperComponentProps<T>) {
-  const { cards, gesture, swipeX, swipeY } = useSwiper(initialCards);
+  const { cards, gesture, swipeX, swipeY } = useSwiper(initialCards, currentIndex, onIndexChange);
 
   return (
     <GestureDetector gesture={gesture}>
       <View style={[styles.container, containerStyle]}>
         <Animated.View style={[styles.cardContainer, cardContainerStyle]}>
           {cards.map((card, index) => (
path: apps/ledger-live-mobile/src/newArch/components/Swiper/components/Swiper.tsx
user: login: mcayuelas-ledger
body: `card` is used below :) 
created_at: 2025-05-14T08:25:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,11 +20,13 @@ export const useLargeMover = ({ currencyIds, initialRange = KeysPriceChange.day
   });
 
   const loading = currencies.some(currencies => currencies.isLoading);
+  const isError = currencies.some(currencies => currencies.isError);
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/hooks/useLargeMover.ts
user: login: mcayuelas-ledger
body: if one of 5 currencies is in Error, you will redirect? 
created_at: 2025-05-14T08:28:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,23 +1,46 @@
-import { Flex, InfiniteLoader, Text, Button } from "@ledgerhq/native-ui";
-import React, { useState } from "react";
+import { Flex, InfiniteLoader } from "@ledgerhq/native-ui";
+import React, { useState, useMemo } from "react";
 import { useLargeMover } from "./hooks/useLargeMover";
 import getWindowDimensions from "~/logic/getWindowDimensions";
 import { Card } from "./components/Card";
 import { LandingPagesNavigatorParamList } from "~/components/RootNavigator/types/LandingPagesNavigator";
-import { ScreenName } from "~/const";
+import { NavigatorName, ScreenName } from "~/const";
 import { StackNavigatorProps } from "~/components/RootNavigator/types/helpers";
 import { StickyHeader } from "./components/StickyHeader";
 import { SafeAreaView } from "react-native";
 import { useTheme } from "styled-components/native";
+import { SwiperComponent } from "LLM/components/Swiper/components/Swiper";
+import {
+  CurrencyData,
+  KeysPriceChange,
+  MarketCoinDataChart,
+} from "@ledgerhq/live-common/market/utils/types";
+import { NavigationProp, useNavigation } from "@react-navigation/native";
+import { WalletTabNavigatorStackParamList } from "~/components/RootNavigator/types/WalletTabNavigator";
 
 type LargeMoverLandingPageProps = StackNavigatorProps<
   LandingPagesNavigatorParamList,
   ScreenName.LargeMoverLandingPage
 >;
 
+type Card = {
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
user: login: mcayuelas-ledger
body: you can create a dedicated file for types
created_at: 2025-05-14T08:28:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,23 +1,46 @@
-import { Flex, InfiniteLoader, Text, Button } from "@ledgerhq/native-ui";
-import React, { useState } from "react";
+import { Flex, InfiniteLoader } from "@ledgerhq/native-ui";
+import React, { useState, useMemo } from "react";
 import { useLargeMover } from "./hooks/useLargeMover";
 import getWindowDimensions from "~/logic/getWindowDimensions";
 import { Card } from "./components/Card";
 import { LandingPagesNavigatorParamList } from "~/components/RootNavigator/types/LandingPagesNavigator";
-import { ScreenName } from "~/const";
+import { NavigatorName, ScreenName } from "~/const";
 import { StackNavigatorProps } from "~/components/RootNavigator/types/helpers";
 import { StickyHeader } from "./components/StickyHeader";
 import { SafeAreaView } from "react-native";
 import { useTheme } from "styled-components/native";
+import { SwiperComponent } from "LLM/components/Swiper/components/Swiper";
+import {
+  CurrencyData,
+  KeysPriceChange,
+  MarketCoinDataChart,
+} from "@ledgerhq/live-common/market/utils/types";
+import { NavigationProp, useNavigation } from "@react-navigation/native";
+import { WalletTabNavigatorStackParamList } from "~/components/RootNavigator/types/WalletTabNavigator";
 
 type LargeMoverLandingPageProps = StackNavigatorProps<
   LandingPagesNavigatorParamList,
   ScreenName.LargeMoverLandingPage
 >;
 
+type Card = {
+  data?: CurrencyData | undefined;
+  chartData?: MarketCoinDataChart | undefined;
+  range: KeysPriceChange;
+  setRange: (range: KeysPriceChange) => void;
+  isLoading: boolean;
+  isError: boolean;
+};
+
+const LoadingIndicator = ({ height }: { height: number }) => (
+  <Flex height={height} width="100%" justifyContent="center" alignItems="center">
+    <InfiniteLoader color="primary.c50" size={38} />
+  </Flex>
+);
+
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
user: login: mcayuelas-ledger
body: you can create a LoadingComponent in `components/`

Also I don't think `height ` is needed, just use 100% as  the component will be a children 
created_at: 2025-05-14T08:29:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,46 +31,57 @@ export const LargeMoverLandingPage = ({ route }: LargeMoverLandingPageProps) =>
 
   const [currentIndex, setCurrentIndex] = useState(0);
 
+  const navigation = useNavigation<NavigationProp<WalletTabNavigatorStackParamList>>();
+
   const currenciesWithId = useMemo(
     () =>
       currencies.map((currency, index) => ({
         ...currency,
         idCard: index,
+        range,
+        setRange,
       })),
-    [currencies],
+    [currencies, range, setRange],
   );
 
-  if (loading) {
+  const renderCard = (card: CardType) => {
+    if (!card.data || !card.chartData) {
+      return null;
+    }
     return (
-      <Flex height={height} width="100%" justifyContent="center" alignItems="center">
-        <InfiniteLoader color="primary.c50" size={38} />
-      </Flex>
+      <Card
+        {...card.data}
+        chartData={card.chartData}
+        range={range}
+        setRange={setRange}
+        height={height}
+      />
     );
-  }
+  };
 
-  if (currencies.length === 0) {
-    return (
-      <Flex>
-        <Text>{"No data available"}</Text>
-      </Flex>
-    );
+  if (isError) {
+    navigation.navigate(NavigatorName.Market, {
+      screen: ScreenName.MarketList,
+      params: { top100: true },
+    });
+    return null;
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
user: login: themooneer
body: you don't need to return null here 
created_at: 2025-05-15T06:28:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,46 +31,57 @@ export const LargeMoverLandingPage = ({ route }: LargeMoverLandingPageProps) =>
 
   const [currentIndex, setCurrentIndex] = useState(0);
 
+  const navigation = useNavigation<NavigationProp<WalletTabNavigatorStackParamList>>();
+
   const currenciesWithId = useMemo(
     () =>
       currencies.map((currency, index) => ({
         ...currency,
         idCard: index,
+        range,
+        setRange,
       })),
-    [currencies],
+    [currencies, range, setRange],
   );
 
-  if (loading) {
+  const renderCard = (card: CardType) => {
+    if (!card.data || !card.chartData) {
+      return null;
+    }
     return (
-      <Flex height={height} width="100%" justifyContent="center" alignItems="center">
-        <InfiniteLoader color="primary.c50" size={38} />
-      </Flex>
+      <Card
+        {...card.data}
+        chartData={card.chartData}
+        range={range}
+        setRange={setRange}
+        height={height}
+      />
     );
-  }
+  };
 
-  if (currencies.length === 0) {
-    return (
-      <Flex>
-        <Text>{"No data available"}</Text>
-      </Flex>
-    );
+  if (isError) {
+    navigation.navigate(NavigatorName.Market, {
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
user: login: themooneer
body: use `navigation.replace` instead 
created_at: 2025-05-15T06:29:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,46 +31,57 @@ export const LargeMoverLandingPage = ({ route }: LargeMoverLandingPageProps) =>
 
   const [currentIndex, setCurrentIndex] = useState(0);
 
+  const navigation = useNavigation<NavigationProp<WalletTabNavigatorStackParamList>>();
+
   const currenciesWithId = useMemo(
     () =>
       currencies.map((currency, index) => ({
         ...currency,
         idCard: index,
+        range,
+        setRange,
       })),
-    [currencies],
+    [currencies, range, setRange],
   );
 
-  if (loading) {
+  const renderCard = (card: CardType) => {
+    if (!card.data || !card.chartData) {
+      return null;
+    }
     return (
-      <Flex height={height} width="100%" justifyContent="center" alignItems="center">
-        <InfiniteLoader color="primary.c50" size={38} />
-      </Flex>
+      <Card
+        {...card.data}
+        chartData={card.chartData}
+        range={range}
+        setRange={setRange}
+        height={height}
+      />
     );
-  }
+  };
 
-  if (currencies.length === 0) {
-    return (
-      <Flex>
-        <Text>{"No data available"}</Text>
-      </Flex>
-    );
+  if (isError) {
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
user: login: themooneer
body: in term of UX , if am using the Large Mover, and suddenly there is an error and it redirect me directly to the Market Page i would ask many questions.
@RobinVncnt @mcayuelas-ledger what is the purpose of this ? should it be well translated in term of UI if the user encounters an errror, show error message and a cta to redirect to market for example with a clear wording. 
While the large mover feature is impressive & fancy in term of UI , these kind of details can degrade the user experience. 
IMHO, it's a suggestion to discover 
created_at: 2025-05-15T06:34:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,46 +31,57 @@ export const LargeMoverLandingPage = ({ route }: LargeMoverLandingPageProps) =>
 
   const [currentIndex, setCurrentIndex] = useState(0);
 
+  const navigation = useNavigation<NavigationProp<WalletTabNavigatorStackParamList>>();
+
   const currenciesWithId = useMemo(
     () =>
       currencies.map((currency, index) => ({
         ...currency,
         idCard: index,
+        range,
+        setRange,
       })),
-    [currencies],
+    [currencies, range, setRange],
   );
 
-  if (loading) {
+  const renderCard = (card: CardType) => {
+    if (!card.data || !card.chartData) {
+      return null;
+    }
     return (
-      <Flex height={height} width="100%" justifyContent="center" alignItems="center">
-        <InfiniteLoader color="primary.c50" size={38} />
-      </Flex>
+      <Card
+        {...card.data}
+        chartData={card.chartData}
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
user: login: themooneer
body: i prefer this way rather than the previous line  `{...card.data}`
created_at: 2025-05-15T06:38:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,46 +31,57 @@ export const LargeMoverLandingPage = ({ route }: LargeMoverLandingPageProps) =>
 
   const [currentIndex, setCurrentIndex] = useState(0);
 
+  const navigation = useNavigation<NavigationProp<WalletTabNavigatorStackParamList>>();
+
   const currenciesWithId = useMemo(
     () =>
       currencies.map((currency, index) => ({
         ...currency,
         idCard: index,
+        range,
+        setRange,
       })),
-    [currencies],
+    [currencies, range, setRange],
   );
 
-  if (loading) {
+  const renderCard = (card: CardType) => {
+    if (!card.data || !card.chartData) {
+      return null;
+    }
     return (
-      <Flex height={height} width="100%" justifyContent="center" alignItems="center">
-        <InfiniteLoader color="primary.c50" size={38} />
-      </Flex>
+      <Card
+        {...card.data}
+        chartData={card.chartData}
+        range={range}
+        setRange={setRange}
+        height={height}
+      />
     );
-  }
+  };
 
-  if (currencies.length === 0) {
-    return (
-      <Flex>
-        <Text>{"No data available"}</Text>
-      </Flex>
-    );
+  if (isError) {
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
user: login: RobinVncnt
body: There was a mistake, you are only redirected when there is no data, so the case where you access the Large Mover page and then get redirected to the Market page cannot happen. Do you think an error message is necessary for this redirection?
created_at: 2025-05-15T08:07:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,46 +31,57 @@ export const LargeMoverLandingPage = ({ route }: LargeMoverLandingPageProps) =>
 
   const [currentIndex, setCurrentIndex] = useState(0);
 
+  const navigation = useNavigation<NavigationProp<WalletTabNavigatorStackParamList>>();
+
   const currenciesWithId = useMemo(
     () =>
       currencies.map((currency, index) => ({
         ...currency,
         idCard: index,
+        range,
+        setRange,
       })),
-    [currencies],
+    [currencies, range, setRange],
   );
 
-  if (loading) {
+  const renderCard = (card: CardType) => {
+    if (!card.data || !card.chartData) {
+      return null;
+    }
     return (
-      <Flex height={height} width="100%" justifyContent="center" alignItems="center">
-        <InfiniteLoader color="primary.c50" size={38} />
-      </Flex>
+      <Card
+        {...card.data}
+        chartData={card.chartData}
+        range={range}
+        setRange={setRange}
+        height={height}
+      />
     );
-  }
+  };
 
-  if (currencies.length === 0) {
-    return (
-      <Flex>
-        <Text>{"No data available"}</Text>
-      </Flex>
-    );
+  if (isError) {
path: apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/LargeMoverLandingPage/index.tsx
user: login: themooneer
body: in this case, when there is no data, a significant name for the variable like `hasNoData` imho is better than `isError`  wdyt
created_at: 2025-05-15T10:18:21Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-12T14:30:36Z
body: [vc]: #wrfyMdI+y1nZ/+XFxOe7UWTs+NHNrRLCvyKXjhmrLf8=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJyZWFjdC11aS1zdG9yeWJvb2siLCJyb290RGlyZWN0b3J5IjpudWxsLCJsaXZlRmVlZGJhY2siOnsibGluayI6bnVsbCwicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjB9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vbGVkZ2VyaHEvcmVhY3QtdWktc3Rvcnlib29rLzJvZ1IxdVc0YkQ4ejVIZ21EZ2NrQjNEWXBjV2UiLCJuZXh0Q29tbWl0U3RhdHVzIjoiSUdOT1JFRCIsInByZXZpZXdVcmwiOiJyZWFjdC11aS1zdG9yeWJvb2stZ2l0LWZlYXQtbGFyZ2UtbW92ZXItc3dpcGUtbGVkZ2VyaHEudmVyY2VsLmFwcCJ9LHsibmFtZSI6Im5hdGl2ZS11aS1zdG9yeWJvb2siLCJyb290RGlyZWN0b3J5IjpudWxsLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9sZWRnZXJocS9uYXRpdmUtdWktc3Rvcnlib29rLzVudk5FeFpQSE5DMU5nc0tVMTlLYUhyd3BHSEsiLCJuZXh0Q29tbWl0U3RhdHVzIjoiSUdOT1JFRCIsInByZXZpZXdVcmwiOiJuYXRpdmUtdWktc3Rvcnlib29rLWdpdC1mZWF0LWxhcmdlLW1vdmVyLXN3aXBlLWxlZGdlcmhxLnZlcmNlbC5hcHAifSx7Im5hbWUiOiJ3ZWItdG9vbHMiLCJyb290RGlyZWN0b3J5IjoiYXBwcy93ZWItdG9vbHMiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9sZWRnZXJocS93ZWItdG9vbHMvRFppZldBNW1qOEFjaVNMWFByNWQxSEZRSDZjWSIsIm5leHRDb21taXRTdGF0dXMiOiJJR05PUkVEIiwicHJldmlld1VybCI6IndlYi10b29scy1naXQtZmVhdC1sYXJnZS1tb3Zlci1zd2lwZS1sZWRnZXJocS52ZXJjZWwuYXBwIn0seyJuYW1lIjoibGVkZ2VyLWxpdmUtZ2l0aHViLWJvdCIsInJvb3REaXJlY3RvcnkiOiJ0b29scy9naXRodWItYm90IiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vbGVkZ2VyaHEvbGVkZ2VyLWxpdmUtZ2l0aHViLWJvdC9DYWc1eVJTc1NOSEVzMTQzR1czZmVkOHJFQ3BCIiwibmV4dENvbW1pdFN0YXR1cyI6IklHTk9SRUQiLCJwcmV2aWV3VXJsIjoibGVkZ2VyLWxpdmUtZ2l0aHViLWJvdC1naXQtZmVhdC1sYXJnZS1tb3Zlci1zd2lwZS1sZWRnZXJocS52ZXJjZWwuYXBwIn1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)


<details><summary>4 Skipped Deployments</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **ledger-live-github-bot** | ⬜️ Ignored ([Inspect](https://vercel.com/ledgerhq/ledger-live-github-bot/Cag5yRSsSNHEs143GW3fed8rECpB)) | [Visit Preview](https://ledger-live-github-bot-git-feat-large-mover-swipe-ledgerhq.vercel.app) |  | May 16, 2025 3:01pm |
| **native-ui-storybook** | ⬜️ Ignored ([Inspect](https://vercel.com/ledgerhq/native-ui-storybook/5nvNExZPHNC1NgsKU19KaHrwpGHK)) | [Visit Preview](https://native-ui-storybook-git-feat-large-mover-swipe-ledgerhq.vercel.app) |  | May 16, 2025 3:01pm |
| **react-ui-storybook** | ⬜️ Ignored ([Inspect](https://vercel.com/ledgerhq/react-ui-storybook/2ogR1uW4bD8z5HgmDgckB3DYpcWe)) | [Visit Preview](https://react-ui-storybook-git-feat-large-mover-swipe-ledgerhq.vercel.app) |  | May 16, 2025 3:01pm |
| **web-tools** | ⬜️ Ignored ([Inspect](https://vercel.com/ledgerhq/web-tools/DZifWA5mj8AciSLXPr5d1HFQH6cY)) | [Visit Preview](https://web-tools-git-feat-large-mover-swipe-ledgerhq.vercel.app) |  | May 16, 2025 3:01pm |
</details>





author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-05-16T15:17:14Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=LedgerHQ_ledger-live&pullRequest=10183) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [28.2% Coverage on New Code](https://sonarcloud.io/component_measures?id=LedgerHQ_ledger-live&pullRequest=10183&metric=new_coverage&view=list) (required ≥ 80%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=LedgerHQ_ledger-live&pullRequest=10183)


author_association: NONE