Skip to content

Commit

Permalink
Merge pull request #5699 from LedgerHQ/feat/llm-flexible-content-cards
Browse files Browse the repository at this point in the history
Feat/llm flexible content cards
  • Loading branch information
cgrellard-ledger authored Dec 22, 2023
2 parents 241cdac + 2486496 commit 973275e
Show file tree
Hide file tree
Showing 41 changed files with 1,307 additions and 221 deletions.
21 changes: 16 additions & 5 deletions apps/ledger-live-mobile/.unimportedrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
{
"entry": ["index.js"],
"extensions": [".ts", ".js", ".jsx", ".tsx", ".d.ts"],
"ignorePatterns": ["**/node_modules/**"],
"entry": [
"index.js"
],
"extensions": [
".ts",
".js",
".jsx",
".tsx",
".d.ts"
],
"ignorePatterns": [
"**/node_modules/**"
],
"ignoreUnresolved": [],
"ignoreUnimported": [
"**/*.test.*",
Expand All @@ -13,7 +23,8 @@
"src/**/*.android.*",
"src/**/*.ios.*",
"src/components/RootNavigator/types.ts",
"src/logic/keyboardVisible.ts"
"src/logic/keyboardVisible.ts",
"src/contentCards/cards/vertical/*"
],
"ignoreUnused": [
"@ledgerhq/react-native-passcode-auth",
Expand All @@ -35,4 +46,4 @@
"react-native-tcp-socket",
"react-native-udp"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -797,8 +797,14 @@
);
MTL_ENABLE_DEBUG_INFO = NO;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited) ";
OTHER_CPLUSPLUSFLAGS = "$(inherited) ";
OTHER_CFLAGS = (
"$(inherited)",
" ",
);
OTHER_CPLUSPLUSFLAGS = (
"$(inherited)",
" ",
);
OTHER_LDFLAGS = "$(inherited)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
Expand Down Expand Up @@ -918,8 +924,14 @@
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited) ";
OTHER_CPLUSPLUSFLAGS = "$(inherited) ";
OTHER_CFLAGS = (
"$(inherited)",
" ",
);
OTHER_CPLUSPLUSFLAGS = (
"$(inherited)",
" ",
);
OTHER_LDFLAGS = "$(inherited)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
Expand Down Expand Up @@ -983,8 +995,14 @@
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = "$(inherited) ";
OTHER_CPLUSPLUSFLAGS = "$(inherited) ";
OTHER_CFLAGS = (
"$(inherited)",
" ",
);
OTHER_CPLUSPLUSFLAGS = (
"$(inherited)",
" ",
);
OTHER_LDFLAGS = "$(inherited)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
Expand Down
19 changes: 19 additions & 0 deletions apps/ledger-live-mobile/src/actions/dynamicContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import {
AssetContentCard,
LearnContentCard,
NotificationContentCard,
CategoryContentCard,
BrazeContentCard,
} from "../dynamicContent/types";
import {
DynamicContentActionTypes,
DynamicContentSetWalletCardsPayload,
DynamicContentSetAssetCardsPayload,
DynamicContentSetLearnCardsPayload,
DynamicContentSetNotificationCardsPayload,
DynamicContentSetCategoriesCardsPayload,
DynamicContentSetMobileCardsPayload,
} from "./types";

