diff --git a/src/locale/locales/en/common.json b/src/locale/locales/en/common.json index daea5cf21..2c3c51046 100644 --- a/src/locale/locales/en/common.json +++ b/src/locale/locales/en/common.json @@ -353,5 +353,7 @@ "URL": "URL", "add": "Add", "addToFavourites": "Add to favourites", - "removeFavourites": "Remove from favourites" + "removeFavourites": "Remove from favourites", + "scanQR": "Scan QR", + "actions": "Actions" } diff --git a/src/module/common/component/input/Button/Button.overrides.ts b/src/module/common/component/input/Button/Button.overrides.ts index bfdde77a9..2b888f4b5 100644 --- a/src/module/common/component/input/Button/Button.overrides.ts +++ b/src/module/common/component/input/Button/Button.overrides.ts @@ -3,6 +3,7 @@ import "@peersyst/react-native-components"; declare module "@peersyst/react-native-components" { export interface ButtonVariantOverrides { primary: true; + primarySoft: true; secondary: true; tertiary: true; quaternary: true; diff --git a/src/module/common/component/input/Button/Button.styles.ts b/src/module/common/component/input/Button/Button.styles.ts index 8f08bf574..c1298b563 100644 --- a/src/module/common/component/input/Button/Button.styles.ts +++ b/src/module/common/component/input/Button/Button.styles.ts @@ -1,7 +1,7 @@ import { Button } from "@peersyst/react-native-components"; import styled from "@peersyst/react-native-styled"; import { ButtonProps } from "./Button.types"; -import { emphasize } from "@peersyst/react-utils"; +import { alpha, emphasize } from "@peersyst/react-utils"; export const ButtonRoot = styled(Button)(({ theme, rounded = true }) => { const themeMode = theme.palette.mode; @@ -29,6 +29,10 @@ export const ButtonRoot = styled(Button)(({ theme, rounded = true } backgroundColor: theme.palette.primary, color: "#FFFFFF", }, + primarySoft: { + backgroundColor: alpha(theme.palette.primary, 0.12), + color: theme.palette.primary, + }, secondary: { backgroundColor: "#FFFFFF", color: "#000000", @@ -71,6 +75,9 @@ export const ButtonRoot = styled(Button)(({ theme, rounded = true } primary: { backgroundColor: emphasize(theme.palette.primary, 0.15), }, + primarySoft: { + backgroundColor: emphasize(alpha(theme.palette.primary, 0.12), 0.3), + }, secondary: { backgroundColor: emphasize("#FFFFFF", 0.02), }, diff --git a/src/module/common/component/navigation/BottomBar/BottomBar.tsx b/src/module/common/component/navigation/BottomBar/BottomBar.tsx index 8794f6d90..d68795bc6 100644 --- a/src/module/common/component/navigation/BottomBar/BottomBar.tsx +++ b/src/module/common/component/navigation/BottomBar/BottomBar.tsx @@ -7,7 +7,7 @@ import { DatabaseIcon } from "module/common/icons/DatabaseIcon"; import useTranslate from "module/common/hook/useTranslate"; import { capitalize } from "@peersyst/react-utils"; import { config } from "config"; -import BottomBarQRScanner from "./BottomBarQRScanner/BottomBarQRScanner"; +import QuickActionsBottomBarItem from "./QuickActionsBottomBarItem/QuickActionsBottomBarItem"; import { MainScreens } from "../MainNavigatorGroup/MainScreens"; type BottomBarProps = Pick; @@ -35,7 +35,7 @@ const BottomBar = ({ state, navigation }: BottomBarProps): JSX.Element => { label={capitalize(translate("staking"))} Icon={} /> - {config.signerFeature.enabled && } + {config.signerFeature.enabled && ( handleNavigation(MainScreens.DAPPS)} diff --git a/src/module/common/component/navigation/BottomBar/BottomBarQRScanner/BottomBarQRScanner.tsx b/src/module/common/component/navigation/BottomBar/BottomBarQRScanner/BottomBarQRScanner.tsx deleted file mode 100644 index 35bcd3528..000000000 --- a/src/module/common/component/navigation/BottomBar/BottomBarQRScanner/BottomBarQRScanner.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useState } from "react"; -import MainBottomBarItem from "../MainBottomBarItem/MainBottomBarItem"; -import { QRCodeIcon } from "icons"; -import useTranslate from "module/common/hook/useTranslate"; -import QrScanner from "module/common/component/input/QrScanner/QrScanner"; -import useSignerModal from "module/signer/hooks/useSignerModal"; -import { parseSignerDeepLinkData } from "module/signer/utils/parseSignerDeepLinkData"; -import { useToast } from "@peersyst/react-native-components"; - -const BottomBarQRScanner = (): JSX.Element => { - const translate = useTranslate(); - const translateError = useTranslate("error"); - const { showSignerModal } = useSignerModal(); - const { showToast } = useToast(); - - const [open, setOpen] = useState(false); - - const handleOpen = () => setOpen(true); - - const handleScan = (data: string) => { - setOpen(false); - const signerData = parseSignerDeepLinkData(data); - if (!signerData) showToast(translateError("invalidSignerRequest"), { type: "error" }); - else showSignerModal(signerData!.type, signerData!.id); - }; - - return ( - <> - - {open && setOpen(false)} onScan={({ data }) => handleScan(data)} />} - - ); -}; - -export default BottomBarQRScanner; diff --git a/src/module/common/component/navigation/BottomBar/MainBottomBarItem/MainBottomBarItem.styles.ts b/src/module/common/component/navigation/BottomBar/MainBottomBarItem/MainBottomBarItem.styles.ts index 83edff11f..07dac55d4 100644 --- a/src/module/common/component/navigation/BottomBar/MainBottomBarItem/MainBottomBarItem.styles.ts +++ b/src/module/common/component/navigation/BottomBar/MainBottomBarItem/MainBottomBarItem.styles.ts @@ -1,9 +1,21 @@ import styled from "@peersyst/react-native-styled"; -import Button from "module/common/component/input/Button/Button"; +import { Animated, Pressable } from "react-native"; +import { classify } from "@peersyst/react-utils"; +import { LinearGradient } from "expo-linear-gradient"; -export const MainBottomBarItemRoot = styled(Button)(() => ({ - lg: { - width: 70, - height: 70, - }, +export const MainBottomBarItemRoot = styled(Pressable)(() => ({ + width: 64, + height: 64, + borderRadius: 10000, +})); + +export const MainBottomBarItemBackgroundGradiend = styled(Animated.createAnimatedComponent(classify(LinearGradient)))(() => ({ + position: "absolute", + top: 0, + left: 0, + zIndex: -1, + elevation: -1, + width: "100%", + height: "100%", + borderRadius: 10000, })); diff --git a/src/module/common/component/navigation/BottomBar/MainBottomBarItem/MainBottomBarItem.tsx b/src/module/common/component/navigation/BottomBar/MainBottomBarItem/MainBottomBarItem.tsx index 1dc246014..90b98e847 100644 --- a/src/module/common/component/navigation/BottomBar/MainBottomBarItem/MainBottomBarItem.tsx +++ b/src/module/common/component/navigation/BottomBar/MainBottomBarItem/MainBottomBarItem.tsx @@ -1,31 +1,50 @@ -import { ButtonProps } from "module/common/component/input/Button/Button.types"; -import { MainBottomBarItemRoot } from "./MainBottomBarItem.styles"; -import { Col, SvgIconProps } from "@peersyst/react-native-components"; -import Typography from "module/common/component/display/Typography/Typography"; -import { JSXElementConstructor } from "react"; +import { MainBottomBarItemBackgroundGradiend, MainBottomBarItemRoot } from "./MainBottomBarItem.styles"; +import { Col } from "@peersyst/react-native-components"; +import { useEffect, useRef } from "react"; import useThemeMode from "module/common/hook/useThemeMode"; import { theme } from "config/theme/theme"; +import useWalletGradient from "module/wallet/hook/useWalletGradient"; +import { Animated, ViewStyle } from "react-native"; +import { NearIcon } from "icons"; -export interface MainBottomBarItemProps extends ButtonProps { - Icon: JSXElementConstructor; - label?: string; +export interface MainBottomBarItemProps { + onPress: () => void; + style?: ViewStyle; } -const MainBottomBarItem = ({ Icon, label, ...rest }: MainBottomBarItemProps): JSX.Element => { +const MainBottomBarItem = (props: MainBottomBarItemProps): JSX.Element => { const mode = useThemeMode(); const textColor = mode === "light" ? theme.palette.background : theme.palette.text; + const walletGradient = useWalletGradient(); + + const gradientAnim = useRef(new Animated.Value(1)).current; + + useEffect(() => { + Animated.timing(gradientAnim, { + toValue: 0, + duration: 300, + useNativeDriver: false, + }).start(); + }, []); + + const backgroundColor = walletGradient[0]; + const secondaryBackgroundColor = walletGradient[1]; + return ( - + - - {label && ( - - {label} - - )} + + ); }; diff --git a/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/QuickActionsBottomBarItem.tsx b/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/QuickActionsBottomBarItem.tsx new file mode 100644 index 000000000..8c0e5424d --- /dev/null +++ b/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/QuickActionsBottomBarItem.tsx @@ -0,0 +1,17 @@ +import MainBottomBarItem from "../MainBottomBarItem/MainBottomBarItem"; +import QuickActionsModal from "module/home/component/feedback/QuickActionsModal"; +import { useQuickActionsBottomBarItem } from "./hooks/useQuickActionsBottomBarItem"; + +const QuickActionsBottomBarItem = (): JSX.Element => { + const { modals, actions, open, showModal, hideModal } = useQuickActionsBottomBarItem(); + + return ( + <> + {modals} + + + + ); +}; + +export default QuickActionsBottomBarItem; diff --git a/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/hooks/useBuyQuickAction.tsx b/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/hooks/useBuyQuickAction.tsx new file mode 100644 index 000000000..63b40e9d3 --- /dev/null +++ b/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/hooks/useBuyQuickAction.tsx @@ -0,0 +1,18 @@ +import useNavigation from "module/common/hook/useNavigation"; +import { MainScreens } from "../../../MainNavigatorGroup/MainScreens"; + +export interface UseBuyQuickActionReturn { + handleBuyPress: () => void; +} + +export function useBuyQuickAction(): UseBuyQuickActionReturn { + const navigate = useNavigation(); + + function handleBuyPress() { + navigate.navigate(MainScreens.FIAT_ORDERS); + } + + return { + handleBuyPress, + }; +} diff --git a/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/hooks/useQuickActionsBottomBarItem.tsx b/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/hooks/useQuickActionsBottomBarItem.tsx new file mode 100644 index 000000000..ee378087d --- /dev/null +++ b/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/hooks/useQuickActionsBottomBarItem.tsx @@ -0,0 +1,96 @@ +import { capitalize } from "@peersyst/react-utils"; +import { ResourceKey } from "i18next"; +import { ArrowSendIcon, ArrowReceiveIcon, SwapIcon, BuyIcon } from "icons"; +import { useModalState } from "module/common/hook/useModalState"; +import useTranslate from "module/common/hook/useTranslate"; +import { QRCodeIcon } from "module/common/icons/QRCodeIcon"; +import { QuickAction } from "module/home/component/feedback/QuickActionsModal.types"; +import ReceiveModal from "module/transaction/component/core/ReceiveModal/ReceiveModal"; +import SendModal from "module/transaction/component/core/SendModal/SendModal"; +import { Fragment } from "react"; +import QrScanner from "module/common/component/input/QrScanner/QrScanner"; +import { useScanQuickAction } from "./useScanQuickAction"; +import { useSwapQuickAction } from "./useSwapQuickAction"; +import { useBuyQuickAction } from "./useBuyQuickAction"; +import { useConfig } from "@peersyst/react-native-components"; + +export interface UseQuickActionsBottomBarItemReturn { + actions: QuickAction[]; + modals: JSX.Element; + open: boolean; + showModal: () => void; + hideModal: () => void; +} + +export function useQuickActionsBottomBarItem(): UseQuickActionsBottomBarItemReturn { + const { open, showModal, hideModal } = useModalState(); + const translate = useTranslate(); + const signerFeatureConfig = useConfig("signerFeature"); + const { handleSwapPress } = useSwapQuickAction(); + const { handleBuyPress } = useBuyQuickAction(); + const { scanOpened, showScanModal, handleScan, hideScanModal } = useScanQuickAction(); + const { open: sendOpened, showModal: showSendModal, hideModal: hideSendModal } = useModalState(); + const { open: receiveOpened, showModal: showReceiveModal, hideModal: hideReceiveModal } = useModalState(); + + function getCapitalizedTranslation(key: ResourceKey): string { + return capitalize(translate(key)); + } + + function withHandleOnPress(callback: () => void) { + return () => { + hideModal(); + callback(); + }; + } + + const actions: QuickAction[] = [ + { + Icon: QRCodeIcon, + label: getCapitalizedTranslation("scanQR"), + onPress: withHandleOnPress(showScanModal), + variant: "primary", + }, + { + Icon: ArrowSendIcon, + label: getCapitalizedTranslation("send"), + onPress: withHandleOnPress(showSendModal), + variant: "soft", + }, + { + Icon: ArrowReceiveIcon, + label: getCapitalizedTranslation("receive"), + onPress: withHandleOnPress(showReceiveModal), + variant: "soft", + }, + ...(signerFeatureConfig.enabled + ? [ + { + Icon: SwapIcon, + label: getCapitalizedTranslation("swap"), + onPress: withHandleOnPress(handleSwapPress), + variant: "soft", + } as const, + ] + : []), + { + Icon: BuyIcon, + label: getCapitalizedTranslation("buy"), + onPress: withHandleOnPress(handleBuyPress), + variant: "soft", + }, + ]; + + return { + actions, + modals: ( + + handleScan(data)} /> + + + + ), + open, + showModal, + hideModal, + }; +} diff --git a/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/hooks/useScanQuickAction.tsx b/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/hooks/useScanQuickAction.tsx new file mode 100644 index 000000000..83cd29ae9 --- /dev/null +++ b/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/hooks/useScanQuickAction.tsx @@ -0,0 +1,33 @@ +import { useModalState } from "module/common/hook/useModalState"; +import useTranslate from "module/common/hook/useTranslate"; +import { parseSignerDeepLinkData } from "module/signer/utils/parseSignerDeepLinkData"; +import useSignerModal from "module/signer/hooks/useSignerModal"; +import { useToast } from "@peersyst/react-native-components"; + +export interface UseScanQuickActionReturn { + handleScan: (data: string) => void; + scanOpened: boolean; + showScanModal: () => void; + hideScanModal: () => void; +} + +export function useScanQuickAction(): UseScanQuickActionReturn { + const translateError = useTranslate("error"); + const { showSignerModal } = useSignerModal(); + const { showToast } = useToast(); + const { open: scanOpened, showModal: showScanModal, hideModal: hideScanModal } = useModalState(); + + const handleScan = (data: string) => { + hideScanModal(); + const signerData = parseSignerDeepLinkData(data); + if (!signerData) showToast(translateError("invalidSignerRequest"), { type: "error" }); + else showSignerModal(signerData!.type, signerData!.id); + }; + + return { + handleScan, + scanOpened, + showScanModal, + hideScanModal, + }; +} diff --git a/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/hooks/useSwapQuickAction.tsx b/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/hooks/useSwapQuickAction.tsx new file mode 100644 index 000000000..a27eb6187 --- /dev/null +++ b/src/module/common/component/navigation/BottomBar/QuickActionsBottomBarItem/hooks/useSwapQuickAction.tsx @@ -0,0 +1,22 @@ +import useGetSwapLink from "module/common/hook/useGetSwapLink"; +import useNavigation from "module/common/hook/useNavigation"; +import { MainScreens } from "../../../MainNavigatorGroup/MainScreens"; +import { DAppScreens } from "module/dapp/navigator/DAppsNavigator.types"; + +export interface UseSwapQuickActionReturn { + handleSwapPress: () => void; +} + +export function useSwapQuickAction(): UseSwapQuickActionReturn { + const navigate = useNavigation(); + const uriSwap = useGetSwapLink(); + + function handleSwapPress(): void { + // We need to cast the params of the navigate.navigate to any because the React Navigation types are not working properly + navigate.navigate(MainScreens.DAPPS, { screen: DAppScreens.WEBVIEW, params: { url: uriSwap } } as any); + } + + return { + handleSwapPress, + }; +} diff --git a/src/module/home/component/feedback/QuickActionButton/QuickActionButton.styles.ts b/src/module/home/component/feedback/QuickActionButton/QuickActionButton.styles.ts new file mode 100644 index 000000000..7dca90112 --- /dev/null +++ b/src/module/home/component/feedback/QuickActionButton/QuickActionButton.styles.ts @@ -0,0 +1,19 @@ +import { ElementStyler } from "@peersyst/react-native-components"; +import styled from "@peersyst/react-native-styled"; +import { View } from "react-native"; + +export const QuickActionsButtonPrimaryIcon = styled(ElementStyler)(({ theme }) => ({ + color: theme.palette.white, + fontSize: 24, +})); + +export const QuickActionsButtonPrimaryIconPositioner = styled(View)(() => ({ + position: "absolute", + left: 0, + zIndex: 2, +})); + +export const QuickActionsButtonSoftIcon = styled(ElementStyler)(({ theme }) => ({ + color: theme.palette.primary, + fontSize: 24, +})); diff --git a/src/module/home/component/feedback/QuickActionButton/QuickActionButton.tsx b/src/module/home/component/feedback/QuickActionButton/QuickActionButton.tsx new file mode 100644 index 000000000..291754a00 --- /dev/null +++ b/src/module/home/component/feedback/QuickActionButton/QuickActionButton.tsx @@ -0,0 +1,45 @@ +import Button from "module/common/component/input/Button/Button"; +import { QuickAction } from "../QuickActionsModal.types"; +import Typography from "module/common/component/display/Typography/Typography"; +import { Row } from "@peersyst/react-native-components"; +import { + QuickActionsButtonPrimaryIcon, + QuickActionsButtonPrimaryIconPositioner, + QuickActionsButtonSoftIcon, +} from "./QuickActionButton.styles"; + +export interface QuickActionButtonProps { + quickAction: QuickAction; +} + +export function QuickActionButton({ quickAction: { variant, Icon, label, ...rest } }: QuickActionButtonProps): JSX.Element { + if (variant === "primary") { + return ( + + ); + } else { + return ( + + ); + } +} diff --git a/src/module/home/component/feedback/QuickActionsModal.tsx b/src/module/home/component/feedback/QuickActionsModal.tsx new file mode 100644 index 000000000..689c78a80 --- /dev/null +++ b/src/module/home/component/feedback/QuickActionsModal.tsx @@ -0,0 +1,43 @@ +import CardModal from "module/common/component/feedback/CardModal/CardModal"; +import { CardModalProps } from "module/common/component/feedback/CardModal/CardModal.types"; +import ModalHeader from "module/common/component/navigation/ModalHeader/ModalHeader"; +import useTranslate from "module/common/hook/useTranslate"; +import { QuickAction } from "./QuickActionsModal.types"; +import { Col } from "@peersyst/react-native-components"; +import { QuickActionButton } from "./QuickActionButton/QuickActionButton"; + +export type QuickActionsModalProps = Omit & { + quickActions: QuickAction[]; +}; + +const QuickActionsModal = ({ onClose, quickActions, ...rest }: QuickActionsModalProps): JSX.Element => { + const translate = useTranslate(); + + return ( + <> + + {(open, setOpen) => ({ + header: ( + { + setOpen(false); + if (open !== undefined) onClose?.(); + }} + /> + ), + body: ( + + {quickActions.map((quickAction, index) => ( + + ))} + + ), + })} + + + ); +}; + +export default QuickActionsModal; diff --git a/src/module/home/component/feedback/QuickActionsModal.types.ts b/src/module/home/component/feedback/QuickActionsModal.types.ts new file mode 100644 index 000000000..e98a09df9 --- /dev/null +++ b/src/module/home/component/feedback/QuickActionsModal.types.ts @@ -0,0 +1,9 @@ +import { SvgIconProps } from "@peersyst/react-native-components"; +import { JSXElementConstructor } from "react"; + +export interface QuickAction { + label: string; + Icon: JSXElementConstructor; + onPress: () => void; + variant: "primary" | "soft"; +}