diff --git a/src/components/NetworkSwitcher.tsx b/src/components/NetworkSwitcher.tsx index 2b385abb1fe..c4dbaaf2490 100644 --- a/src/components/NetworkSwitcher.tsx +++ b/src/components/NetworkSwitcher.tsx @@ -1,12 +1,22 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { BlurView } from '@react-native-community/blur'; import { 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 { ChainImage } from '@/components/coin-icon/ChainImage'; -import { AnimatedText, Box, DesignSystemProvider, globalColors, Separator, Text, useBackgroundColor, useColorMode } from '@/design-system'; +import { + AnimatedText, + Box, + DesignSystemProvider, + globalColors, + Inset, + Separator, + Text, + TextShadow, + useBackgroundColor, + useColorMode, +} from '@/design-system'; import { useForegroundColor } from '@/design-system/color/useForegroundColor'; import * as i18n from '@/languages'; import deviceUtils, { DEVICE_WIDTH } from '@/utils/deviceUtils'; @@ -28,7 +38,7 @@ import Animated, { useAnimatedStyle, useDerivedValue, useSharedValue, - withDelay, + withClamp, withSequence, withSpring, withTiming, @@ -48,14 +58,15 @@ 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'; -import { useTheme } from '@/theme'; +import { triggerHaptics } from 'react-native-turbo-haptics'; +import { AnimatedTextIcon } from './AnimatedComponents/AnimatedTextIcon'; const t = i18n.l.network_switcher; const translations = { edit: i18n.t(t.edit), done: i18n.t(i18n.l.done), - networks: i18n.t(t.networks), + network: i18n.t(t.network), more: i18n.t(t.more), show_more: i18n.t(t.show_more), show_less: i18n.t(t.show_less), @@ -74,23 +85,13 @@ function EditButton({ editing }: { editing: SharedValue }) { 'worklet'; editing.value = !editing.value; }} - scaleTo={0.95} - style={{ - borderColor, - borderCurve: 'continuous', - borderRadius: 14, - borderWidth: THICK_BORDER_WIDTH, - height: 28, - justifyContent: 'center', - overflow: 'hidden', - paddingHorizontal: 10, - position: 'absolute', - right: 0, - }} + style={[sx.editButton, { borderColor }]} > - - {text} - + + + {text} + + ); } @@ -100,27 +101,15 @@ function Header({ editing }: { editing: SharedValue }) { const fill = useForegroundColor('fill'); const title = useDerivedValue(() => { - return editing.value ? translations.edit : translations.networks; + return editing.value ? translations.edit : translations.network; }); return ( - - - - + + - - + + {title} @@ -130,6 +119,8 @@ function Header({ editing }: { editing: SharedValue }) { ); } +const BANNER_HEIGHT = 75; + const CustomizeNetworksBanner = !shouldShowCustomizeNetworksBanner(customizeNetworksBannerStore.getState().dismissedAt) ? () => null : function CustomizeNetworksBanner({ editing }: { editing: SharedValue }) { @@ -143,19 +134,14 @@ const CustomizeNetworksBanner = !shouldShowCustomizeNetworksBanner(customizeNetw const dismissedAt = customizeNetworksBannerStore(s => s.dismissedAt); if (!shouldShowCustomizeNetworksBanner(dismissedAt)) return null; - const height = 75; const blue = '#268FFF'; return ( - + + } > - - - - + + + + 􀍱 @@ -212,14 +174,14 @@ const CustomizeNetworksBanner = !shouldShowCustomizeNetworksBanner(customizeNetw - + ); }; -const BADGE_BORDER_COLORS = { +const ALL_BADGE_BORDER_COLORS = { default: { dark: globalColors.white10, light: '#F2F3F4', @@ -230,7 +192,7 @@ const BADGE_BORDER_COLORS = { }, }; -const useNetworkOptionStyle = (isSelected: SharedValue, color?: string) => { +const useNetworkOptionStyle = (isSelected: SharedValue, color?: string, disableScale = false) => { const { isDarkMode } = useColorMode(); const label = useForegroundColor('labelTertiary'); @@ -253,11 +215,11 @@ const useNetworkOptionStyle = (isSelected: SharedValue, color?: string) const scale = useSharedValue(1); useAnimatedReaction( - () => isSelected.value, + () => (disableScale ? false : isSelected.value), (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(0.93, { duration: 110, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }), withTiming(1, TIMING_CONFIGS.fadeConfig) ); } @@ -291,115 +253,89 @@ function AllNetworksOption({ const blue = useForegroundColor('blue'); const isSelected = useDerivedValue(() => selected.value === undefined); - const { animatedStyle } = useNetworkOptionStyle(isSelected, blue); + const { animatedStyle } = useNetworkOptionStyle(isSelected, blue, true); const overlappingBadge = useAnimatedStyle(() => { return { borderColor: isSelected.value - ? BADGE_BORDER_COLORS.selected[isDarkMode ? 'dark' : 'light'] - : BADGE_BORDER_COLORS.default[isDarkMode ? 'dark' : 'light'], + ? ALL_BADGE_BORDER_COLORS.selected[isDarkMode ? 'dark' : 'light'] + : ALL_BADGE_BORDER_COLORS.default[isDarkMode ? 'dark' : 'light'], }; }); return ( { 'worklet'; setSelected(undefined); }} - scaleTo={0.95} + scaleTo={0.94} + style={[sx.allNetworksButton, animatedStyle]} > - - - - - - - - - - - - - - - - - {i18n.t(t.all_networks)} - - + + + + + + + + + + + + + + + + {i18n.t(t.all_networks)} + ); } function AllNetworksSection({ editing, - setSelected, selected, + setSelected, }: { editing: SharedValue; - setSelected: (chainId: ChainId | undefined) => void; selected: SharedValue; + setSelected: (chainId: ChainId | undefined) => void; }) { - const style = useAnimatedStyle(() => ({ - opacity: editing.value ? withTiming(0, TIMING_CONFIGS.fastFadeConfig) : withTiming(1, TIMING_CONFIGS.fastFadeConfig), - height: withTiming( - editing.value ? 0 : ITEM_HEIGHT + 14, // 14 is the gap to the separator - TIMING_CONFIGS.fastFadeConfig + const animatedStyle = useAnimatedStyle(() => ({ + height: withClamp( + { min: 0, max: ITEM_HEIGHT + 14 * 2 }, + withSpring( + editing.value ? 0 : ITEM_HEIGHT + 14 * 2, // 14 is the gap to the separator + SPRING_CONFIGS.springConfig + ) ), - marginTop: editing.value ? 0 : 14, + opacity: editing.value ? withSpring(0, SPRING_CONFIGS.snappierSpringConfig) : withTiming(1, TIMING_CONFIGS.slowerFadeConfig), pointerEvents: editing.value ? 'none' : 'auto', })); + return ( - + - + + + ); } function NetworkOption({ chainId, selected }: { chainId: ChainId; selected: SharedValue }) { const { isDarkMode } = useColorMode(); - const getColorsForChainId = useBackendNetworksStore(state => state.getColorsForChainId); + const chainColor = useBackendNetworksStore.getState().getColorsForChainId(chainId, isDarkMode); const chainName = useBackendNetworksStore.getState().getChainsLabel()[chainId]; - const chainColor = getColorsForChainId(chainId, isDarkMode); const isSelected = useDerivedValue(() => selected.value === chainId); const { animatedStyle } = useNetworkOptionStyle(isSelected, chainColor); return ( - + - + {chainName} @@ -407,11 +343,12 @@ function NetworkOption({ chainId, selected }: { chainId: ChainId; selected: Shar } const SHEET_OUTER_INSET = 8; -const SHEET_INNER_PADDING = 16; -const GAP = 12; -const ITEM_WIDTH = (DEVICE_WIDTH - SHEET_INNER_PADDING * 2 - SHEET_OUTER_INSET * 2 - GAP) / 2; +const SHEET_INNER_PADDING = 18; +const ITEM_GAP = 12; +const ITEM_WIDTH = (DEVICE_WIDTH - SHEET_INNER_PADDING * 2 - SHEET_OUTER_INSET * 2 - ITEM_GAP) / 2; const ITEM_HEIGHT = 48; const SEPARATOR_HEIGHT = 68; +const SEPARATOR_HEIGHT_NETWORK_CHIP = 18; const ALL_NETWORKS_BADGE_SIZE = 16; const THICKER_BORDER_WIDTH = 5 / 3; @@ -451,33 +388,29 @@ function Draggable({ const itemIndex = networks.value[section].indexOf(chainId); const slotPosition = positionFromIndex(itemIndex, sectionsOffsets.value[section]); - const opacity = - section === Section.unpinned && isUnpinnedHidden.value - ? withTiming(0, TIMING_CONFIGS.fastFadeConfig) - : withDelay(100, withTiming(1, TIMING_CONFIGS.fadeConfig)); - const isBeingDragged = dragging.value?.chainId === chainId; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const position = isBeingDragged ? dragging.value!.position : slotPosition; return { - opacity, - zIndex: zIndex.value, + opacity: withSpring(section === Section.unpinned && isUnpinnedHidden.value ? 0 : 1, SPRING_CONFIGS.snappierSpringConfig), transform: [ - { scale: withSpring(isBeingDragged ? 1.05 : 1, SPRING_CONFIGS.springConfig) }, { translateX: isBeingDragged ? position.x : withSpring(position.x, SPRING_CONFIGS.springConfig) }, { translateY: isBeingDragged ? position.y : withSpring(position.y, SPRING_CONFIGS.springConfig) }, + { scale: withSpring(isBeingDragged ? 1.075 : 1, SPRING_CONFIGS.springConfig) }, ], + zIndex: zIndex.value, }; }); - return {children}; + return {children}; } const indexFromPosition = (x: number, y: number, offset: { y: number }) => { 'worklet'; const yoffsets = y > offset.y ? offset.y : 0; - const column = x > ITEM_WIDTH + GAP / 2 ? 1 : 0; - const row = Math.floor((y - yoffsets) / (ITEM_HEIGHT + GAP)); + const column = x > ITEM_WIDTH + ITEM_GAP / 2 ? 1 : 0; + const row = Math.floor((y - yoffsets) / (ITEM_HEIGHT + ITEM_GAP)); const index = row * 2 + column; return index < 0 ? 0 : index; // row can be negative if the dragged item is above the first row }; @@ -486,7 +419,7 @@ const positionFromIndex = (index: number, offset: { y: number }) => { 'worklet'; const column = index % 2; const row = Math.floor(index / 2); - const position = { x: column * (ITEM_WIDTH + GAP), y: row * (ITEM_HEIGHT + GAP) + offset.y }; + const position = { x: column * (ITEM_WIDTH + ITEM_GAP), y: row * (ITEM_HEIGHT + ITEM_GAP) + offset.y }; return position; }; @@ -497,22 +430,21 @@ type DraggingState = { }; function SectionSeparator({ - sectionsOffsets, editing, expanded, networks, + sectionsOffsets, + showExpandButtonAsNetworkChip, }: { - sectionsOffsets: SharedValue>; editing: SharedValue; expanded: SharedValue; networks: SharedValue>; + sectionsOffsets: SharedValue>; + showExpandButtonAsNetworkChip: SharedValue; }) { + const { isDarkMode } = useColorMode(); const pressed = useSharedValue(false); - const showExpandButtonAsNetworkChip = useDerivedValue(() => { - return !expanded.value && !editing.value && networks.value[Section.pinned].length % 2 !== 0; - }); - const visible = useDerivedValue(() => { return networks.value[Section.unpinned].length > 0 || editing.value; }); @@ -523,8 +455,12 @@ function SectionSeparator({ pressed.value = true; }) .onEnd(() => { - pressed.value = false; + triggerHaptics('selection'); expanded.value = !expanded.value; + pressed.value = false; + }) + .onFinalize(() => { + if (pressed.value) pressed.value = false; }); const text = useDerivedValue(() => { @@ -534,13 +470,13 @@ function SectionSeparator({ }); const unpinnedNetworksLength = useDerivedValue(() => networks.value[Section.unpinned].length.toString()); + const showMoreOrLessIcon = useDerivedValue(() => (expanded.value ? '􀆇' : '􀆈')); + + const showMoreOrLessIconStyle = useAnimatedStyle(() => ({ opacity: editing.value ? 0 : 1 })); const showMoreAmountStyle = useAnimatedStyle(() => ({ - opacity: expanded.value || editing.value ? 0 : 1, + opacity: withSpring(expanded.value || editing.value ? 0 : 1, SPRING_CONFIGS.snappierSpringConfig), + width: expanded.value ? 0 : parseFloat(unpinnedNetworksLength.value) >= 10 ? 30 : 24, })); - const showMoreOrLessIcon = useDerivedValue(() => (expanded.value ? '􀆇' : '􀆈') as string); - const showMoreOrLessIconStyle = useAnimatedStyle(() => ({ opacity: editing.value ? 0 : 1 })); - - const { isDarkMode } = useColorMode(); const separatorContainerStyles = useAnimatedStyle(() => { if (showExpandButtonAsNetworkChip.value) { @@ -549,87 +485,68 @@ function SectionSeparator({ backgroundColor: isDarkMode ? globalColors.white10 : globalColors.grey20, borderColor: '#F5F8FF05', height: ITEM_HEIGHT, - width: ITEM_WIDTH, - flexDirection: 'row', - alignItems: 'center', - borderRadius: 24, - borderWidth: THICK_BORDER_WIDTH, + opacity: visible.value ? 1 : 0, transform: [{ translateX: position.x }, { translateY: position.y }], + width: ITEM_WIDTH, }; } return { backgroundColor: 'transparent', + borderColor: 'transparent', + height: SEPARATOR_HEIGHT, opacity: visible.value ? 1 : 0, - transform: [{ translateY: sectionsOffsets.value[Section.separator].y }, { scale: withTiming(pressed.value ? 0.95 : 1) }], - position: 'absolute', + transform: [ + { translateY: sectionsOffsets.value[Section.separator].y }, + { scale: withTiming(pressed.value ? 0.925 : 1, TIMING_CONFIGS.buttonPressConfig) }, + ], width: '100%', - height: SEPARATOR_HEIGHT, }; }); return ( - - - + + + {unpinnedNetworksLength} - + {text} - - - {showMoreOrLessIcon} - - + + {showMoreOrLessIcon} + ); } function EmptyUnpinnedPlaceholder({ - sectionsOffsets, - networks, isUnpinnedHidden, + networks, + sectionsOffsets, }: { - sectionsOffsets: SharedValue>; - networks: SharedValue>; isUnpinnedHidden: SharedValue; + networks: SharedValue>; + sectionsOffsets: SharedValue>; }) { - const styles = useAnimatedStyle(() => { + const { isDarkMode } = useColorMode(); + const animatedStyle = useAnimatedStyle(() => { const isVisible = networks.value[Section.unpinned].length === 0 && !isUnpinnedHidden.value; return { - opacity: isVisible ? withTiming(1, { duration: 800 }) : 0, + opacity: withSpring(isVisible ? 0.5 : 0, SPRING_CONFIGS.snappierSpringConfig), transform: [{ translateY: sectionsOffsets.value[Section.unpinned].y }], }; }); - const { isDarkMode } = useColorMode(); + return ( - - {i18n.t(t.drag_here_to_unpin)} + + {i18n.t(t.drop_here_to_unpin)} ); @@ -637,12 +554,12 @@ function EmptyUnpinnedPlaceholder({ function NetworksGrid({ editing, - setSelected, selected, + setSelected, }: { editing: SharedValue; - setSelected: (chainId: ChainId | undefined) => void; selected: SharedValue; + setSelected: (chainId: ChainId | undefined) => void; }) { const initialPinned = networkSwitcherStore.getState().pinnedNetworks; const sortedSupportedChainIds = useBackendNetworksStore.getState().getSortedSupportedChainIds(); @@ -662,33 +579,38 @@ function NetworksGrid({ }, [networks]); const expanded = useSharedValue(false); + const dragging = useSharedValue(null); const isUnpinnedHidden = useDerivedValue(() => !expanded.value && !editing.value); - const dragging = useSharedValue(null); + const showExpandButtonAsNetworkChip = useDerivedValue(() => { + return !expanded.value && !editing.value && networks.value[Section.pinned].length % 2 !== 0; + }); const sectionsOffsets = useDerivedValue(() => { - const pinnedHeight = Math.ceil(networks.value[Section.pinned].length / 2) * (ITEM_HEIGHT + GAP) - GAP; + const pinnedHeight = Math.ceil(networks.value[Section.pinned].length / 2) * (ITEM_HEIGHT + ITEM_GAP) - ITEM_GAP; return { [Section.pinned]: { y: 0 }, [Section.separator]: { y: pinnedHeight }, - [Section.unpinned]: { y: pinnedHeight + SEPARATOR_HEIGHT }, + [Section.unpinned]: { y: pinnedHeight + (showExpandButtonAsNetworkChip.value ? SEPARATOR_HEIGHT_NETWORK_CHIP : SEPARATOR_HEIGHT) }, }; }); + const containerHeight = useDerivedValue(() => { const length = networks.value[Section.unpinned].length; - const paddingBottom = 32; + const paddingBottom = 18; const unpinnedHeight = isUnpinnedHidden.value ? length === 0 ? -SEPARATOR_HEIGHT + paddingBottom : 0 : length === 0 ? ITEM_HEIGHT + paddingBottom - : Math.ceil((length + 1) / 2) * (ITEM_HEIGHT + GAP) - GAP + paddingBottom; + : Math.ceil((length + (editing.value ? 1 : 0)) / 2) * (ITEM_HEIGHT + ITEM_GAP) - ITEM_GAP + paddingBottom; const height = sectionsOffsets.value[Section.unpinned].y + unpinnedHeight; return height; }); + const containerStyle = useAnimatedStyle(() => ({ - height: withDelay(expanded.value ? 0 : 25, withTiming(containerHeight.value, TIMING_CONFIGS.slowerFadeConfig)), + height: withSpring(containerHeight.value, SPRING_CONFIGS.springConfig), })); const dragNetwork = Gesture.Pan() @@ -711,6 +633,7 @@ function NetworksGrid({ } const position = positionFromIndex(index, sectionOffset); + triggerHaptics('soft'); dragging.value = { chainId, position }; }) .onChange(e => { @@ -732,6 +655,7 @@ function NetworksGrid({ networks[Section.unpinned] = sortedSupportedChainIds.filter(chainId => !networks[Section.pinned].includes(chainId)); } else if (section === Section.pinned && newIndex !== currentIndex) { // Reorder + triggerHaptics('selection'); networks[Section.pinned].splice(currentIndex, 1); networks[Section.pinned].splice(newIndex, 0, chainId); } @@ -758,6 +682,7 @@ function NetworksGrid({ const chainId = networks.value[section][index]; if (!chainId) return; + triggerHaptics('selection'); setSelected(chainId); }); @@ -779,7 +704,13 @@ function NetworksGrid({ ))} - + @@ -840,33 +771,160 @@ export function NetworkSelector() { return ( - - + + ); } const sx = StyleSheet.create({ + allNetworksButton: { + alignItems: 'center', + borderCurve: 'continuous', + borderRadius: 24, + borderWidth: THICK_BORDER_WIDTH, + flexDirection: 'row', + height: ITEM_HEIGHT, + overflow: 'hidden', + paddingHorizontal: 12, + }, + allNetworksCoinIcons: { + alignItems: 'center', + flexDirection: 'row', + marginLeft: 20, + position: 'absolute', + }, + allNetworksContainer: { + gap: 14, + justifyContent: 'flex-end', + }, + banner: { + left: 0, + position: 'absolute', + right: 0, + top: -(BANNER_HEIGHT + 14), + }, + bannerBlurView: { + height: BANNER_HEIGHT, + }, + bannerContent: { + alignItems: 'center', + flex: 1, + flexDirection: 'row', + gap: 12, + height: 68, + marginTop: 68 - BANNER_HEIGHT, + padding: 16 + 12, + }, + bannerGradient: { + alignItems: 'center', + backgroundColor: '#268FFF14', + borderColor: '#268FFF0D', + borderRadius: 10, + borderWidth: 1, + height: 36, + justifyContent: 'center', + width: 36, + }, + editButton: { + borderCurve: 'continuous', + borderRadius: 14, + borderWidth: THICK_BORDER_WIDTH, + height: 28, + justifyContent: 'center', + overflow: 'hidden', + paddingHorizontal: 10 - THICK_BORDER_WIDTH, + position: 'absolute', + right: 4, + top: IS_IOS ? 0 : -14, + }, + emptyUnpinnedPlaceholder: { + alignItems: 'center', + borderColor: '#F5F8FF05', + borderRadius: 24, + borderWidth: THICK_BORDER_WIDTH, + flexDirection: 'row', + height: 48, + paddingHorizontal: 12, + width: '100%', + }, + flex: { + flex: 1, + }, + headerContainer: { + alignItems: 'center', + borderBottomWidth: 1, + height: 66, + paddingTop: 20, + width: '100%', + }, + headerContent: { + alignItems: 'center', + flexDirection: 'row', + height: 28, + justifyContent: 'center', + }, + networkCountBadge: { + alignItems: 'center', + borderRadius: 12, + height: 24, + justifyContent: 'center', + width: 24, + }, + networkOption: { + alignItems: 'center', + borderCurve: 'continuous', + borderRadius: 24, + borderWidth: THICK_BORDER_WIDTH, + flexDirection: 'row', + height: ITEM_HEIGHT, + overflow: 'hidden', + paddingHorizontal: 12, + width: ITEM_WIDTH, + }, overlappingBadge: { - borderWidth: THICKER_BORDER_WIDTH, borderRadius: ALL_NETWORKS_BADGE_SIZE, + borderWidth: THICKER_BORDER_WIDTH, + height: ALL_NETWORKS_BADGE_SIZE + THICKER_BORDER_WIDTH * 2, marginLeft: -9, width: ALL_NETWORKS_BADGE_SIZE + THICKER_BORDER_WIDTH * 2, - height: ALL_NETWORKS_BADGE_SIZE + THICKER_BORDER_WIDTH * 2, + }, + positionAbsolute: { + position: 'absolute', + }, + sectionSeparatorContainer: { + alignItems: 'center', + borderRadius: 24, + borderWidth: THICK_BORDER_WIDTH, + flexDirection: 'row', + gap: 8, + justifyContent: 'center', + paddingHorizontal: 12, + position: 'absolute', }, sheet: { - flex: 1, - width: deviceUtils.dimensions.width - 16, + borderCurve: 'continuous', + borderRadius: 42, + borderWidth: THICK_BORDER_WIDTH, bottom: Math.max(safeAreaInsetValues.bottom + 5, IS_IOS ? 8 : 30), + flex: 1, + left: 8, + overflow: 'hidden', + paddingHorizontal: 16, pointerEvents: 'box-none', position: 'absolute', - zIndex: 30000, - left: 8, right: 8, - paddingHorizontal: 16, + width: deviceUtils.dimensions.width - 16, + zIndex: 30000, + }, + sheetHandle: { + alignSelf: 'center', borderCurve: 'continuous', - borderRadius: 42, - borderWidth: THICK_BORDER_WIDTH, + borderRadius: 3, + height: 5, overflow: 'hidden', + position: 'absolute', + top: 6, + width: 36, }, }); diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 5865734fece..cfc438fea2f 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -3076,8 +3076,9 @@ "tap_the": "Tap the", "button_to_set_up": "button below to set up" }, - "drag_here_to_unpin": "Drop Here to Unpin", + "drop_here_to_unpin": "Drop Here to Unpin", "edit": "Edit", + "network": "Network", "networks": "Networks", "drag_to_rearrange": "Drag to Rearrange", "show_less": "Show Less",