Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/(protected)/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function TabLayout() {
return (
<Tabs
screenOptions={{
animation: Platform.OS !== 'web' ? 'shift' : 'none',
animation: 'none',
freezeOnBlur: Platform.OS !== 'web',
sceneStyle: { backgroundColor: '#121212' },
tabBarActiveTintColor: 'white',
Expand Down
37 changes: 20 additions & 17 deletions app/(protected)/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ export default function Home() {
const updateUser = useUserStore(state => state.updateUser);
const openSpinWinModal = useSpinWinModalStore(state => state.setModal);
const intercom = useIntercom();
const { data: cardStatus } = useCardStatus();
const { data: cardDetails } = useCardDetails();
const { data: cardStatus, isLoading: isCardStatusLoading } = useCardStatus();
const { data: cardDetails, isLoading: isCardDetailsLoading } = useCardDetails();
const { data: spinStatus } = useSpinStatus();
const { data: giveaway } = useCurrentGiveaway();
const countdown = useGiveawayCountdown(giveaway?.giveawayDate);
Expand Down Expand Up @@ -88,8 +88,9 @@ export default function Home() {
hasTriggeredInitialRefresh.current = false;
}, [user?.safeAddress]);

const { data: userDepositTransactions, isLoading: isDepositsLoading } =
useUserTransactions(user?.safeAddress);
const { data: userDepositTransactions, isLoading: isDepositsLoading } = useUserTransactions(
user?.safeAddress,
);

const { data: totalSavingsUSD, isLoading: isTotalSavingsLoading } = useTotalSavingsUSD();

Expand Down Expand Up @@ -117,6 +118,14 @@ export default function Home() {
}, [user, intercom]);

const isInitialLoading = isBalanceLoading || isLoadingTokens || isDepositsLoading;
const isCardBalanceLoading = isCardStatusLoading || (userHasCard && isCardDetailsLoading);
const isHeadlineLoading =
isLoadingTokens ||
isBalanceLoading ||
isTotalSavingsLoading ||
isCardBalanceLoading ||
totalSavingsUSD === undefined;
const headlineBalance = totalUSDExcludingVaultTokens + (totalSavingsUSD ?? 0) + cardBalance;

if (!isInitialLoading && !balance && !isDeposited && !hasTokens) {
return <HomeEmptyState />;
Expand All @@ -132,17 +141,15 @@ export default function Home() {
<View className="flex-row items-center justify-between">
<View className="flex-row items-center gap-2">
<View className="flex-row items-center gap-2">
{isLoadingTokens ||
isBalanceLoading ||
isTotalSavingsLoading ||
totalSavingsUSD === undefined ? (
{isHeadlineLoading ? (
<Skeleton className="h-[4.5rem] w-56 rounded-xl" />
) : (
<CountUp
prefix="$"
count={totalUSDExcludingVaultTokens + (totalSavingsUSD ?? 0) + cardBalance}
count={headlineBalance}
isTrailingZero={false}
decimalPlaces={2}
animateOnMount={false}
classNames={{
wrapper: 'text-foreground',
decimalSeparator: 'text-2xl',
Expand All @@ -168,10 +175,7 @@ export default function Home() {
</View>
<DashboardHeaderButtons hideWithdraw />
</View>
) : isLoadingTokens ||
isBalanceLoading ||
isTotalSavingsLoading ||
totalSavingsUSD === undefined ? (
) : isHeadlineLoading ? (
<View className="items-center pt-6">
<Skeleton className="h-16 w-48 rounded-xl" />
<View className="mt-8 flex-row justify-center gap-6">
Expand All @@ -181,16 +185,14 @@ export default function Home() {
</View>
</View>
) : (
<DashboardHeaderMobile
balance={totalUSDExcludingVaultTokens + (totalSavingsUSD ?? 0) + cardBalance}
mode={SavingMode.BALANCE_ONLY}
/>
<DashboardHeaderMobile balance={headlineBalance} mode={SavingMode.BALANCE_ONLY} />
)}
{isScreenMedium ? (
<DesktopCards
totalUSDExcludingVaultTokens={totalUSDExcludingVaultTokens}
topThreeTokens={topThreeTokens}
isLoadingTokens={isLoadingTokens}
isLoadingCard={isCardBalanceLoading}
userHasCard={userHasCard}
cardBalance={cardBalance}
/>
Expand All @@ -199,6 +201,7 @@ export default function Home() {
totalUSDExcludingVaultTokens={totalUSDExcludingVaultTokens}
topThreeTokens={topThreeTokens}
isLoadingTokens={isLoadingTokens}
isLoadingCard={isCardBalanceLoading}
userHasCard={userHasCard}
cardBalance={cardBalance}
/>
Expand Down
5 changes: 3 additions & 2 deletions app/(protected)/(tabs)/savings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export default function Savings() {
const projectedEarnings =
balance && vaultAPY ? balance * (exchangeRate ?? 1) * (vaultAPY / 100) : 0;

const stickyHeader = (
const pageHeader = (
<View className="mx-auto w-full max-w-7xl px-4 pb-[40px] pt-6 md:pb-7 md:pt-12">
{isScreenMedium ? (
<View className="flex-row items-center justify-between">
Expand Down Expand Up @@ -180,7 +180,8 @@ export default function Savings() {
);

return (
<PageLayout isLoading={isInitialLoading} stickyHeader={stickyHeader}>
<PageLayout isLoading={isInitialLoading}>
{pageHeader}
<View className="mx-auto w-full max-w-7xl gap-10 px-4 pb-8 md:gap-7 md:pb-12">
{isScreenMedium ? (
<View className="flex-row gap-4">
Expand Down
4 changes: 2 additions & 2 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -320,14 +320,14 @@ export default Sentry.wrap(function RootLayout() {
}

return (
<SafeAreaProvider onLayout={onLayoutRootView}>
<SafeAreaProvider style={{ flex: 1 }} onLayout={onLayoutRootView}>
<TurnkeyProvider>
<LazyThirdwebProvider>
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<ApolloProvider client={getInfoClient()}>
<Intercom>
<GestureHandlerRootView>
<GestureHandlerRootView style={{ flex: 1 }}>
<BottomSheetModalProvider>
{Platform.OS === 'web' && (
<Head>
Expand Down
2 changes: 1 addition & 1 deletion app/welcome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export default function Welcome() {
</View>

{/* Subtitle */}
<Text className="native:text-lg mb-8 px-4 text-center text-base font-normal leading-tight text-white/60">
<Text className="mb-8 px-4 text-center text-base font-normal leading-tight text-white/60">
Please select your account to continue, you will be asked to login with your passkey.
</Text>

Expand Down
4 changes: 2 additions & 2 deletions components/Card/BorrowPositionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export function BorrowPositionCard({
)}
<View className="flex-row items-center gap-1">
<Text className="text-base font-medium text-white/70">Net APY earned</Text>
<View className="mt-1">
<View>
<TooltipPopover
text="This is the yield you will earn on your borrowed savings balance"
analyticsContext="borrow_position_net_apy"
Expand Down Expand Up @@ -225,7 +225,7 @@ export function BorrowPositionCard({
)}
<View className="flex-row items-center gap-1">
<Text className="text-base font-medium text-white/70">Net APY earned</Text>
<View className="mt-1">
<View>
<TooltipPopover
text="This is the yield you will earn on your borrowed savings balance"
analyticsContext="borrow_position_net_apy"
Expand Down
76 changes: 60 additions & 16 deletions components/CountUp.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Platform, TextStyle, View } from 'react-native';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Platform, StyleSheet, TextStyle, View } from 'react-native';
import { AnimatedRollingNumber } from 'react-native-animated-rolling-numbers';

import { Text } from '@/components/ui/text';
Expand Down Expand Up @@ -34,6 +34,8 @@ interface CountUpProps {
isTrailingZero?: boolean;
prefix?: string | React.ReactNode;
suffix?: string;
animated?: boolean;
animateOnMount?: boolean;
}

const DURATION = 500;
Expand All @@ -46,22 +48,36 @@ const CountUp = ({
isTrailingZero = true,
prefix,
suffix,
animated = true,
animateOnMount = true,
}: CountUpProps) => {
const [isMounted, setIsMounted] = useState(false);
// On Android, delay showing AnimatedRollingNumber so we don't paint it with height=0
// (stacked digits → white blob). Show static text for one frame then switch.
const [useRolling, setUseRolling] = useState(Platform.OS !== 'android');
const [canAnimateAfterMount, setCanAnimateAfterMount] = useState(animateOnMount);
const [hasSwitchedToRolling, setHasSwitchedToRolling] = useState(animateOnMount);
// On Android, delay arming AnimatedRollingNumber so we don't paint it with height=0
// (stacked digits → white blob).
const [useRolling, setUseRolling] = useState(
animated && animateOnMount && Platform.OS !== 'android',
);
useEffect(() => {
setIsMounted(true);
}, []);
useEffect(() => {
if (Platform.OS === 'android' && isMounted) {
if (!animated) {
setUseRolling(false);
return;
}
if (Platform.OS !== 'android') {
setUseRolling(true);
return;
}
if (isMounted) {
const t = requestAnimationFrame(() => {
requestAnimationFrame(() => setUseRolling(true));
});
return () => cancelAnimationFrame(t);
}
}, [isMounted]);
}, [animated, isMounted]);

const safeCount = isFinite(count) && count >= 0 ? count : 0;
const wholeNumber = Math.floor(safeCount);
Expand All @@ -71,6 +87,8 @@ const CountUp = ({
const formattedText = isNaN(Number(trailingZero)) ? '0' : trailingZero;

const formattedWhole = wholeNumber.toLocaleString('en-US');
const displayKey = `${formattedWhole}.${formattedText}`;
const initialDisplayKeyRef = useRef(displayKey);

const wholeStyle = useMemo(
() => (Platform.OS === 'android' ? textStyleForAndroid(styles?.wholeText) : styles?.wholeText),
Expand All @@ -82,7 +100,24 @@ const CountUp = ({
[styles?.decimalText],
);

const showRolling = isMounted && useRolling;
useEffect(() => {
if (!animated || animateOnMount || canAnimateAfterMount) return;
const t = requestAnimationFrame(() => setCanAnimateAfterMount(true));
return () => cancelAnimationFrame(t);
}, [animated, animateOnMount, canAnimateAfterMount]);

const hasChangedSinceInitial = displayKey !== initialDisplayKeyRef.current;
const showRolling =
animated &&
isMounted &&
useRolling &&
(animateOnMount || hasSwitchedToRolling || (canAnimateAfterMount && hasChangedSinceInitial));

useEffect(() => {
if (showRolling && !hasSwitchedToRolling) {
setHasSwitchedToRolling(true);
}
}, [showRolling, hasSwitchedToRolling]);

return (
<View className={cn('flex-row items-baseline', classNames?.wrapper)}>
Expand All @@ -93,31 +128,31 @@ const CountUp = ({
prefix
)
) : null}
{showRolling ? (
{!showRolling ? <Text style={wholeStyle}>{formattedWhole}</Text> : null}
{animated ? (
<AnimatedRollingNumber
value={wholeNumber}
containerStyle={showRolling ? undefined : countUpStyles.hiddenRollingNumber}
textStyle={wholeStyle}
spinningAnimationConfig={{ duration: DURATION }}
useGrouping
/>
) : (
<Text style={wholeStyle}>{formattedWhole}</Text>
)}
) : null}
{decimalPlaces > 0 ? (
<>
<Text className={classNames?.decimalSeparator} style={styles?.decimalSeparator}>
.
</Text>
{showRolling ? (
{!showRolling ? <Text style={decimalStyle}>{formattedText}</Text> : null}
{animated ? (
<AnimatedRollingNumber
value={Number(formattedText)}
formattedText={formattedText}
containerStyle={showRolling ? undefined : countUpStyles.hiddenRollingNumber}
textStyle={decimalStyle}
spinningAnimationConfig={{ duration: DURATION }}
/>
) : (
<Text style={decimalStyle}>{formattedText}</Text>
)}
) : null}
</>
) : null}
{suffix ? (
Expand All @@ -127,4 +162,13 @@ const CountUp = ({
);
};

const countUpStyles = StyleSheet.create({
hiddenRollingNumber: {
left: 0,
opacity: 0,
position: 'absolute',
top: 0,
},
});

export default CountUp;
Loading
Loading