Skip to content

Commit

Permalink
Merge pull request #25 from displaynone:save-restore
Browse files Browse the repository at this point in the history
feat: added backup and restore sites
  • Loading branch information
displaynone authored Apr 23, 2023
2 parents 7068b78 + 7a28a51 commit 60bac53
Show file tree
Hide file tree
Showing 19 changed files with 1,890 additions and 44 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,6 @@ android/

.env

# @end expo-cli
# @end expo-cli

.eslintcache
1 change: 1 addition & 0 deletions app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ module.exports = {
plugins: [
'expo-localization',
'@morrowdigital/watermelondb-expo-plugin',
'expo-document-picker',
[
'expo-build-properties',
{
Expand Down
2 changes: 1 addition & 1 deletion app/(home)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const Home: FC = () => {

return (
<>
<Progress />
{!!sites.length && <Progress />}
<SitesList sites={sites} deleteSite={deleteSite} />
</>
);
Expand Down
93 changes: 93 additions & 0 deletions app/settings/export.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Trans } from '@lingui/macro';
import * as FileSystem from 'expo-file-system';
import * as Sharing from 'expo-sharing';
import React, { FC } from 'react';
import { Dimensions, StyleSheet, View } from 'react-native';
import { Button, MD3Theme, useTheme } from 'react-native-paper';
import { ExportIcon } from '../../src/icons/Export';
import Site from '../../src/models/Site';
import { useDB } from '../../src/providers/DatabaseProvider';
import Container from '../../src/ui/Container';
import Text from '../../src/ui/Text';
import { format } from 'date-fns';

const SettingsExport: FC = () => {
const { listSites } = useDB();
const theme = useTheme();
const styles = getStyles(theme);

const downloadStringAsFile = async () => {
const sites = await listSites();
const content = JSON.stringify(
sites.map(
site =>
({
label: site.label,
secret: site.secret,
algorithm: site.algorithm,
digits: site.digits,
issuer: site.issuer,
type: site.type,
period: site.period,
} as Site),
),
null,
2,
);

const fileUri =
(FileSystem.documentDirectory || '') +
'shield-authenticator_' +
format(Date.now(), 'yyyy-MM-dd-HH-mm-ss') +
'.json';
await FileSystem.writeAsStringAsync(fileUri, content);

const isAvailable = await Sharing.isAvailableAsync();
if (!isAvailable) {
alert('Sharing is not available on your device.');
return;
}

await Sharing.shareAsync(fileUri);
};

return (
<Container>
<View style={styles.container}>
<Text size="headlineSmall" variant={['bold', 'primary']}>
<Trans>Backup your data</Trans>
</Text>
<Text size="bodyLarge" variant={'secondary'} numberOfLines={3}>
<Trans>
To ensure that you don't lose your saved data, you can download a
file of the sites and restore them at a later time
</Trans>
</Text>

<ExportIcon
width={Dimensions.get('screen').width - 48}
height={Dimensions.get('screen').width - 48}
/>
<View style={styles.buttonContainer}>
<Button mode="contained" onPress={() => downloadStringAsFile()}>
<Trans>Generate file</Trans>
</Button>
</View>
</View>
</Container>
);
};

const getStyles = (theme: MD3Theme) =>
StyleSheet.create({
container: {
flex: 1,
padding: 0,
margin: 0,
},
buttonContainer: {
marginTop: 24,
},
});

export default SettingsExport;
123 changes: 123 additions & 0 deletions app/settings/import.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { Plural, Trans } from '@lingui/macro';
import * as FileSystem from 'expo-file-system';
import * as Sharing from 'expo-sharing';
import React, { FC, useState } from 'react';
import { Dimensions, StyleSheet, View } from 'react-native';
import { Button, MD3Theme, ProgressBar, useTheme } from 'react-native-paper';
import { ImportIcon } from '../../src/icons/Import';
import Site from '../../src/models/Site';
import { useDB } from '../../src/providers/DatabaseProvider';
import Container from '../../src/ui/Container';
import Text from '../../src/ui/Text';
import * as DocumentPicker from 'expo-document-picker';
import { OtpRecord } from '../../src/types';

const SettingsImport: FC = () => {
const { newSite } = useDB();
const theme = useTheme();
const styles = getStyles(theme);
const [processing, setProcessing] = useState(false);
const [numberOfSites, setNumberOfSites] = useState(0);
const [sitesProcessed, setSitesProcessed] = useState(0);

const loadBackupFile = async () => {
const result = await DocumentPicker.getDocumentAsync({});
if (result.type === 'success') {
const content = await FileSystem.readAsStringAsync(result.uri);
try {
const sites = JSON.parse(content) as Site[];
if (!sites.length) {
throw new Error();
}
setNumberOfSites(sites.length);
setProcessing(true);
for (let i = 0; i < sites.length; i++) {
const site = sites[i];
await newSite(
{
algorithm: site.algorithm,
digits: site.digits,
label: site.label,
period: site.period,
secret: site.secret,
type: site.type,
issuer: site.issuer,
} as OtpRecord,
false,
);
setSitesProcessed(value => value + 1);
}
} catch (e) {
alert('The format of the file is not valid');
}
}
};

return (
<Container>
<View style={styles.container}>
<Text size="headlineSmall" variant={['bold', 'primary']}>
<Trans>Restore sites</Trans>
</Text>
<Text size="bodyLarge" variant={'secondary'} numberOfLines={5}>
<Trans>
If you want to restore your saved sites from a backup, you can load
the file stored on your device. It's important to note that the
existing sites will remain unchanged and won't be replaced
</Trans>
</Text>

<ImportIcon
width={Dimensions.get('screen').width - 48}
height={Dimensions.get('screen').width - 48}
/>
{processing && (
<View style={styles.buttonContainer}>
<Text size="labelLarge" variant={['bold', 'primary', 'marginless']}>
{sitesProcessed !== numberOfSites && <Trans>Processing</Trans>}
{sitesProcessed === numberOfSites && <Trans>Completed</Trans>}
</Text>
<View style={styles.processContainer}>
<ProgressBar progress={0} />
</View>
<Text>
<Plural
value={sitesProcessed}
one={<Trans>Processed 1 site of {numberOfSites}</Trans>}
other={<Trans>Processed # sites of {numberOfSites}</Trans>}
/>
</Text>
</View>
)}
{!processing && (
<View style={styles.buttonContainer}>
<Button
mode="contained"
onPress={() => loadBackupFile()}
disabled={processing}
>
<Trans>Load backup sites</Trans>
</Button>
</View>
)}
</View>
</Container>
);
};

const getStyles = (theme: MD3Theme) =>
StyleSheet.create({
container: {
flex: 1,
padding: 0,
margin: 0,
},
buttonContainer: {
marginTop: 24,
},
processContainer: {
marginVertical: 16,
},
});

export default SettingsImport;
Binary file modified build/latest.apk
Binary file not shown.
36 changes: 33 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,28 @@
"@react-native-masked-view/masked-view": "0.2.8",
"@react-navigation/native": "^6.1.4",
"@react-navigation/stack": "^6.3.14",
"@shopify/flash-list": "1.4.0",
"date-fns": "^2.29.3",
"dotenv": "^16.0.3",
"expo": "~48.0.6",
"expo-background-fetch": "~11.1.1",
"expo-barcode-scanner": "~12.3.2",
"expo-build-properties": "~0.5.1",
"expo-camera": "~13.2.1",
"expo-clipboard": "~4.1.2",
"expo-constants": "~14.2.1",
"expo-document-picker": "~11.2.2",
"expo-file-system": "~15.2.2",
"expo-font": "~11.1.1",
"expo-linking": "~4.0.1",
"expo-local-authentication": "~13.2.1",
"expo-localization": "~14.1.1",
"expo-router": "^1.0.1",
"expo-secure-store": "~12.1.1",
"expo-sharing": "~11.2.2",
"expo-splash-screen": "~0.18.1",
"expo-status-bar": "~1.4.4",
"expo-task-manager": "~11.1.1",
"make-plural": "^7.2.0",
"react": "18.2.0",
"react-dom": "18.2.0",
Expand All @@ -55,10 +62,7 @@
"react-native-url-polyfill": "^1.3.0",
"react-native-vector-icons": "^9.2.0",
"react-native-web": "~0.18.11",
"totp-generator": "^0.0.14",
"@shopify/flash-list": "1.4.0",
"expo-task-manager": "~11.1.1",
"expo-background-fetch": "~11.1.1"
"totp-generator": "^0.0.14"
},
"jest": {
"preset": "jest-expo",
Expand Down
2 changes: 1 addition & 1 deletion src/components/AddSite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const AddSite: FC = () => {
icon="plus"
style={styles.fab}
onPress={() => push('/qr')}
label={t`Add site`}
label={t`Add new site`}
/>
);
};
Expand Down
Loading

0 comments on commit 60bac53

Please sign in to comment.