feat(gui): Button to open modal with QR code of Bitcoin address (#116)

This commit is contained in:
binarybaron 2024-10-14 04:01:11 +06:00 committed by GitHub
parent 2bffe40a37
commit 15b43bf4a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 134 additions and 71 deletions

View File

@ -216,7 +216,7 @@ export default function SwapStatusAlert({
<Alert
key={swap.swap_id}
severity="warning"
action={<SwapResumeButton swap={swap} />}
action={<SwapResumeButton swap={swap}>Resume Swap</SwapResumeButton>}
variant="filled"
>
<AlertTitle>

View File

@ -1,7 +1,6 @@
import { Box } from "@material-ui/core";
import { ReactNode } from "react";
import CopyableMonospaceTextBox from "renderer/components/other/CopyableMonospaceTextBox";
import BitcoinQrCode from "./BitcoinQrCode";
import ActionableMonospaceTextBox from "renderer/components/other/ActionableMonospaceTextBox";
import InfoBox from "./InfoBox";
type Props = {
@ -20,7 +19,7 @@ export default function DepositAddressInfoBox({
return (
<InfoBox
title={title}
mainContent={<CopyableMonospaceTextBox address={address} />}
mainContent={<ActionableMonospaceTextBox content={address} displayCopyIcon={true} enableQrCode={true} />}
additionalContent={
<Box
style={{
@ -31,7 +30,6 @@ export default function DepositAddressInfoBox({
}}
>
<Box>{additionalContent}</Box>
<BitcoinQrCode address={address} />
</Box>
}
icon={icon}

View File

@ -0,0 +1,120 @@
import { Box, Button, IconButton, Tooltip, makeStyles } from "@material-ui/core";
import { FileCopyOutlined, CropFree as CropFreeIcon } from "@material-ui/icons";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { useState } from "react";
import MonospaceTextBox from "./MonospaceTextBox";
import { Modal } from "@material-ui/core";
import QRCode from "react-qr-code";
type ModalProps = {
open: boolean;
onClose: () => void;
content: string;
};
type Props = {
content: string;
displayCopyIcon?: boolean;
enableQrCode?: boolean;
};
const useStyles = makeStyles((theme) => ({
container: {
display: "flex",
alignItems: "center",
cursor: "pointer",
},
textBoxWrapper: {
flexGrow: 1,
},
iconButton: {
marginLeft: theme.spacing(1),
},
modalContent: {
display: "flex",
flexDirection: "column",
gap: theme.spacing(2),
justifyContent: "center",
alignItems: "center",
height: "100%",
},
qrCode: {
maxWidth: "90%",
maxHeight: "90%",
},
}));
function QRCodeModal({ open, onClose, content }: ModalProps) {
const classes = useStyles();
return (
<Modal open={open} onClose={onClose}>
<Box className={classes.modalContent}>
<QRCode
value={content}
size={500}
className={classes.qrCode}
viewBox="0 0 500 500"
/>
<Button onClick={onClose} size="large" variant="contained" color="primary">
Done
</Button>
</Box>
</Modal>
);
}
export default function ActionableMonospaceTextBox({
content,
displayCopyIcon = true,
enableQrCode = true,
}: Props) {
const classes = useStyles();
const [copied, setCopied] = useState(false);
const [qrCodeOpen, setQrCodeOpen] = useState(false);
const [isQrCodeButtonHovered, setIsQrCodeButtonHovered] = useState(false);
const handleCopy = async () => {
await writeText(content);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<>
<Tooltip title={isQrCodeButtonHovered ? "" : (copied ? "Copied to clipboard" : "Click to copy")} arrow>
<Box className={classes.container}>
<Box className={classes.textBoxWrapper} onClick={handleCopy}>
<MonospaceTextBox>
{content}
{displayCopyIcon && (
<IconButton onClick={handleCopy} size="small" className={classes.iconButton}>
<FileCopyOutlined />
</IconButton>
)}
{enableQrCode && (
<Tooltip title="Show QR Code" arrow>
<IconButton
onClick={() => setQrCodeOpen(true)}
onMouseEnter={() => setIsQrCodeButtonHovered(true)}
onMouseLeave={() => setIsQrCodeButtonHovered(false)}
size="small"
className={classes.iconButton}
>
<CropFreeIcon />
</IconButton>
</Tooltip>
)}
</MonospaceTextBox>
</Box>
</Box>
</Tooltip>
{enableQrCode && (
<QRCodeModal
open={qrCodeOpen}
onClose={() => setQrCodeOpen(false)}
content={content}
/>
)}
</>
);
}

View File

@ -1,46 +0,0 @@
import { Box, Tooltip } from "@material-ui/core";
import { FileCopyOutlined } from "@material-ui/icons";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { useState } from "react";
import MonospaceTextBox from "./MonospaceTextBox";
type Props = {
address: string;
noIcon?: boolean;
};
/** Display addresses monospaced and clickable such that a click copies the address to the clipboard. */
export default function CopyableMonospaceTextBox({
address,
noIcon = false,
}: Props) {
// Signal that the address was copied
const [copied, setCopied] = useState(false);
const tooltip = copied ? "Copied to clipboard" : "Click to copy";
// Copy address to clipboard on-click
const handleClick = async () => {
// Copy to clipboard
await writeText(address);
// Change tooltip to show that we copied the address
setCopied(true);
// After a delay, show default tooltip again (2sec)
setTimeout(() => setCopied(false), 2_000);
};
// Apply icon unless specified otherwise
const icon = noIcon ? null : <FileCopyOutlined />;
return (
<Tooltip title={tooltip} arrow>
{/* Div is necessary to make the tooltip work */}
<Box style={{ cursor: "pointer" }}>
<MonospaceTextBox
content={address}
endIcon={icon}
onClick={handleClick}
/>
</Box>
</Tooltip>
);
}

View File

@ -1,10 +1,7 @@
import { Box, Typography, makeStyles } from "@material-ui/core";
import { ReactNode } from "react";
type Props = {
content: string;
onClick?: (content: string) => void;
endIcon?: ReactNode;
children: React.ReactNode;
};
const useStyles = makeStyles((theme) => ({
@ -14,31 +11,25 @@ const useStyles = makeStyles((theme) => ({
backgroundColor: theme.palette.grey[900],
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(1),
gap: theme.spacing(1),
},
content: {
wordBreak: "break-word",
whiteSpace: "pre-wrap",
fontFamily: "monospace",
lineHeight: "1.5em",
lineHeight: 1.5,
display: "flex",
alignItems: "center",
},
}));
export default function MonospaceTextBox({ content, endIcon, onClick }: Props) {
export default function MonospaceTextBox({ children }: Props) {
const classes = useStyles();
const handleClick = () => onClick?.(content);
return (
<Box className={classes.root} onClick={handleClick}>
<Typography
component="span"
variant="overline"
className={classes.content}
>
{content}
<Box className={classes.root}>
<Typography component="span" variant="overline" className={classes.content}>
{children}
</Typography>
{endIcon}
</Box>
);
}
}

View File

@ -10,7 +10,7 @@ import {
} from "@material-ui/core";
import { OpenInNew } from "@material-ui/icons";
import { GetSwapInfoResponse } from "models/tauriModel";
import CopyableMonospaceTextBox from "renderer/components/other/CopyableMonospaceTextBox";
import ActionableMonospaceTextBox from "renderer/components/other/ActionableMonospaceTextBox";
import MonospaceTextBox from "renderer/components/other/MonospaceTextBox";
import {
MoneroBitcoinExchangeRate,
@ -90,7 +90,7 @@ export default function HistoryRowExpanded({
<TableCell>
<Box>
{swap.seller.addresses.map((addr) => (
<CopyableMonospaceTextBox key={addr} address={addr} />
<ActionableMonospaceTextBox key={addr} content={addr} displayCopyIcon={false} enableQrCode={false} />
))}
</Box>
</TableCell>