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

feat: Save locally added manifest into the storage on both Mobile and Desktop client #6719

Merged
merged 16 commits into from May 10, 2024
Merged
7 changes: 7 additions & 0 deletions .changeset/hot-zebras-ring.md
@@ -0,0 +1,7 @@
---
"ledger-live-desktop": patch
"live-mobile": patch
"@ledgerhq/live-common": patch
---

Save locally added manifest into the storage on both Mobile and Desktop client
Expand Up @@ -7,8 +7,13 @@ import {
} from "~/renderer/reducers/settings";
import { useSelector } from "react-redux";
import { RemoteLiveAppProvider } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index";
import { LocalLiveAppProvider } from "@ledgerhq/live-common/platform/providers/LocalLiveAppProvider/index";
import { LocalLiveAppProvider } from "@ledgerhq/live-common/wallet-api/LocalLiveAppProvider/index";
import { RampCatalogProvider } from "@ledgerhq/live-common/platform/providers/RampCatalogProvider/index";
import { useDB } from "../storage";
import {
DISCOVER_STORE_KEY,
INITIAL_PLATFORM_STATE,
} from "@ledgerhq/live-common/wallet-api/constants";

type PlatformAppProviderWrapperProps = {
children: ReactNode;
Expand All @@ -21,6 +26,7 @@ export function PlatformAppProviderWrapper({ children }: PlatformAppProviderWrap
const allowExperimentalApps = useSelector(allowExperimentalAppsSelector);
const provider = useSelector(catalogProviderSelector);
const locale = useSelector(languageSelector);
const localLiveAppDB = useLocalLiveAppDB();

return (
<RemoteLiveAppProvider
Expand All @@ -33,11 +39,20 @@ export function PlatformAppProviderWrapper({ children }: PlatformAppProviderWrap
}}
updateFrequency={AUTO_UPDATE_DEFAULT_DELAY}
>
<LocalLiveAppProvider>
<LocalLiveAppProvider db={localLiveAppDB}>
<RampCatalogProvider provider={provider} updateFrequency={AUTO_UPDATE_DEFAULT_DELAY}>
{children}
</RampCatalogProvider>
</LocalLiveAppProvider>
</RemoteLiveAppProvider>
);
}

function useLocalLiveAppDB() {
return useDB(
"app",
DISCOVER_STORE_KEY,
INITIAL_PLATFORM_STATE,
state => state.localLiveApp || INITIAL_PLATFORM_STATE.localLiveApp,
);
}
@@ -1,4 +1,3 @@
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/platform/providers/LocalLiveAppProvider/index";
import { useRemoteLiveAppManifest } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index";
import { Flex, Icon, Tag as TagCore, Text } from "@ledgerhq/react-ui";
import React, { useCallback, useEffect, useMemo } from "react";
Expand All @@ -7,6 +6,7 @@ import styled, { DefaultTheme, StyledComponent } from "styled-components";
import { StakeOnClickProps } from "../EthStakingModalBody";
import { StakingIcon } from "../StakingIcon";
import { ListProvider } from "../types";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/wallet-api/LocalLiveAppProvider/index";

export const Container: StyledComponent<
"div",
Expand Down
Expand Up @@ -15,7 +15,6 @@ import {
LiveAppManifestSchemaType,
} from "@ledgerhq/live-common/platform/types";
import Text from "~/renderer/components/Text";
import { useLocalLiveAppContext } from "@ledgerhq/live-common/platform/providers/LocalLiveAppProvider/index";
import Switch from "~/renderer/components/Switch";
import FormLiveAppInput from "./FormLiveAppInput";
import NestedFormCategory from "./NestedFormCategory";
Expand All @@ -32,6 +31,7 @@ import { Separator } from "~/renderer/components/Onboarding/Screens/SelectUseCas
import { DEFAULT_FORM, DEFAULT_VALUES } from "./defaultValues";

import { objectKeysType } from "@ledgerhq/live-common/helpers";
import { useLocalLiveAppContext } from "@ledgerhq/live-common/wallet-api/LocalLiveAppProvider/index";