const setDynamicContentWalletCardsAction = createAction<DynamicContentSetWalletCardsPayload>(
Expand All @@ -34,6 +38,21 @@ const setDynamicContentLearnCardsAction = createAction<DynamicContentSetLearnCar
export const setDynamicContentLearnCards = (learnCards: LearnContentCard[]) =>
setDynamicContentLearnCardsAction(learnCards);

const setDynamicContentCategoriesCardsAction =
createAction<DynamicContentSetCategoriesCardsPayload>(
DynamicContentActionTypes.DYNAMIC_CONTENT_SET_CATEGORIES_CARDS,
);

export const setDynamicContentMobileCards = (mobileCards: BrazeContentCard[]) =>
setDynamicContentMobileCardsAction(mobileCards);

const setDynamicContentMobileCardsAction = createAction<DynamicContentSetMobileCardsPayload>(
DynamicContentActionTypes.DYNAMIC_CONTENT_SET_MOBILE_CARDS,
);

export const setDynamicContentCategoriesCards = (categoriesCards: CategoryContentCard[]) =>
setDynamicContentCategoriesCardsAction(categoriesCards);

const setDynamicContentNotificationCardsAction =
createAction<DynamicContentSetNotificationCardsPayload>(
DynamicContentActionTypes.DYNAMIC_CONTENT_SET_NOTIFICATION_CARDS,
Expand Down
10 changes: 9 additions & 1 deletion apps/ledger-live-mobile/src/actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ export enum DynamicContentActionTypes {
DYNAMIC_CONTENT_SET_ASSET_CARDS = "DYNAMIC_CONTENT_SET_ASSET_CARDS",
DYNAMIC_CONTENT_SET_LEARN_CARDS = "DYNAMIC_CONTENT_SET_LEARN_CARDS",
DYNAMIC_CONTENT_SET_NOTIFICATION_CARDS = "DYNAMIC_CONTENT_SET_NOTIFICATION_CARDS",
DYNAMIC_CONTENT_SET_CATEGORIES_CARDS = "DYNAMIC_CONTENT_SET_CATEGORIES_CARDS",
DYNAMIC_CONTENT_SET_MOBILE_CARDS = "DYNAMIC_CONTENT_SET_MOBILE_CARDS",
}

export type DynamicContentSetWalletCardsPayload = DynamicContentState["walletCards"];
Expand All @@ -179,11 +181,17 @@ export type DynamicContentSetLearnCardsPayload = DynamicContentState["learnCards

export type DynamicContentSetNotificationCardsPayload = DynamicContentState["notificationCards"];

export type DynamicContentSetCategoriesCardsPayload = DynamicContentState["categoriesCards"];

export type DynamicContentSetMobileCardsPayload = DynamicContentState["mobileCards"];

export type DynamicContentPayload =
| DynamicContentSetWalletCardsPayload
| DynamicContentSetAssetCardsPayload
| DynamicContentSetLearnCardsPayload
| DynamicContentSetNotificationCardsPayload;
| DynamicContentSetNotificationCardsPayload
| DynamicContentSetCategoriesCardsPayload
| DynamicContentSetMobileCardsPayload;

// === RATINGS ACTIONS ===

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Linking } from "react-native";
import { Flex, FullBackgroundCard } from "@ledgerhq/native-ui";
import { useTheme } from "styled-components/native";
import { WalletContentCard } from "~/dynamicContent/types";
import useDynamicContent from "~/dynamicContent/dynamicContent";
import useDynamicContent from "~/dynamicContent/useDynamicContent";
import ForceTheme from "../theme/ForceTheme";

type CarouselCardProps = {
Expand Down
2 changes: 1 addition & 1 deletion apps/ledger-live-mobile/src/components/Carousel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { memo, useMemo, useCallback, useRef, useState } from "react";
import { NativeScrollEvent, NativeSyntheticEvent, ScrollView } from "react-native";
import useDynamicContent from "~/dynamicContent/dynamicContent";
import useDynamicContent from "~/dynamicContent/useDynamicContent";
import { width } from "~/helpers/normalizeSize";
import CarouselCard from "./CarouselCard";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Learn from "~/screens/Learn/learn";
import ExploreTabNavigatorTabBar from "../ExploreTab/ExploreTabNavigatorTabBar";
import ExploreTabNavigatorTabBarDisabled from "../ExploreTab/ExploreTabNavigatorTabBarDisabled";
import { useIsNewsfeedAvailable } from "~/hooks/newsfeed/useIsNewsfeedAvailable";
import useDynamicContent from "~/dynamicContent/dynamicContent";
import useDynamicContent from "~/dynamicContent/useDynamicContent";
import { NavigationHeaderBackButton } from "../NavigationHeaderBackButton";

const ExploreTab = createMaterialTopTabNavigator<ExploreTabNavigatorStackParamList>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Flex, Icons, Text } from "@ledgerhq/native-ui";
import React from "react";
import { Image as NativeImage, Pressable } from "react-native";
import { useTheme } from "styled-components/native";
import { ButtonAction } from "~/contentCards/cards/types";

export const Image = ({ uri }: { uri: string }) => {
return (
<Flex width={40} height={40}>
<NativeImage source={{ uri }} borderRadius={9999} style={{ width: "100%", height: "100%" }} />
</Flex>
);
};

export const Close = ({ onPress }: { onPress: ButtonAction }) => {
return (
<Pressable onPress={onPress} hitSlop={11}>
<Icons.Close size="XS" />
</Pressable>
);
};

type LabelProps = {
label: string;
};

export const Tag = ({ label }: LabelProps) => {
const { colors } = useTheme();

return (
<Flex bg={colors.primary.c80} borderRadius={"4px"} height={"18px"} justifyContent="center">
<Text variant="small" fontWeight="bold" px={"6px"} color={colors.neutral.c00}>
{label}
</Text>
</Flex>
);
};

export const Title = ({ label }: LabelProps) => {
return (
<Text variant="body" fontWeight="medium" numberOfLines={1}>
{label}
</Text>
);
};

export const Subtitle = ({ label }: LabelProps) => {
const { colors } = useTheme();

return (
<Text variant="paragraph" fontWeight="medium" color={colors.neutral.c70} numberOfLines={1}>
{label}
</Text>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Flex } from "@ledgerhq/native-ui";
import React, { useEffect } from "react";
import { TouchableOpacity } from "react-native";
import { useTheme } from "styled-components/native";
import { Close, Image, Subtitle, Tag, Title } from "~/contentCards/cards/horizontal/elements";
import { ContentCardBuilder } from "~/contentCards/cards/utils";

type Props = {
title: string;
description: string;
image: string;
tag?: string;
};

const HorizontalCard = ContentCardBuilder<Props>(({ title, description, image, tag, metadata }) => {
const { colors, space } = useTheme();

const isDismissable = !!metadata.actions?.onDismiss;
const isTag = !!tag;

useEffect(() => metadata.actions?.onView?.());

return (
<TouchableOpacity onPress={metadata.actions?.onClick} key={metadata.id}>
<Flex
bg={colors.opacityDefault.c05}
p="13px"
borderRadius="12px"
flexDirection="row"
justifyContent="space-between"
alignItems="center"
columnGap={13}
>
{image ? <Image uri={image} /> : null}

<Flex flex={1} rowGap={space[2]}>
<Flex flexDirection="row" justifyContent="space-between" columnGap={space[3]}>
<Flex overflow={"hidden"} flex={1}>
<Title label={title} />
</Flex>

<Flex alignSelf="center" height="16px">
{isDismissable ? (
<Close onPress={metadata.actions?.onDismiss} />
) : (
isTag && <Tag label={tag} />
)}
</Flex>
</Flex>
{description ? <Subtitle label={description} /> : null}
</Flex>
</Flex>
</TouchableOpacity>
);
});

export default HorizontalCard;
26 changes: 26 additions & 0 deletions apps/ledger-live-mobile/src/contentCards/cards/types.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ComponentProps } from "react";
import { TouchableOpacity } from "react-native-gesture-handler";

export type ButtonAction = ComponentProps<typeof TouchableOpacity>["onPress"];

export type ContentCardProps = {
metadata: ContentCardMetadata;
};

export type ContentCardMetadata = {
id: string;

actions?: {
onView?: () => void;
onClick?: ButtonAction;
onDismiss?: ButtonAction;
};
};

/**
* Defines a content card item.
*/
export interface ContentCardItem<P extends ContentCardProps = ContentCardProps> {
component: React.FC<P & ContentCardProps>;
props: P & ContentCardProps;
}
19 changes: 19 additions & 0 deletions apps/ledger-live-mobile/src/contentCards/cards/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";
import { ContentCardProps, ContentCardItem } from "~/contentCards/cards/types";

export const ContentCardBuilder =
<P extends object>(ContentCardComponent: React.FC<P & ContentCardProps>) =>
(props: P & ContentCardProps) => <ContentCardComponent {...props} />;

/**
* Util function to create a content card item with proper typings that will be used in a layout.
*/
export const contentCardItem = <P extends ContentCardProps>(
component: React.FC<P>,
props: ContentCardItem<P>["props"],
) => {
return {
component,
props,
} as ContentCardItem<P>;
};
Loading

1 comment on commit 973275e

@vercel
Copy link

@vercel vercel bot commented on 973275e Dec 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.