diff --git a/src/components/Discover/DiscoverHome.tsx b/src/components/Discover/DiscoverHome.tsx
index 019328a92b6..8da0ba974af 100644
--- a/src/components/Discover/DiscoverHome.tsx
+++ b/src/components/Discover/DiscoverHome.tsx
@@ -70,10 +70,11 @@ export default function DiscoverHome() {
{isProfilesEnabled && }
+
{trendingTokensEnabled && (
<>
-
+
>
)}
diff --git a/src/components/Discover/TrendingTokens.tsx b/src/components/Discover/TrendingTokens.tsx
index 0c3e0fba658..3a14f94011a 100644
--- a/src/components/Discover/TrendingTokens.tsx
+++ b/src/components/Discover/TrendingTokens.tsx
@@ -1,5 +1,5 @@
import { DropdownMenu } from '@/components/DropdownMenu';
-import { globalColors, Text, useBackgroundColor } from '@/design-system';
+import { globalColors, Text, TextIcon, useBackgroundColor, useColorMode } from '@/design-system';
import { useForegroundColor } from '@/design-system/color/useForegroundColor';
import { SwapCoinIcon } from '@/__swaps__/screens/Swap/components/SwapCoinIcon';
@@ -8,7 +8,7 @@ import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks
import { ChainId } from '@/state/backendNetworks/types';
import { ChainImage } from '@/components/coin-icon/ChainImage';
import Skeleton, { FakeAvatar, FakeText } from '@/components/skeleton/Skeleton';
-import { SortDirection, TrendingCategory, TrendingSort } from '@/graphql/__generated__/arc';
+import { SortDirection, Timeframe, TrendingCategory, TrendingSort } from '@/graphql/__generated__/arc';
import { formatCurrency, formatNumber } from '@/helpers/strings';
import * as i18n from '@/languages';
import { Navigation } from '@/navigation';
@@ -22,71 +22,95 @@ import { darkModeThemeColors } from '@/styles/colors';
import { useTheme } from '@/theme';
import { useCallback, useEffect, useMemo } from 'react';
import React, { Dimensions, FlatList, View } from 'react-native';
-import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import LinearGradient from 'react-native-linear-gradient';
-import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
+import Animated, { runOnJS, useSharedValue } from 'react-native-reanimated';
import { ButtonPressAnimation } from '../animations';
import { useFarcasterAccountForWallets } from '@/hooks/useFarcasterAccountForWallets';
import { ImgixImage } from '../images';
import { useRemoteConfig } from '@/model/remoteConfig';
import { useAccountSettings } from '@/hooks';
+import { getColorWorklet, getMixedColor, opacity } from '@/__swaps__/utils/swaps';
+import { THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants';
+import { IS_IOS } from '@/env';
const t = i18n.l.trending_tokens;
-const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient);
-
-function FilterButton({ icon, label, onPress }: { onPress?: VoidFunction; label: string; icon: string | JSX.Element }) {
- const pressed = useSharedValue(false);
-
- const tap = Gesture.Tap()
- .onBegin(() => {
- pressed.value = true;
- if (onPress) runOnJS(onPress)();
- })
- .onFinalize(() => (pressed.value = false));
-
- const animatedStyles = useAnimatedStyle(() => ({
- transform: [{ scale: withTiming(pressed.value ? 0.95 : 1, { duration: 100 }) }],
- }));
-
- const backgroundColor = useBackgroundColor('fillTertiary');
- const borderColor = useBackgroundColor('fillSecondary');
-
- const iconColor = useForegroundColor('labelQuaternary');
+function FilterButton({
+ icon,
+ label,
+ onPress,
+ selected,
+ iconColor,
+ highlightedBackgroundColor,
+}: {
+ onPress?: VoidFunction;
+ label: string;
+ icon: string | JSX.Element;
+ selected: boolean;
+ iconColor?: string;
+ highlightedBackgroundColor?: string;
+}) {
+ const { isDarkMode } = useColorMode();
+ const fillTertiary = useBackgroundColor('fillTertiary');
+ const separatorSecondary = useForegroundColor('separatorSecondary');
+ const borderColor = selected && isDarkMode ? globalColors.white80 : separatorSecondary;
+ const defaultIconColor = getColorWorklet('labelSecondary', selected ? false : isDarkMode);
+
+ const gradientColors = useMemo(() => {
+ if (!selected) return [fillTertiary, fillTertiary];
+ return highlightedBackgroundColor
+ ? [highlightedBackgroundColor, globalColors.white100]
+ : [
+ isDarkMode ? opacity(globalColors.white100, 0.72) : opacity(fillTertiary, 0.2),
+ isDarkMode ? opacity(globalColors.white100, 0.92) : opacity(fillTertiary, 0),
+ ];
+ }, [fillTertiary, highlightedBackgroundColor, selected, isDarkMode]);
return (
-
-
+
{typeof icon === 'string' ? (
-
+
{icon}
-
+
) : (
icon
)}
-
- {label}
-
-
+
+ {/* This first Text element sets the width of the container */}
+
+ {label}
+
+ {/* This second Text element is the visible label, positioned absolutely within the established frame */}
+
+ {label}
+
+
+
-
-
+
+
);
}
@@ -146,69 +170,70 @@ function CategoryFilterButton({
}: {
category: TrendingCategory;
icon: string;
- iconColor: string;
+ iconColor: string | { default: string; selected: string };
highlightedBackgroundColor: string;
iconWidth?: number;
label: string;
}) {
- const { isDarkMode } = useTheme();
+ const { isDarkMode } = useColorMode();
const fillTertiary = useBackgroundColor('fillTertiary');
- const fillSecondary = useBackgroundColor('fillSecondary');
+ const separatorSecondary = useForegroundColor('separatorSecondary');
const selected = useTrendingTokensStore(state => state.category === category);
- const borderColor = selected && isDarkMode ? globalColors.white80 : fillSecondary;
+ const borderColor = selected && isDarkMode ? globalColors.white80 : separatorSecondary;
- const pressed = useSharedValue(false);
+ const gradientColors = useMemo(() => {
+ if (!selected) return [fillTertiary, fillTertiary];
+ return [highlightedBackgroundColor, globalColors.white100];
+ }, [fillTertiary, highlightedBackgroundColor, selected]);
const selectCategory = useCallback(() => {
useTrendingTokensStore.getState().setCategory(category);
}, [category]);
- const tap = Gesture.Tap()
- .onBegin(() => {
- pressed.value = true;
- })
- .onEnd(() => {
- pressed.value = false;
- runOnJS(selectCategory)();
- });
-
- const animatedStyles = useAnimatedStyle(() => ({
- transform: [{ scale: withTiming(pressed.value ? 0.95 : 1, { duration: 100 }) }],
- }));
-
return (
-
-
+
-
- {icon}
-
-
- {label}
-
-
-
+ {icon}
+
+
+ {/* This first Text element sets the width of the container */}
+
+ {label}
+
+ {/* This second Text element is the visible label, positioned absolutely within the established frame */}
+
+ {label}
+
+
+
+
);
}
@@ -266,7 +291,7 @@ function FriendHolders({ friends }: { friends: FarcasterUser[] }) {
function TrendingTokenLoadingRow() {
const backgroundColor = useBackgroundColor('surfacePrimary');
- const { isDarkMode } = useTheme();
+ const { isDarkMode } = useColorMode();
return (
@@ -464,7 +489,7 @@ function TrendingTokenRow({ token }: { token: TrendingToken }) {
}
function NoResults() {
- const { isDarkMode } = useTheme();
+ const { isDarkMode } = useColorMode();
const fillQuaternary = useBackgroundColor('fillQuaternary');
const backgroundColor = isDarkMode ? '#191A1C' : fillQuaternary;
@@ -488,12 +513,27 @@ function NoResults() {
}
function NetworkFilter() {
- const selected = useSharedValue(undefined);
+ const { isDarkMode } = useColorMode();
+ const { colors } = useTheme();
- const { chainId, setChainId } = useTrendingTokensStore(state => ({
- chainId: state.chainId,
- setChainId: state.setChainId,
- }));
+ const selected = useSharedValue(undefined);
+ const chainId = useTrendingTokensStore(state => state.chainId);
+ const setChainId = useTrendingTokensStore(state => state.setChainId);
+
+ const { icon, label, lightenedNetworkColor } = useMemo(() => {
+ if (!chainId) return { icon: '', label: i18n.t(t.all), lightenedNetworkColor: undefined };
+ return {
+ icon: (
+
+
+
+ ),
+ label: useBackendNetworksStore.getState().getChainsLabel()[chainId],
+ lightenedNetworkColor: colors.networkColors[chainId]
+ ? getMixedColor(colors.networkColors[chainId], globalColors.white100, isDarkMode ? 0.55 : 0.6)
+ : undefined,
+ };
+ }, [chainId, colors.networkColors, isDarkMode]);
const setSelected = useCallback(
(chainId: ChainId | undefined) => {
@@ -504,13 +544,6 @@ function NetworkFilter() {
[selected, setChainId]
);
- const label = !chainId ? i18n.t(t.all) : useBackendNetworksStore.getState().getChainsLabel()[chainId];
-
- const icon = useMemo(() => {
- if (!chainId) return '';
- return ;
- }, [chainId]);
-
const navigateToNetworkSelector = useCallback(() => {
Navigation.handleAction(Routes.NETWORK_SELECTOR, {
selected,
@@ -518,11 +551,20 @@ function NetworkFilter() {
});
}, [selected, setSelected]);
- return ;
+ return (
+
+ );
}
function TimeFilter() {
const timeframe = useTrendingTokensStore(state => state.timeframe);
+ const shouldAbbreviate = timeframe === Timeframe.H24;
return (
useTrendingTokensStore.getState().setTimeframe(timeframe)}
>
-
+
);
}
function SortFilter() {
const sort = useTrendingTokensStore(state => state.sort);
+ const selected = sort !== TrendingSort.Recommended;
- const iconColor = useForegroundColor('labelQuaternary');
+ const iconColor = useForegroundColor(selected ? 'labelSecondary' : 'labelTertiary');
return (
@@ -591,8 +643,8 @@ function TrendingTokenData() {
return (
}
data={trendingTokens}
renderItem={({ item }) => }
@@ -603,6 +655,7 @@ function TrendingTokenData() {
const padding = 20;
export function TrendingTokens() {
+ const { isDarkMode } = useColorMode();
return (
@@ -623,8 +676,8 @@ export function TrendingTokens() {
category={TrendingCategory.New}
label={i18n.t(t.filters.categories.NEW)}
icon=""
- iconColor={'#FFDA24'}
- highlightedBackgroundColor={'#F9EAA1'}
+ iconColor={{ default: isDarkMode ? globalColors.yellow60 : '#FFBB00', selected: '#F5A200' }}
+ highlightedBackgroundColor={'#FFEAC2'}
iconWidth={18}
/>
{
- const activeSwipeRoute = useNavigationStore(state => state.activeSwipeRoute);
+ const isOnDiscoverScreen = useNavigationStore(state => state.isRouteActive(Routes.DISCOVER_SCREEN));
+
useEffect(() => {
- const isOnDiscoverScreen = activeSwipeRoute === Routes.DISCOVER_SCREEN;
const data = currentlyTrackedMetrics.get(PerformanceMetrics.timeSpentOnDiscoverScreen);
if (!isOnDiscoverScreen && data?.startTimestamp) {
@@ -17,5 +17,5 @@ export const useTrackDiscoverScreenTime = () => {
if (isOnDiscoverScreen) {
PerformanceTracking.startMeasuring(PerformanceMetrics.timeSpentOnDiscoverScreen);
}
- }, [activeSwipeRoute]);
+ }, [isOnDiscoverScreen]);
};
diff --git a/src/components/NetworkSwitcher.tsx b/src/components/NetworkSwitcher.tsx
index 50c4e13d639..65a66961d7a 100644
--- a/src/components/NetworkSwitcher.tsx
+++ b/src/components/NetworkSwitcher.tsx
@@ -1,15 +1,14 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
-import { getChainColorWorklet } from '@/__swaps__/utils/swaps';
+import { getChainColorWorklet, opacity } from '@/__swaps__/utils/swaps';
import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
import { ChainId } from '@/state/backendNetworks/types';
import { AnimatedBlurView } from '@/components/AnimatedComponents/AnimatedBlurView';
import { ButtonPressAnimation } from '@/components/animations';
import { SPRING_CONFIGS, TIMING_CONFIGS } from '@/components/animations/animationConfigs';
-import { AnimatedChainImage, ChainImage } from '@/components/coin-icon/ChainImage';
+import { ChainImage } from '@/components/coin-icon/ChainImage';
import { AnimatedText, Box, DesignSystemProvider, globalColors, Separator, Text, useBackgroundColor, useColorMode } from '@/design-system';
import { useForegroundColor } from '@/design-system/color/useForegroundColor';
import * as i18n from '@/languages';
-import { useTheme } from '@/theme';
import deviceUtils, { DEVICE_WIDTH } from '@/utils/deviceUtils';
import MaskedView from '@react-native-masked-view/masked-view';
import chroma from 'chroma-js';
@@ -19,6 +18,7 @@ import { RouteProp, useRoute } from '@react-navigation/native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import LinearGradient from 'react-native-linear-gradient';
import Animated, {
+ Easing,
FadeIn,
FadeOutUp,
LinearTransition,
@@ -46,6 +46,8 @@ import { IS_IOS } from '@/env';
import { safeAreaInsetValues } from '@/utils';
import { noop } from 'lodash';
import { TapToDismiss } from './DappBrowser/control-panel/ControlPanel';
+import { THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants';
+import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton';
const t = i18n.l.network_switcher;
@@ -72,11 +74,18 @@ function EditButton({ editing }: { editing: SharedValue }) {
editing.value = !editing.value;
}}
scaleTo={0.95}
- style={[
- { position: 'absolute', right: 0 },
- { paddingHorizontal: 10, height: 28, justifyContent: 'center' },
- { borderColor, borderWidth: 1.33, borderRadius: 14 },
- ]}
+ style={{
+ borderColor,
+ borderCurve: 'continuous',
+ borderRadius: 14,
+ borderWidth: THICK_BORDER_WIDTH,
+ height: 28,
+ justifyContent: 'center',
+ overflow: 'hidden',
+ paddingHorizontal: 10,
+ position: 'absolute',
+ right: 0,
+ }}
>
{text}
@@ -96,7 +105,17 @@ function Header({ editing }: { editing: SharedValue }) {
return (
-
+
@@ -199,16 +218,28 @@ const CustomizeNetworksBanner = !shouldShowCustomizeNetworksBanner(customizeNetw
);
};
+const BADGE_BORDER_COLORS = {
+ default: {
+ dark: globalColors.white10,
+ light: '#F2F3F4',
+ },
+ selected: {
+ dark: '#1E2E40',
+ light: '#D7E9FD',
+ },
+};
+
const useNetworkOptionStyle = (isSelected: SharedValue, color?: string) => {
const { isDarkMode } = useColorMode();
const label = useForegroundColor('labelTertiary');
const surfacePrimary = useBackgroundColor('surfacePrimary');
const networkSwitcherBackgroundColor = isDarkMode ? '#191A1C' : surfacePrimary;
+ const separatorTertiary = useForegroundColor('separatorTertiary');
const defaultStyle = {
backgroundColor: isDarkMode ? globalColors.white10 : globalColors.grey20,
- borderColor: '#F5F8FF05',
+ borderColor: isDarkMode ? opacity(separatorTertiary, 0.02) : separatorTertiary,
};
const selectedStyle = {
backgroundColor: chroma
@@ -222,9 +253,12 @@ const useNetworkOptionStyle = (isSelected: SharedValue, color?: string)
const scale = useSharedValue(1);
useAnimatedReaction(
() => isSelected.value,
- current => {
- if (current === true) {
- scale.value = withSequence(withTiming(0.95, { duration: 50 }), withTiming(1, { duration: 80 }));
+ (current, prev) => {
+ if (current === true && prev === false) {
+ scale.value = withSequence(
+ withTiming(0.9, { duration: 120, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }),
+ withTiming(1, TIMING_CONFIGS.fadeConfig)
+ );
}
}
);
@@ -252,29 +286,28 @@ function AllNetworksOption({
selected: SharedValue;
setSelected: (chainId: ChainId | undefined) => void;
}) {
+ const { isDarkMode } = useColorMode();
const blue = useForegroundColor('blue');
const isSelected = useDerivedValue(() => selected.value === undefined);
- const { animatedStyle, selectedStyle, defaultStyle } = useNetworkOptionStyle(isSelected, blue);
+ const { animatedStyle } = useNetworkOptionStyle(isSelected, blue);
const overlappingBadge = useAnimatedStyle(() => {
return {
- borderColor: isSelected.value ? selectedStyle.backgroundColor : defaultStyle.backgroundColor,
- borderWidth: 1.67,
- borderRadius: 16,
- marginLeft: -9,
- width: 16 + 1.67 * 2, // size + borders
- height: 16 + 1.67 * 2,
+ borderColor: isSelected.value
+ ? BADGE_BORDER_COLORS.selected[isDarkMode ? 'dark' : 'light']
+ : BADGE_BORDER_COLORS.default[isDarkMode ? 'dark' : 'light'],
};
});
- const tapGesture = Gesture.Tap().onTouchesDown(() => {
- 'worklet';
- setSelected(undefined);
- });
-
return (
-
+ {
+ 'worklet';
+ setSelected(undefined);
+ }}
+ scaleTo={0.95}
+ >
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
{i18n.t(t.all_networks)}
-
+
);
}
@@ -338,9 +381,17 @@ function NetworkOption({ chainId, selected }: { chainId: ChainId; selected: Shar
@@ -358,6 +409,10 @@ const GAP = 12;
const ITEM_WIDTH = (DEVICE_WIDTH - SHEET_INNER_PADDING * 2 - SHEET_OUTER_INSET * 2 - GAP) / 2;
const ITEM_HEIGHT = 48;
const SEPARATOR_HEIGHT = 68;
+
+const ALL_NETWORKS_BADGE_SIZE = 16;
+const THICKER_BORDER_WIDTH = 5 / 3;
+
const enum Section {
pinned,
separator,
@@ -482,7 +537,7 @@ function SectionSeparator({
const showMoreOrLessIcon = useDerivedValue(() => (expanded.value ? '' : '') as string);
const showMoreOrLessIconStyle = useAnimatedStyle(() => ({ opacity: editing.value ? 0 : 1 }));
- const { isDarkMode } = useTheme();
+ const { isDarkMode } = useColorMode();
const separatorContainerStyles = useAnimatedStyle(() => {
if (showExpandButtonAsNetworkChip.value) {
@@ -495,7 +550,7 @@ function SectionSeparator({
flexDirection: 'row',
alignItems: 'center',
borderRadius: 24,
- borderWidth: 1.33,
+ borderWidth: THICK_BORDER_WIDTH,
transform: [{ translateX: position.x }, { translateY: position.y }],
};
}
@@ -559,13 +614,13 @@ function EmptyUnpinnedPlaceholder({
transform: [{ translateY: sectionsOffsets.value[Section.unpinned].y }],
};
});
- const { isDarkMode } = useTheme();
+ const { isDarkMode } = useColorMode();
return (
; onClose: VoidFunction }>) {
- const { isDarkMode } = useTheme();
+ const { isDarkMode } = useColorMode();
const surfacePrimary = useBackgroundColor('surfacePrimary');
const backgroundColor = isDarkMode ? '#191A1C' : surfacePrimary;
const separatorSecondary = useForegroundColor('separatorSecondary');
@@ -760,7 +815,7 @@ function Sheet({ children, editing, onClose }: PropsWithChildren<{ editing: Shar
sx.sheet,
{
backgroundColor,
- borderColor: separatorSecondary,
+ borderColor: isDarkMode ? separatorSecondary : globalColors.white100,
},
]}
>
@@ -789,6 +844,13 @@ export function NetworkSelector() {
}
const sx = StyleSheet.create({
+ overlappingBadge: {
+ borderWidth: THICKER_BORDER_WIDTH,
+ borderRadius: ALL_NETWORKS_BADGE_SIZE,
+ marginLeft: -9,
+ width: ALL_NETWORKS_BADGE_SIZE + THICKER_BORDER_WIDTH * 2,
+ height: ALL_NETWORKS_BADGE_SIZE + THICKER_BORDER_WIDTH * 2,
+ },
sheet: {
flex: 1,
width: deviceUtils.dimensions.width - 16,
@@ -799,7 +861,9 @@ const sx = StyleSheet.create({
left: 8,
right: 8,
paddingHorizontal: 16,
+ borderCurve: 'continuous',
borderRadius: 42,
- borderWidth: 1.33,
+ borderWidth: THICK_BORDER_WIDTH,
+ overflow: 'hidden',
},
});
diff --git a/src/components/coin-icon/ChainImage.tsx b/src/components/coin-icon/ChainImage.tsx
index 671296c8445..90f8ffbc960 100644
--- a/src/components/coin-icon/ChainImage.tsx
+++ b/src/components/coin-icon/ChainImage.tsx
@@ -89,5 +89,3 @@ export const ChainImage = forwardRef(function ChainImage(
/>
);
});
-
-export const AnimatedChainImage = Animated.createAnimatedComponent(ChainImage);
diff --git a/src/languages/en_US.json b/src/languages/en_US.json
index 4bfe7a65a0d..040e9da9ab7 100644
--- a/src/languages/en_US.json
+++ b/src/languages/en_US.json
@@ -3043,8 +3043,10 @@
"TOP_LOSERS": "Top Losers"
},
"time": {
- "H12": "12h",
- "H24": "24h",
+ "H12": "12 Hours",
+ "H12_ABBREVIATED": "12h",
+ "H24": "24 Hours",
+ "H24_ABBREVIATED": "24h",
"D7": "1 Week",
"D3": "1 Month"
}
@@ -3056,11 +3058,11 @@
"tap_the": "Tap the",
"button_to_set_up": "button below to set up"
},
- "drag_here_to_unpin": "Drag here to unpin networks",
+ "drag_here_to_unpin": "Drop Here to Unpin",
"edit": "Edit",
"networks": "Networks",
- "drag_to_rearrange": "Drag to rearrange",
- "show_less": "Show less",
+ "drag_to_rearrange": "Drag to Rearrange",
+ "show_less": "Show Less",
"more": "More",
"show_more": "More Networks",
"all_networks": "All Networks"