feat(gui): Display dialog when update is available (#132)

* feat(tauri): Use new tauri signing key

* feat(gui): Display MUI dialog when update is available
This commit is contained in:
binarybaron 2024-11-05 21:01:23 +01:00 committed by GitHub
parent a9b1d05af0
commit c1afc7aa2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 170 additions and 32 deletions

View File

@ -9,6 +9,7 @@ import HistoryPage from "./pages/history/HistoryPage";
import SwapPage from "./pages/swap/SwapPage";
import WalletPage from "./pages/wallet/WalletPage";
import GlobalSnackbarProvider from "./snackbar/GlobalSnackbarProvider";
import UpdaterDialog from "./modal/updater/UpdaterDialog";
const useStyles = makeStyles((theme) => ({
innerContent: {
@ -58,6 +59,7 @@ export default function App() {
<Router>
<Navigation />
<InnerContent />
<UpdaterDialog/>
</Router>
</GlobalSnackbarProvider>
</ThemeProvider>

View File

@ -0,0 +1,166 @@
import { useEffect, useState } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
Button,
LinearProgress,
Typography,
makeStyles,
LinearProgressProps,
Box,
} from '@material-ui/core';
import SystemUpdateIcon from '@material-ui/icons/SystemUpdate';
import { check, Update, DownloadEvent } from '@tauri-apps/plugin-updater';
import { useSnackbar } from 'notistack';
import { relaunch } from '@tauri-apps/plugin-process';
const useStyles = makeStyles((theme) => ({
progress: {
marginTop: theme.spacing(2)
},
releaseNotes: {
marginTop: theme.spacing(2),
marginBottom: theme.spacing(1)
},
noteContent: {
whiteSpace: 'pre-line'
}
}));
interface DownloadProgress {
contentLength: number | null;
downloadedBytes: number;
}
function LinearProgressWithLabel(props: LinearProgressProps & { label?: string }) {
return (
<Box display="flex" alignItems="center">
<Box width="100%" mr={1}>
<LinearProgress variant="determinate" {...props} />
</Box>
<Box minWidth={85}>
<Typography variant="body2" color="textSecondary">
{props.label || `${Math.round(props.value)}%`}
</Typography>
</Box>
</Box>
);
}
export default function UpdaterDialog() {
const classes = useStyles();
const [availableUpdate, setAvailableUpdate] = useState<Update | null>(null);
const [downloadProgress, setDownloadProgress] = useState<DownloadProgress | null>(null);
const {enqueueSnackbar} = useSnackbar();
useEffect(() => {
// Check for updates when component mounts
check().then((updateResponse) => {
setAvailableUpdate(updateResponse);
}).catch((err) => {
enqueueSnackbar(`Failed to check for updates: ${err}`, {
variant: 'error',
});
});
}, []);
// If no update is available, don't render the dialog
if (!availableUpdate?.available) return null;
function hideNotification() {
setAvailableUpdate(null);
};
async function handleInstall() {
try {
await availableUpdate.downloadAndInstall((event: DownloadEvent) => {
if (event.event === 'Started') {
setDownloadProgress({
contentLength: event.data.contentLength || null,
downloadedBytes: 0,
});
} else if (event.event === 'Progress') {
setDownloadProgress(prev => ({
...prev,
downloadedBytes: prev.downloadedBytes + event.data.chunkLength,
}));
} else if (event.event === 'Finished') {
// Relaunch the application for the new version to be used
relaunch();
}
});
} catch (err) {
enqueueSnackbar(`Failed to install update: ${err}`, {
variant: "error"
});
}
};
const isDownloading = downloadProgress !== null;
const progress = isDownloading
? Math.round((downloadProgress.downloadedBytes / downloadProgress.contentLength) * 100)
: 0;
return (
<Dialog
fullWidth
maxWidth="sm"
open={availableUpdate.available}
onClose={hideNotification}
>
<DialogTitle>Update Available</DialogTitle>
<DialogContent>
<DialogContentText>
A new version (v{availableUpdate.version}) is available. Your current version is {availableUpdate.currentVersion}.
The update will be verified using PGP signature verification to ensure authenticity.
{availableUpdate.body && (
<>
<Typography variant="h6" className={classes.releaseNotes}>
Release Notes:
</Typography>
<Typography variant="body2" component="div" className={classes.noteContent}>
{availableUpdate.body}
</Typography>
</>
)}
</DialogContentText>
{isDownloading && (
<Box className={classes.progress}>
<LinearProgressWithLabel
value={progress}
label={`${(downloadProgress.downloadedBytes / 1024 / 1024).toFixed(1)} MB${
downloadProgress.contentLength
? ` / ${(downloadProgress.contentLength / 1024 / 1024).toFixed(1)} MB`
: ''
}`}
/>
</Box>
)}
</DialogContent>
<DialogActions>
<Button
variant="text"
color="default"
onClick={hideNotification}
disabled={isDownloading}
>
Remind me later
</Button>
<Button
endIcon={<SystemUpdateIcon />}
variant="contained"
color="primary"
onClick={handleInstall}
disabled={isDownloading}
>
{isDownloading ? 'DOWNLOADING...' : 'INSTALL UPDATE'}
</Button>
</DialogActions>
</Dialog>
);
}

View File

@ -18,8 +18,6 @@ import {
import App from "./components/App";
import { initEventListeners } from "./rpc";
import { persistor, store } from "./store/storeRenderer";
import { Box } from "@material-ui/core";
import { checkForAppUpdates } from "./updater";
const container = document.getElementById("root");
const root = createRoot(container!);
@ -76,5 +74,4 @@ async function fetchInitialData() {
}
fetchInitialData();
initEventListeners();
checkForAppUpdates();
initEventListeners();

View File

@ -1,27 +0,0 @@
import { check } from "@tauri-apps/plugin-updater";
import { ask, message } from "@tauri-apps/plugin-dialog";
import { relaunch } from "@tauri-apps/plugin-process";
export async function checkForAppUpdates() {
const update = await check();
if (update?.available) {
const yes = await ask(
`
Update to ${update.version} is available!
Release notes: ${update.body}
`,
{
title: "Update Now!",
kind: "info",
okLabel: "Update",
cancelLabel: "Cancel",
}
);
if (yes) {
await update.downloadAndInstall();
await relaunch();
}
}
}

View File

@ -41,7 +41,7 @@
"endpoints": [
"https://cdn.crabnebula.app/update/unstoppableswap/unstoppableswap-gui-rs/{{target}}-{{arch}}/{{current_version}}"
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyNTQ3NTQxQTQ2MkI4N0IKUldSN3VHS2tRWFZVMGpWYytkRFg4dFBzNEh5ZnlxaHBubGpRalVMMG5nVytiR3JPOUE3QjRxc00K"
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEE2MDgxRDEwMDZENkYxNUMKUldSYzhkWUdFQjBJcGwzN24yZlduTzNndFZnVW9Qa1k2WFVTMEMxcHBSc2dSVVlzbVNHdGNFQ0EK"
},
"cli": {
"description": "Start the GUI application",