diff --git a/.changeset/calm-lizards-doubt.md b/.changeset/calm-lizards-doubt.md new file mode 100644 index 000000000000..c7712a9827a1 --- /dev/null +++ b/.changeset/calm-lizards-doubt.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +Add label clear signing diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index d631d48c6ce3..e8eaabc9866d 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -6849,12 +6849,16 @@ "components": { "disclaimer": { "clearSigningEnabled": "Clear signing enabled", + "clearSigningDisabled": "Clear signing disabled", "checkbox": "Do not remind me again.", "CTA": "Open {{app}}" }, "manifestsList": { "title": "Explore", "description": "Discover the best of web3 curated by Ledger" + }, + "label": { + "clearSigning": "clear signing" } }, "main": { @@ -6864,6 +6868,10 @@ "header": { "title": "Explore web3", "placeholder": "Search or type a URL" + }, + "clearSigning": { + "title": "Clear signing", + "description": "Clear signing allows you to sign a message without revealing the content of the message to the app. This is useful for privacy and security reasons." } } } diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/__integrations__/web3hub.integration.test.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/__integrations__/web3hub.integration.test.tsx index f134c6466322..394b6fcb3e0a 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/__integrations__/web3hub.integration.test.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/__integrations__/web3hub.integration.test.tsx @@ -245,6 +245,32 @@ describe("Web3Hub integration test", () => { expect(screen.getByRole("searchbox")).toBeDisabled(); }); + it("Should be able to see Clear Signing section and label on disclaimer", async () => { + const { user } = render(); + expect(await screen.findByText("Explore web3")).toBeOnTheScreen(); + await waitForLoader(); + + expect((await screen.findAllByText("Clear signing"))[0]).toBeOnTheScreen(); + expect((await screen.findAllByRole("banner", { name: /clear signing/i }))[0]).toBeOnTheScreen(); + + expect((await screen.findAllByText("Clear-signing"))[0]).toBeOnTheScreen(); + await user.press(screen.getAllByText("Clear-signing")[0]); + expect(await screen.findByText("Clear signing enabled")).toBeOnTheScreen(); + + expect(await screen.findByText("Open Clear-signing")).toBeOnTheScreen(); + await user.press(screen.getByText("Open Clear-signing")); + expect(await screen.findByText("clear-signing-0")).toBeOnTheScreen(); + expect(await screen.findByText("Clear-signing")).toBeOnTheScreen(); + + expect(await screen.findByRole("button", { name: /back/i })).toBeOnTheScreen(); + await user.press(screen.getByRole("button", { name: /back/i })); + expect(await screen.findByText("Explore web3")).toBeOnTheScreen(); + + expect((await screen.findAllByText("Dummy Wallet App"))[0]).toBeOnTheScreen(); + await user.press(screen.getAllByText("Dummy Wallet App")[0]); + expect(await screen.findByText("Clear signing disabled")).toBeOnTheScreen(); + }); + it("Should only show the confirmation bottom modal if not dismissed previously", async () => { const { user } = render(); diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/Disclaimer/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/Disclaimer/index.tsx index 8517d86aa8a3..36aea632c4d3 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/Disclaimer/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/Disclaimer/index.tsx @@ -25,6 +25,10 @@ export default function Disclaimer({ : undefined; }, [locale, manifest?.content.description]); + const clearSigningEnabled = useMemo(() => { + return manifest?.categories.includes("clear signing"); + }, [manifest?.categories]); + return ( {manifest ? ( @@ -43,14 +47,25 @@ export default function Disclaimer({ - - - - - - {t("web3hub.components.disclaimer.clearSigningEnabled")} - - + {clearSigningEnabled ? ( + + + + + + {t("web3hub.components.disclaimer.clearSigningEnabled")} + + + ) : ( + + + + + + {t("web3hub.components.disclaimer.clearSigningDisabled")} + + + )} = ({ text, style }) => { + const { badgeColor, borderColor, backgroundColor } = style; + return ( + + {text} + + ); +}; + +export default Label; diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/index.tsx index ce088d17b73f..20d2bc24b505 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/ManifestItem/index.tsx @@ -7,6 +7,7 @@ import { AppBranch, AppManifest } from "@ledgerhq/live-common/wallet-api/types"; import type { MainProps, SearchProps } from "LLM/features/Web3Hub/types"; import AppIcon from "LLM/features/Web3Hub/components/AppIcon"; import { Theme } from "~/colors"; +import Label from "./Label"; export type NavigationProp = MainProps["navigation"] | SearchProps["navigation"]; @@ -45,7 +46,6 @@ function getBranchStyle(branch: AppBranch, colors: Theme["colors"]) { }; } } - export default function ManifestItem({ manifest, onPress, @@ -78,40 +78,44 @@ export default function ManifestItem({ return manifest.icon?.trim(); }, [manifest.icon]); + const clearSigningEnabled = useMemo(() => { + return manifest?.categories.includes("clear signing"); + }, [manifest?.categories]); + return ( - + {manifest.name} - {manifest.branch !== "stable" && ( - - {t(`platform.catalog.branch.${manifest.branch}`, { - defaultValue: manifest.branch, - })} - - )} + + {manifest.branch !== "stable" && ( + + {url} diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/index.tsx index e007b2f55f70..a9f0d1edfa84 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/components/ManifestsList/index.tsx @@ -21,6 +21,7 @@ type Props = { title?: string; pt?: number; pb?: number; + headerComponent?: React.ReactNode; }; const AnimatedFlashList = Animated.createAnimatedComponent>(FlashList); @@ -39,7 +40,14 @@ const renderItem = ({ return ; }; -export default function ManifestsList({ navigation, onScroll, title, pt = 0, pb = 0 }: Props) { +export default function ManifestsList({ + navigation, + onScroll, + title, + pt = 0, + pb = 0, + headerComponent, +}: Props) { const { t } = useTranslation(); const [selectedCategory, selectCategory] = useState("all"); const { data, isLoading, onEndReached } = useManifestsListViewModel(selectedCategory); @@ -68,13 +76,13 @@ export default function ManifestsList({ navigation, onScroll, title, pt = 0, pb }} ListHeaderComponent={ <> + {headerComponent} {title ?? t("web3hub.components.manifestsList.title")} {t("web3hub.components.manifestsList.description")} - diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubMain/components/HorizontalList/MinimalAppCard/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubMain/components/HorizontalList/MinimalAppCard/index.tsx new file mode 100644 index 000000000000..8bffe698628d --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubMain/components/HorizontalList/MinimalAppCard/index.tsx @@ -0,0 +1,29 @@ +import React, { useCallback, useMemo } from "react"; +import { TouchableOpacity } from "react-native"; +import { Flex, Text } from "@ledgerhq/native-ui"; +import AppIcon from "LLM/features/Web3Hub/components/AppIcon"; +import { AppManifest } from "@ledgerhq/live-common/wallet-api/types"; + +export default function MinimalAppCard({ + item, + onPress, +}: { + item: AppManifest; + onPress: (manifest: AppManifest) => void; +}) { + const disabled = useMemo(() => item.branch === "soon", [item]); + const handlePress = useCallback(() => { + if (!disabled) { + onPress(item); + } + }, [disabled, item, onPress]); + + return ( + + + + {item.name} + + + ); +} diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubMain/components/HorizontalList/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubMain/components/HorizontalList/index.tsx new file mode 100644 index 000000000000..a76246c9ca8d --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubMain/components/HorizontalList/index.tsx @@ -0,0 +1,70 @@ +import React from "react"; +import { StyleSheet } from "react-native"; +import { FlashList } from "@shopify/flash-list"; +import { Box, Flex, InfiniteLoader, Text } from "@ledgerhq/native-ui"; +import { AppManifest } from "@ledgerhq/live-common/wallet-api/types"; +import MinimalAppCard from "./MinimalAppCard"; + +type Props = { + title: string; + isLoading: boolean; + data: AppManifest[]; + onEndReached?: () => void; + onPressItem: (manifest: AppManifest) => void; + testID?: string; +}; + +type PropRenderItem = { + item: AppManifest; + extraData?: (manifest: AppManifest) => void; +}; + +const identityFn = (item: AppManifest) => item.id; + +const renderItem = ({ item, extraData: onPressItem = () => {} }: PropRenderItem) => ( + +); + +export default function HorizontalList({ + title, + isLoading, + data, + onEndReached, + onPressItem, + testID, +}: Props) { + return ( + <> + + {title} + + + + + + ) : null + } + estimatedItemSize={70} + data={data} + showsHorizontalScrollIndicator={false} + extraData={onPressItem} + onEndReached={onEndReached} + /> + + + ); +} + +const styles = StyleSheet.create({ + container: { + paddingHorizontal: 5, + }, +}); diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubMain/components/ManifestsCategoryList/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubMain/components/ManifestsCategoryList/index.tsx new file mode 100644 index 000000000000..3ec4fd2d1ca7 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubMain/components/ManifestsCategoryList/index.tsx @@ -0,0 +1,46 @@ +import React, { useCallback } from "react"; +import { NavigatorName, ScreenName } from "~/const"; +import useManifestsListViewModel from "LLM/features/Web3Hub/components/ManifestsList/useManifestsListViewModel"; +import Disclaimer, { useDisclaimerViewModel } from "LLM/features/Web3Hub/components/Disclaimer"; +import { MainProps } from "LLM/features/Web3Hub/types"; +import HorizontalList from "../HorizontalList"; + +type Props = { + title: string; + categoryId: string; + navigation: MainProps["navigation"]; +}; + +const ManifestsCategoryList = ({ title, categoryId, navigation }: Props) => { + const { data, isLoading, onEndReached } = useManifestsListViewModel(categoryId); + + const goToApp = useCallback( + (manifestId: string) => { + navigation.push(NavigatorName.Web3Hub, { + screen: ScreenName.Web3HubApp, + params: { + manifestId: manifestId, + }, + }); + }, + [navigation], + ); + + const disclaimer = useDisclaimerViewModel(goToApp); + + return data && data.length > 0 ? ( + <> + + + + ) : null; +}; + +export default ManifestsCategoryList; diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubMain/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubMain/index.tsx index 4616c95efcd4..ed67b5d79feb 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubMain/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/screens/Web3HubMain/index.tsx @@ -7,6 +7,7 @@ import useScrollHandler from "LLM/features/Web3Hub/hooks/useScrollHandler"; import ManifestsList from "LLM/features/Web3Hub/components/ManifestsList"; import { MAIN_BUTTON_BOTTOM, MAIN_BUTTON_SIZE } from "~/components/TabBar/shared"; import Header, { ANIMATION_HEIGHT, TOTAL_HEADER_HEIGHT } from "./components/Header"; +import ManifestsCategoryList from "./components/ManifestsCategoryList"; const PADDING_BOTTOM = MAIN_BUTTON_SIZE + MAIN_BUTTON_BOTTOM; @@ -32,6 +33,15 @@ export default function Web3HubMain({ navigation }: MainProps) { pt={TOTAL_HEADER_HEIGHT} // Using this padding to keep the view visible under the tab button pb={PADDING_BOTTOM} + headerComponent={ + + } /> diff --git a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/utils/api/mocks/manifests.ts b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/utils/api/mocks/manifests.ts index 4d5356930336..f724f1b2e428 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Web3Hub/utils/api/mocks/manifests.ts +++ b/apps/ledger-live-mobile/src/newArch/features/Web3Hub/utils/api/mocks/manifests.ts @@ -215,6 +215,37 @@ export const mocks: AppManifest[] = [ domains: ["https://"], visibility: "complete", }, + { + id: "clear-signing", + name: "Clear-signing", + private: false, + url: "https://www.staderlabs.com/eth/stake/", + homepageUrl: "https://www.staderlabs.com/eth/", + icon: "https://cdn.live.ledger.com/icons/platform/stader-bnb.png", + platforms: ["android", "ios", "desktop"], + apiVersion: "^2.0.0 || ^1.0.0", + manifestVersion: "2", + branch: "stable", + categories: ["defi", "clear signing"], + currencies: ["ethereum", "ethereum/erc20/bnb", "bsc"], + content: { + shortDescription: { + en: "Our liquid staking solution allows users to stake ETH and get a fungible liquid token(ETHx) back that shows their claim to the underlying staked assets. We work with the ecosystem projects to ensure wide usability of these tokens on DEXs, lending/borrowing protocols, yield aggregators and more.", + }, + description: { + en: "About Stader - Stader is a non-custodial, smart contract-based staking platform that helps retail and institutions conveniently discover and access staking solutions. In addition to its own platform, Stader’s modular smart contracts and staking middleware infrastructure for Proof-of-Stake (PoS) networks can be leveraged for retail crypto users, exchanges, custodians, and mainstream FinTech players Our Project - Stader Liquid staking helps us build a future where community members will no longer have to choose between securing the network through staking or participating in the thriving DeFi, NFT and gaming protocols on Polygon. Instead, users can access both staking and DeFi rewards using the liquid token across liquidity pools, yield farming, lending and borrowing. Stader provides a convenient way for the community to stake with multiple validators at once, helping them reduce risk & costs while increasing returns. Our liquid staking solution allows users to stake Matic and get a fungible liquid token MaticX that shows their claim to the underlying staked assets. The MaticX token can be leveraged on the multiple Defi opportunities.", + }, + }, + permissions: [ + "account.list", + "account.request", + "currency.list", + "message.sign", + "transaction.signAndBroadcast", + ], + domains: ["https://"], + visibility: "complete", + }, ]; function getManifests() {