function createLocalManifest() {
return (
Expand Down
Expand Up @@ -9,9 +9,9 @@ import {
import { useRemoteLiveAppManifest } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index";
import WebPlatformPlayer from "~/renderer/components/WebPlatformPlayer";
import useTheme from "~/renderer/hooks/useTheme";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/platform/providers/LocalLiveAppProvider/index";
import { useDeepLinkListener } from "~/renderer/screens/earn/useDeepLinkListener";
import { useDiscreetMode } from "~/renderer/components/Discreet";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/wallet-api/LocalLiveAppProvider/index";

const DEFAULT_EARN_APP_ID = "earn";

Expand Down
Expand Up @@ -35,9 +35,9 @@ import SwapWebView, { SwapWebProps, useSwapLiveAppManifestID } from "./SwapWebVi
import { SwapMigrationUI } from "./Migrations/SwapMigrationUI";
import { useSwapLiveAppHook } from "~/renderer/hooks/swap-migrations/useSwapLiveAppHook";
import SwapFormSummary from "./FormSummary";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/platform/providers/LocalLiveAppProvider/index";
import { useRemoteLiveAppManifest } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index";
import { languageSelector } from "~/renderer/reducers/settings";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/wallet-api/LocalLiveAppProvider/index";
import { walletSelector } from "~/renderer/reducers/wallet";

const DAPP_PROVIDERS = ["paraswap", "oneinch", "moonpay"];
Expand Down
Expand Up @@ -8,7 +8,6 @@ import { languageSelector } from "~/renderer/reducers/settings";
import { accountsSelector } from "~/renderer/reducers/accounts";
import { useRemoteLiveAppManifest } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index";
import useTheme from "~/renderer/hooks/useTheme";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/platform/providers/LocalLiveAppProvider/index";
import WebPTXPlayer from "~/renderer/components/WebPTXPlayer";
import { getParentAccount, isTokenAccount } from "@ledgerhq/live-common/account/index";
import { LiveAppManifest, Loadable } from "@ledgerhq/live-common/platform/types";
Expand All @@ -20,6 +19,7 @@ import {
} from "@ledgerhq/live-common/wallet-api/constants";
import { useInternalAppIds } from "@ledgerhq/live-common/hooks/useInternalAppIds";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/wallet-api/LocalLiveAppProvider/index";
import { walletSelector } from "~/renderer/reducers/wallet";

export type DProps = {
Expand Down
Expand Up @@ -6,7 +6,7 @@ import WebPlatformPlayer from "~/renderer/components/WebPlatformPlayer";
import { languageSelector } from "~/renderer/reducers/settings";
import { useSelector } from "react-redux";
import { useRemoteLiveAppManifest } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/platform/providers/LocalLiveAppProvider/index";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/wallet-api/LocalLiveAppProvider/index";

type Props = {
match: {
Expand Down
Expand Up @@ -7,16 +7,19 @@ import { Container, Subtitle } from "./Layout";
import { useSelector } from "react-redux";
import { languageSelector } from "~/renderer/reducers/settings";
import { RecentlyUsedManifest } from "@ledgerhq/live-common/wallet-api/react";
import { LiveAppManifest } from "@ledgerhq/live-common/platform/types";

export function MinimumCard(props: PropsCard<RecentlyUsedManifest>) {
export function MinimumCard(props: PropsCard<RecentlyUsedManifest | LiveAppManifest>) {
const { disabled, onClick } = useCard(props);
const { manifest } = props;

const lang = useSelector(languageSelector);
const usedAt = useMemo(() => {
const rtf = new Intl.RelativeTimeFormat(lang);
return rtf.format(-manifest.usedAt.diff, manifest.usedAt.unit);
}, [lang, manifest.usedAt.diff, manifest.usedAt.unit]);
if ("usedAt" in manifest) {
const rtf = new Intl.RelativeTimeFormat(lang);
return rtf.format(-manifest.usedAt.diff, manifest.usedAt.unit);
} else return;
}, [lang, manifest]);

return (
<Container disabled={disabled} onClick={onClick} width={300}>
Expand All @@ -27,7 +30,7 @@ export function MinimumCard(props: PropsCard<RecentlyUsedManifest>) {
<Text overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis" fontSize={14}>
{manifest.name}
</Text>
<Subtitle>{usedAt}</Subtitle>
{usedAt && <Subtitle>{usedAt}</Subtitle>}
</Flex>
</Flex>
</Container>
Expand Down
@@ -0,0 +1,37 @@
import React from "react";
import { Flex } from "@ledgerhq/react-ui";
import { MinimumCard } from "./Card";
import styled from "styled-components";
import { LiveAppManifest } from "@ledgerhq/live-common/platform/types";
import { useHistory } from "react-router";
import { SectionHeader } from "./SectionHeader";
import { useTranslation } from "react-i18next";

export function LocalLiveAppSection({ localLiveApps }: { localLiveApps: LiveAppManifest[] }) {
const history = useHistory();
const { t } = useTranslation();

return (
<Flex flexDirection="column" marginBottom={4}>
<SectionHeader iconLeft="Download">
{t("platform.catalog.section.locallyLoaded")}
</SectionHeader>
<Scroll>
{localLiveApps.map(manifest => (
<Flex key={manifest.id} margin={2}>
<MinimumCard
manifest={manifest}
onClick={(manifest: LiveAppManifest) => history.push(`/platform/${manifest.id}`)}
/>
</Flex>
))}
</Scroll>
</Flex>
);
}

const Scroll = styled(Flex).attrs({ overflowX: "scroll" })`
&::-webkit-scrollbar {
display: none;
}
`;
Expand Up @@ -4,13 +4,15 @@ import { Text, Flex } from "@ledgerhq/react-ui";
import { RecentlyUsed } from "./RecentlyUsed";
import { Browse } from "./Browse";
import { useTranslation } from "react-i18next";
import { useCatalog, useDiscoverDB } from "../hooks";
import { useCatalog, useRecentlyUsedDB } from "../hooks";
import { LocalLiveAppSection } from "./LocalLiveAppSection";

export function Catalog() {
const discoverDB = useDiscoverDB();
const recentlyUsedDB = useRecentlyUsedDB();

const { t } = useTranslation();
const { categories, recentlyUsed, disclaimer, search } = useCatalog(discoverDB);
const { categories, recentlyUsed, disclaimer, search, localLiveApps } =
useCatalog(recentlyUsedDB);

return (
<Flex flexDirection="column" paddingBottom={100}>
Expand All @@ -20,6 +22,8 @@ export function Catalog() {
{t("platform.catalog.title")}
</Text>

{localLiveApps.length ? <LocalLiveAppSection localLiveApps={localLiveApps} /> : null}

{recentlyUsed.data.length ? (
<RecentlyUsed recentlyUsed={recentlyUsed} disclaimer={disclaimer} />
) : null}
Expand Down
Expand Up @@ -21,12 +21,14 @@ import { useCallback, useMemo } from "react";
import { useHistory } from "react-router";
import { closePlatformAppDrawer, openPlatformAppDisclaimerDrawer } from "~/renderer/actions/UI";
import { useManifests } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index";
import { useLocalLiveAppContext } from "@ledgerhq/live-common/wallet-api/LocalLiveAppProvider/index";

export function useCatalog(db: RecentlyUsedDB) {
export function useCatalog(recentlyUsedDB: RecentlyUsedDB) {
const completeManifests = useManifests({ visibility: ["complete"] });
const combinedManifests = useManifests({ visibility: ["searchable", "complete"] });
const categories = useCategories(completeManifests);
const recentlyUsed = useRecentlyUsed(combinedManifests, db);
const recentlyUsed = useRecentlyUsed(combinedManifests, recentlyUsedDB);
const { state: localLiveApps } = useLocalLiveAppContext();

const search = useSearch({
list: combinedManifests,
Expand All @@ -50,11 +52,11 @@ export function useCatalog(db: RecentlyUsedDB) {
recentlyUsed,
disclaimer,
search,
localLiveApps,
};
}

// TODO: rename to useRecentlyUsedDB
export function useDiscoverDB() {
export function useRecentlyUsedDB() {
return useDB("app", DISCOVER_STORE_KEY, INITIAL_PLATFORM_STATE, state => state.recentlyUsed);
}

Expand Down
Expand Up @@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useMemo } from "react";
import { useSelector } from "react-redux";
import { RouteComponentProps, useHistory } from "react-router-dom";
import { useRemoteLiveAppManifest } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/platform/providers/LocalLiveAppProvider/index";
import { useOnboardingStatePolling } from "@ledgerhq/live-common/onboarding/hooks/useOnboardingStatePolling";
import { OnboardingStep } from "@ledgerhq/live-common/hw/extractOnboardingState";
import { counterValueCurrencySelector, languageSelector } from "~/renderer/reducers/settings";
Expand All @@ -12,6 +11,7 @@ import { getCurrentDevice } from "~/renderer/reducers/devices";
import styled from "styled-components";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
import { StaticContext } from "react-router";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/wallet-api/LocalLiveAppProvider/index";

const pollingPeriodMs = 1000;

Expand Down
Expand Up @@ -4,13 +4,13 @@ import React, { useCallback } from "react";
import Button from "~/renderer/components/Button";
import { useTranslation } from "react-i18next";
import { readFile, writeFile } from "fs";
import { useLocalLiveAppContext } from "@ledgerhq/live-common/platform/providers/LocalLiveAppProvider/index";
import { SettingsSectionRow as Row } from "../../SettingsSection";
import { useHistory } from "react-router-dom";
import styled from "styled-components";
import { Flex } from "@ledgerhq/react-ui";
import { useDispatch } from "react-redux";
import { openModal } from "~/renderer/actions/modals";
import { useLocalLiveAppContext } from "@ledgerhq/live-common/wallet-api/LocalLiveAppProvider/index";
import { LiveAppManifest } from "@ledgerhq/live-common/platform/types";

const ButtonContainer = styled.div`
Expand All @@ -23,7 +23,7 @@ const RunLocalAppButton = () => {
const { t } = useTranslation();
const {
addLocalManifest,
state: { liveAppByIndex },
state: localLiveApps,
removeLocalManifestById,
} = useLocalLiveAppContext();

Expand All @@ -41,7 +41,7 @@ const RunLocalAppButton = () => {
})
.then(function (response) {
if (!response.canceled && response.filePath) {
const exportedManifest = liveAppByIndex.find(
const exportedManifest = localLiveApps.find(
(manifest: LiveAppManifest) => manifest.id === id,
);

Expand All @@ -56,7 +56,7 @@ const RunLocalAppButton = () => {
}
});
},
[liveAppByIndex],
[localLiveApps],
);

const onBrowseLocalManifest = useCallback(() => {
Expand Down Expand Up @@ -121,7 +121,7 @@ const RunLocalAppButton = () => {
</Button>
</Flex>
</Row>
{liveAppByIndex.map((manifest: LiveAppManifest) => (
{localLiveApps.map((manifest: LiveAppManifest) => (
<Row key={manifest.id} title={manifest.name} desc={manifest.url as string}>
<ButtonContainer>
<Button small primary onClick={() => history.push(`/platform/${manifest.id}`)}>
Expand Down
Expand Up @@ -5,12 +5,12 @@ import { counterValueCurrencySelector, languageSelector } from "~/renderer/reduc
import { useRemoteLiveAppManifest } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index";
import WebPlatformPlayer from "~/renderer/components/WebPlatformPlayer";
import useTheme from "~/renderer/hooks/useTheme";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/platform/providers/LocalLiveAppProvider/index";
import { useHistory, useLocation } from "react-router-dom";
import { WebviewProps } from "~/renderer/components/Web3AppWebview/types";
import { useDebounce } from "@ledgerhq/live-common/hooks/useDebounce";
import { captureException } from "~/sentry/internal";
import { UnableToLoadSwapLiveError } from "~/renderer/screens/exchange/Swap2/Form/SwapWebView";
import { useLocalLiveAppManifest } from "@ledgerhq/live-common/wallet-api/LocalLiveAppProvider/index";

const DEFAULT_SWAP_APP_ID = "swapWeb";

Expand Down
1 change: 1 addition & 0 deletions apps/ledger-live-desktop/static/i18n/en/app.json
Expand Up @@ -27,6 +27,7 @@
"title": "Discover",
"section": {
"recentlyUsed": "Recently used",
"locallyLoaded": "Locally Loaded Live Apps",
"browse": "Browse"
},
"filter": {
Expand Down
19 changes: 17 additions & 2 deletions apps/ledger-live-mobile/src/PlatformAppProviderWrapper.tsx
@@ -1,11 +1,17 @@
import React, { ReactNode } from "react";
import VersionNumber from "react-native-version-number";
import { RemoteLiveAppProvider } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index";
import { LocalLiveAppProvider } from "@ledgerhq/live-common/platform/providers/LocalLiveAppProvider/index";
import { LocalLiveAppProvider } from "@ledgerhq/live-common/wallet-api/LocalLiveAppProvider/index";
import { RampCatalogProvider } from "@ledgerhq/live-common/platform/providers/RampCatalogProvider/index";
import useEnv from "@ledgerhq/live-common/hooks/useEnv";
import { Platform } from "react-native";
import { useLocale } from "~/context/Locale";
import {
DISCOVER_STORE_KEY,
INITIAL_PLATFORM_STATE,
} from "@ledgerhq/live-common/wallet-api/constants";
import { DiscoverDB } from "@ledgerhq/live-common/wallet-api/types";
import { useDB } from "./db";

type PlatformAppProviderWrapperProps = {
children: ReactNode;
Expand All @@ -20,6 +26,7 @@ export default function PlatformAppProviderWrapper({ children }: PlatformAppProv
) as boolean;
const { locale: lang } = useLocale();
const isDebugAppEnabled = useEnv<"PLATFORM_DEBUG">("PLATFORM_DEBUG") as boolean;
const localLiveAppDB = useLocalLiveAppDB();

return (
<RemoteLiveAppProvider
Expand All @@ -32,11 +39,19 @@ export default function PlatformAppProviderWrapper({ children }: PlatformAppProv
lang,
}}
>
<LocalLiveAppProvider>
<LocalLiveAppProvider db={localLiveAppDB}>
<RampCatalogProvider updateFrequency={AUTO_UPDATE_DEFAULT_DELAY}>
{children}
</RampCatalogProvider>
</LocalLiveAppProvider>
</RemoteLiveAppProvider>
);
}

function useLocalLiveAppDB() {
return useDB<DiscoverDB, DiscoverDB["localLiveApp"]>(
DISCOVER_STORE_KEY,
INITIAL_PLATFORM_STATE,
state => state.localLiveApp || INITIAL_PLATFORM_STATE.localLiveApp,
);
}