Skip to content

Commit

Permalink
Merge pull request #8976 from LedgerHQ/feat/LIVE-16502
Browse files Browse the repository at this point in the history
💄 (llm) animation enhancement tab switch
  • Loading branch information
LucasWerey authored Feb 3, 2025
2 parents 91e43a6 + 82e7d13 commit 90bf12e
Show file tree
Hide file tree
Showing 14 changed files with 499 additions and 157 deletions.
5 changes: 5 additions & 0 deletions .changeset/dull-dots-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"live-mobile": minor
---

Enhance switch tab animation on portfolioassets for the assets and accounts tabs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ const ESTIMED_ITEM_SIZE = 150;

type ViewProps = ReturnType<typeof useAccountsListViewModel>;

const View: React.FC<ViewProps> = ({ accountsToDisplay, isSyncEnabled, onAccountPress }) => {
const View: React.FC<ViewProps> = ({
accountsToDisplay,
isSyncEnabled,
onAccountPress,
onContentChange,
}) => {
const List = useMemo(() => {
return isSyncEnabled
? globalSyncRefreshControl<FlashListProps<Account | TokenAccount>>(FlashList)
Expand Down Expand Up @@ -45,6 +50,7 @@ const View: React.FC<ViewProps> = ({ accountsToDisplay, isSyncEnabled, onAccount
data={accountsToDisplay}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
onContentSizeChange={onContentChange}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface Props {
isSyncEnabled?: boolean;
limitNumberOfAccounts?: number;
specificAccounts?: Account[] | TokenAccount[];
onContentChange?: (width: number, height: number) => void;
}

export type NavigationProp = BaseNavigationComposite<
Expand All @@ -37,6 +38,7 @@ const useAccountsListViewModel = ({
isSyncEnabled = false,
limitNumberOfAccounts,
specificAccounts,
onContentChange,
}: Props) => {
const startNavigationTTITimer = useStartProfiler();
const navigation = useNavigation<NavigationProp>();
Expand Down Expand Up @@ -90,6 +92,7 @@ const useAccountsListViewModel = ({
accountsToDisplay,
onAccountPress,
isSyncEnabled,
onContentChange,
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const AssetItem: React.FC<AssetItemProps> = ({ asset, balance }) => {

return (
<>
<ParentCurrencyIcon currency={currency} size={40} forceIconScale={1.2} />
<ParentCurrencyIcon currency={currency} size={40} forceIconScale={2} />
<Flex flex={1} flexShrink={1} testID={`assetItem-${currency.name}`}>
<Text
numberOfLines={1}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ const ESTIMED_ITEM_SIZE = 150;

type ViewProps = ReturnType<typeof useAssetsListViewModel>;

const View: React.FC<ViewProps> = ({ assetsToDisplay, onItemPress, isSyncEnabled }) => {
const View: React.FC<ViewProps> = ({
assetsToDisplay,
onItemPress,
onContentChange,
isSyncEnabled,
}) => {
const List = useMemo(() => {
return isSyncEnabled ? globalSyncRefreshControl<FlashListProps<Asset>>(FlashList) : FlashList;
}, [isSyncEnabled]);
Expand Down Expand Up @@ -46,6 +51,7 @@ const View: React.FC<ViewProps> = ({ assetsToDisplay, onItemPress, isSyncEnabled
data={assetsToDisplay}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
onContentSizeChange={onContentChange}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface Props {
sourceScreenName?: ScreenName;
isSyncEnabled?: boolean;
limitNumberOfAssets?: number;
onContentChange?: (width: number, height: number) => void;
}

export type NavigationProp = BaseNavigationComposite<
Expand All @@ -30,6 +31,7 @@ const useAssetsListViewModel = ({
sourceScreenName,
isSyncEnabled = false,
limitNumberOfAssets,
onContentChange,
}: Props) => {
const hideEmptyTokenAccount = useEnv("HIDE_EMPTY_TOKEN_ACCOUNTS");
const navigation = useNavigation<NavigationProp>();
Expand Down Expand Up @@ -86,6 +88,7 @@ const useAssetsListViewModel = ({
assetsToDisplay,
isSyncEnabled,
onItemPress,
onContentChange,
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { useCallback } from "react";
import Animated, {
Easing,
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import { Box } from "@ledgerhq/native-ui";

export interface AnimatedContainerProps {
children: React.ReactNode;
onHeightChange?: (height: number) => void;
}

const ANIMATION_TIMING_CONFIG = {
duration: 200,
easing: Easing.bezier(0.3, 0, 0, 1),
} as const;

export const AnimatedContainer = ({ children, onHeightChange }: AnimatedContainerProps) => {
const animatedHeight = useSharedValue(1);

const style = useAnimatedStyle(() => ({
height: withTiming(animatedHeight.value, ANIMATION_TIMING_CONFIG),
overflow: "hidden",
}));

const onLayout = useCallback(
(event: { nativeEvent: { layout: { height: number } } }) => {
const height = event.nativeEvent.layout.height;
if (height === 0) return;

animatedHeight.value = height;
onHeightChange?.(height);
},
[animatedHeight, onHeightChange],
);

return (
<Animated.View style={style}>
<Box onLayout={onLayout}>{children}</Box>
</Animated.View>
);
};

export default AnimatedContainer;
61 changes: 37 additions & 24 deletions apps/ledger-live-mobile/src/screens/Portfolio/PortfolioAssets.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux";
import { shallowEqual, useSelector } from "react-redux";
import { useStartProfiler } from "@shopify/react-native-performance";
import { GestureResponderEvent } from "react-native";
import { useNavigation } from "@react-navigation/native";
Expand All @@ -12,9 +12,9 @@ import { blacklistedTokenIdsSelector, discreetModeSelector } from "~/reducers/se
import Assets from "./Assets";
import PortfolioQuickActionsBar from "./PortfolioQuickActionsBar";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
import AddAccountButton from "LLM/features/Accounts/components/AddAccountButton";
import useListsAnimation from "./useListsAnimation";
import TabSection, { TAB_OPTIONS } from "./TabSection";
import { accountsSelector } from "~/reducers/accounts";

type Props = {
hideEmptyTokenAccount: boolean;
Expand All @@ -28,6 +28,7 @@ const PortfolioAssets = ({ hideEmptyTokenAccount, openAddModal }: Props) => {
const accountListFF = useFeature("llmAccountListUI");
const isAccountListUIEnabled = accountListFF?.enabled;
const navigation = useNavigation();
const allAccounts = useSelector(accountsSelector, shallowEqual);
const startNavigationTTITimer = useStartProfiler();
const distribution = useDistribution({
showEmptyAccounts: true,
Expand All @@ -51,8 +52,17 @@ const PortfolioAssets = ({ hideEmptyTokenAccount, openAddModal }: Props) => {
[distribution, blacklistedTokenIdsSet],
);

const { selectedTab, handleToggle, handleLayout, assetsAnimatedStyle, accountsAnimatedStyle } =
useListsAnimation(TAB_OPTIONS.Assets);
const {
handleToggle,
handleLayout,
handleButtonLayout,
handleAccountsContentSizeChange,
handleAssetsContentSizeChange,
selectedTab,
assetsAnimatedStyle,
containerHeight,
accountsAnimatedStyle,
} = useListsAnimation(TAB_OPTIONS.Assets);

const showAssets = selectedTab === TAB_OPTIONS.Assets;
const showAccounts = selectedTab === TAB_OPTIONS.Accounts;
Expand Down Expand Up @@ -94,8 +104,6 @@ const PortfolioAssets = ({ hideEmptyTokenAccount, openAddModal }: Props) => {
[startNavigationTTITimer, showAssets, isAccountListUIEnabled, navigation],
);

const showAddAccountButton = isAccountListUIEnabled && showAccounts;

return (
<>
<TrackScreen
Expand All @@ -111,33 +119,38 @@ const PortfolioAssets = ({ hideEmptyTokenAccount, openAddModal }: Props) => {
t={t}
handleToggle={handleToggle}
handleLayout={handleLayout}
handleButtonLayout={handleButtonLayout}
handleAssetsContentSizeChange={handleAssetsContentSizeChange}
handleAccountsContentSizeChange={handleAccountsContentSizeChange}
onPressButton={onPressButton}
showAssets={showAssets}
showAccounts={showAccounts}
assetsLength={assetsToDisplay.length}
accountsLength={allAccounts.length}
assetsAnimatedStyle={assetsAnimatedStyle}
accountsAnimatedStyle={accountsAnimatedStyle}
maxItemsToDysplay={maxItemsToDysplay}
containerHeight={containerHeight}
/>
) : (
<Assets assets={assetsToDisplay} />
)}
{showAddAccountButton ? (
<AddAccountButton sourceScreenName="Wallet" />
) : (
distribution.list.length === 0 && (
<Button
type="shade"
size="large"
outline
mt={6}
iconPosition="left"
Icon={IconsLegacy.PlusMedium}
onPress={openAddModal}
>
{t("account.emptyState.addAccountCta")}
</Button>
)
{!isAccountListUIEnabled && distribution.list.length === 0 && (
<Button
type="shade"
size="large"
outline
mt={6}
iconPosition="left"
Icon={IconsLegacy.PlusMedium}
onPress={openAddModal}
>
{t("account.emptyState.addAccountCta")}
</Button>
)}
{distribution.list.length >= maxItemsToDysplay && (
{!isAccountListUIEnabled && distribution.list.length >= maxItemsToDysplay && (
<Button type="shade" size="large" outline onPress={onPressButton}>
{showAssets ? t("portfolio.seeAllAssets") : t("portfolio.seeAllAccounts")}
{t("portfolio.seeAllAssets")}
</Button>
)}
</>
Expand Down
56 changes: 54 additions & 2 deletions apps/ledger-live-mobile/src/screens/Portfolio/TabSection.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import React, { memo } from "react";
import { Box, Flex } from "@ledgerhq/native-ui";
import { Box, Button, Flex } from "@ledgerhq/native-ui";
import Animated from "react-native-reanimated";
import { TabSelector } from "@ledgerhq/native-ui";
import AssetsListView from "LLM/features/Assets/components/AssetsListView";
import AccountsListView from "LLM/features/Accounts/components/AccountsListView";
import { ScreenName } from "~/const";
import { LayoutChangeEvent } from "react-native";
import {
GestureResponderEvent,
LayoutChangeEvent,
} from "react-native/Libraries/Types/CoreEventTypes";
import AddAccountButton from "LLM/features/Accounts/components/AddAccountButton";

export const TAB_OPTIONS = {
Assets: "Assets",
Expand All @@ -19,21 +23,41 @@ type BaseAnimationStyle = {
opacity: number;
};

type TabSectionType = (typeof TAB_OPTIONS)[keyof typeof TAB_OPTIONS];

type TabSectionProps = {
t: (key: string) => string;
handleToggle: (value: string) => void;
handleLayout: (event: LayoutChangeEvent) => void;
handleAssetsContentSizeChange: (width: number, height: number) => void;
handleAccountsContentSizeChange: (width: number, height: number) => void;
handleButtonLayout: (tab: TabSectionType, event: LayoutChangeEvent) => void;
onPressButton: (uiEvent: GestureResponderEvent) => void;
showAssets: boolean;
assetsLength: number;
showAccounts: boolean;
accountsLength: number;
assetsAnimatedStyle: BaseAnimationStyle;
accountsAnimatedStyle: BaseAnimationStyle;
containerHeight: number;
maxItemsToDysplay: number;
};

const TabSection: React.FC<TabSectionProps> = ({
t,
handleToggle,
handleLayout,
handleAssetsContentSizeChange,
handleAccountsContentSizeChange,
handleButtonLayout,
onPressButton,
showAssets,
assetsLength,
showAccounts,
accountsLength,
assetsAnimatedStyle,
accountsAnimatedStyle,
containerHeight,
maxItemsToDysplay,
}) => (
<>
Expand All @@ -52,18 +76,46 @@ const TabSection: React.FC<TabSectionProps> = ({
onLayout={handleLayout}
width="200%"
testID="portfolio-assets-layout"
height={containerHeight}
maxHeight={containerHeight}
overflowY="hidden"
>
<Animated.View style={[{ flex: 1 }, assetsAnimatedStyle]}>
<AssetsListView
sourceScreenName={ScreenName.Portfolio}
limitNumberOfAssets={maxItemsToDysplay}
onContentChange={handleAssetsContentSizeChange}
/>
{assetsLength >= maxItemsToDysplay && showAssets && (
<Box
onLayout={event => handleButtonLayout(TAB_OPTIONS.Assets, event)}
testID="assets-button"
>
<Button type="shade" size="large" outline onPress={onPressButton}>
{t("portfolio.seeAllAssets")}
</Button>
</Box>
)}
</Animated.View>
<Animated.View style={[{ flex: 1 }, accountsAnimatedStyle]}>
<AccountsListView
sourceScreenName={ScreenName.Portfolio}
limitNumberOfAccounts={maxItemsToDysplay}
onContentChange={handleAccountsContentSizeChange}
/>
{showAccounts && (
<Box
onLayout={event => handleButtonLayout(TAB_OPTIONS.Accounts, event)}
testID="accounts-button"
>
<AddAccountButton sourceScreenName="Wallet" />
{accountsLength >= maxItemsToDysplay && (
<Button type="shade" size="large" outline onPress={onPressButton}>
{t("portfolio.seeAllAccounts")}
</Button>
)}
</Box>
)}
</Animated.View>
</Flex>
</>
Expand Down
Loading

0 comments on commit 90bf12e

Please sign in to comment.