From a359f8b8fc89a362d735b789ab18e37516747bce Mon Sep 17 00:00:00 2001 From: mashazyu Date: Thu, 3 Jan 2019 16:15:18 +0100 Subject: [PATCH 01/13] create settings segment component, create new section on settings page --- .../export-dialog.js | 0 .../import-dialog.js | 0 components/settings/data-management/index.js | 35 +++++++++++++ components/settings/import-export/index.js | 50 ------------------- components/settings/index.js | 4 +- components/settings/password/create.js | 2 +- .../settings/password/enter-new-password.js | 2 +- components/settings/password/update.js | 2 +- .../{password => }/settings-button.js | 4 +- components/settings/settings-menu.js | 4 +- components/settings/settings-segment.js | 21 ++++++++ i18n/en/labels.js | 2 +- i18n/en/settings.js | 6 ++- 13 files changed, 70 insertions(+), 62 deletions(-) rename components/settings/{import-export => data-management}/export-dialog.js (100%) rename components/settings/{import-export => data-management}/import-dialog.js (100%) create mode 100644 components/settings/data-management/index.js delete mode 100644 components/settings/import-export/index.js rename components/settings/{password => }/settings-button.js (84%) create mode 100644 components/settings/settings-segment.js diff --git a/components/settings/import-export/export-dialog.js b/components/settings/data-management/export-dialog.js similarity index 100% rename from components/settings/import-export/export-dialog.js rename to components/settings/data-management/export-dialog.js diff --git a/components/settings/import-export/import-dialog.js b/components/settings/data-management/import-dialog.js similarity index 100% rename from components/settings/import-export/import-dialog.js rename to components/settings/data-management/import-dialog.js diff --git a/components/settings/data-management/index.js b/components/settings/data-management/index.js new file mode 100644 index 0000000..52e4717 --- /dev/null +++ b/components/settings/data-management/index.js @@ -0,0 +1,35 @@ +import React from 'react' +import { ScrollView } from 'react-native' +import labels from '../../../i18n/en/settings' +import AppText from '../../app-text' +import SettingsSegment from '../settings-segment' +import SettingsButton from '../settings-button' +import openImportDialogAndImport from './import-dialog' +import openShareDialogAndExport from './export-dialog' + +const Settings = () => { + return ( + + + {labels.export.segmentExplainer} + + {labels.export.button} + + + + {labels.import.segmentExplainer} + + {labels.import.button} + + + + {labels.deleteSegment.explainer} + + {labels.deleteSegment.title} + + + + ) +} + +export default Settings diff --git a/components/settings/import-export/index.js b/components/settings/import-export/index.js deleted file mode 100644 index 430f29b..0000000 --- a/components/settings/import-export/index.js +++ /dev/null @@ -1,50 +0,0 @@ -import React, { Component } from 'react' -import { - View, ScrollView, - TouchableOpacity, -} from 'react-native' -import styles from '../../../styles/index' -import labels from '../../../i18n/en/settings' -import AppText from '../../app-text' -import openImportDialogAndImport from './import-dialog' -import openShareDialogAndExport from './export-dialog' - -export default class Settings extends Component { - constructor(props) { - super(props) - this.state = {} - } - - render() { - return ( - - - - {labels.export.button} - - {labels.export.segmentExplainer} - - - {labels.export.button} - - - - - - {labels.import.button} - - {labels.import.segmentExplainer} - - - {labels.import.button} - - - - - ) - } -} diff --git a/components/settings/index.js b/components/settings/index.js index a1eaa03..7bbbdc5 100644 --- a/components/settings/index.js +++ b/components/settings/index.js @@ -1,9 +1,9 @@ import Reminders from './reminders' import NfpSettings from './nfp-settings' -import ImportExport from './import-export' +import DataManagement from './data-management' import Password from './password' import About from './about' export default { - Reminders, NfpSettings, ImportExport, Password, About + Reminders, NfpSettings, DataManagement, Password, About } diff --git a/components/settings/password/create.js b/components/settings/password/create.js index bda688f..cdc9b69 100644 --- a/components/settings/password/create.js +++ b/components/settings/password/create.js @@ -2,7 +2,7 @@ import React, { Component } from 'react' import { View } from 'react-native' import settings from '../../../i18n/en/settings' import EnterNewPassword from './enter-new-password' -import SettingsButton from './settings-button' +import SettingsButton from '../settings-button' import showBackUpReminder from './show-backup-reminder' export default class CreatePassword extends Component { diff --git a/components/settings/password/enter-new-password.js b/components/settings/password/enter-new-password.js index ed09ead..084a6bc 100644 --- a/components/settings/password/enter-new-password.js +++ b/components/settings/password/enter-new-password.js @@ -5,7 +5,7 @@ import nodejs from 'nodejs-mobile-react-native' import { requestHash, changeEncryptionAndRestartApp } from '../../../db' import AppText from '../../app-text' import PasswordField from './password-field' -import SettingsButton from './settings-button' +import SettingsButton from '../settings-button' import styles from '../../../styles' import settings from '../../../i18n/en/settings' diff --git a/components/settings/password/update.js b/components/settings/password/update.js index a246c0e..bac6d2b 100644 --- a/components/settings/password/update.js +++ b/components/settings/password/update.js @@ -6,7 +6,7 @@ import settings from '../../../i18n/en/settings' import { requestHash } from '../../../db' import EnterNewPassword from './enter-new-password' import PasswordField from './password-field' -import SettingsButton from './settings-button' +import SettingsButton from '../settings-button' import showBackUpReminder from './show-backup-reminder' import checkCurrentPassword from './check-current-password' diff --git a/components/settings/password/settings-button.js b/components/settings/settings-button.js similarity index 84% rename from components/settings/password/settings-button.js rename to components/settings/settings-button.js index 97272bb..bfa8983 100644 --- a/components/settings/password/settings-button.js +++ b/components/settings/settings-button.js @@ -2,8 +2,8 @@ import React from 'react' import PropTypes from 'prop-types' import { TouchableOpacity } from 'react-native' -import AppText from '../../app-text' -import styles from '../../../styles' +import AppText from '../app-text' +import styles from '../../styles' const SettingsButton = ({ children, ...props }) => { return ( diff --git a/components/settings/settings-menu.js b/components/settings/settings-menu.js index 4152c11..3d927e0 100644 --- a/components/settings/settings-menu.js +++ b/components/settings/settings-menu.js @@ -7,14 +7,12 @@ import styles from '../../styles/index' import settingsLabels from '../../i18n/en/settings' import AppText from '../app-text' -console.log(settingsLabels.menuTitles) const labels = settingsLabels.menuTitles -console.log(settingsLabels.menuTitles) const menu = [ {title: labels.reminders, component: 'Reminders'}, {title: labels.nfpSettings, component: 'NfpSettings'}, - {title: labels.importExport, component: 'ImportExport'}, + {title: labels.dataManagement, component: 'DataManagement'}, {title: labels.password, component: 'Password'}, {title: labels.about, component: 'About'} ] diff --git a/components/settings/settings-segment.js b/components/settings/settings-segment.js new file mode 100644 index 0000000..c28910d --- /dev/null +++ b/components/settings/settings-segment.js @@ -0,0 +1,21 @@ +import React from 'react' +import PropTypes from 'prop-types' + +import { View } from 'react-native' +import AppText from '../app-text' +import styles from '../../styles' + +const SettingsSegment = ({ children, ...props }) => { + return ( + + {props.title} + {children} + + ) +} + +SettingsSegment.propTypes = { + title: PropTypes.string.isRequired +} + +export default SettingsSegment \ No newline at end of file diff --git a/i18n/en/labels.js b/i18n/en/labels.js index 743f4c1..17e3526 100644 --- a/i18n/en/labels.js +++ b/i18n/en/labels.js @@ -27,7 +27,7 @@ export const headerTitles = { SettingsMenu: 'Settings', Reminders: settingsTitles.reminders, NfpSettings: settingsTitles.nfpSettings, - ImportExport: settingsTitles.importExport, + DataManagement: settingsTitles.dataManagement, Password: settingsTitles.password, About: settingsTitles.about, BleedingEditView: 'Bleeding', diff --git a/i18n/en/settings.js b/i18n/en/settings.js index 9d46eb4..4628f45 100644 --- a/i18n/en/settings.js +++ b/i18n/en/settings.js @@ -2,7 +2,7 @@ export default { menuTitles: { reminders: 'Reminders', - importExport: 'Import and Export', + dataManagement: 'Data management', nfpSettings: 'NFP settings', password: 'Password', about: 'About' @@ -35,6 +35,10 @@ export default { }, segmentExplainer: 'Import data in CSV format' }, + deleteSegment: { + title: 'Delete app data', + explainer: 'Delete all app data from this phone' + }, tempScale: { segmentTitle: 'Temperature scale', segmentExplainer: 'Change the minimum and maximum value for the temperature chart', From 067da42ebaa5cdcbd339ac6038cc34f37358aa1d Mon Sep 17 00:00:00 2001 From: mashazyu Date: Thu, 3 Jan 2019 17:03:12 +0100 Subject: [PATCH 02/13] refactor about section --- components/settings/about.js | 14 +++++++------- components/settings/settings-segment.js | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/components/settings/about.js b/components/settings/about.js index 5d7cba1..49f5843 100644 --- a/components/settings/about.js +++ b/components/settings/about.js @@ -1,20 +1,20 @@ import React, { Component } from 'react' -import { View, ScrollView } from 'react-native' +import { ScrollView } from 'react-native' import AppText from '../app-text' +import SettingsSegment from './settings-segment' import styles from '../../styles/index' import labels from '../../i18n/en/settings' + export default class AboutSection extends Component { render() { return ( - - {`${labels.aboutSection.title} `} + {`${labels.aboutSection.segmentExplainer} `} - - - {`${labels.credits.title} `} + + {`${labels.credits.note}`} - + ) } diff --git a/components/settings/settings-segment.js b/components/settings/settings-segment.js index c28910d..0a87719 100644 --- a/components/settings/settings-segment.js +++ b/components/settings/settings-segment.js @@ -7,7 +7,7 @@ import styles from '../../styles' const SettingsSegment = ({ children, ...props }) => { return ( - + {props.title} {children} From d7c4932ec2a270323083e0dc4171613e71b6eca5 Mon Sep 17 00:00:00 2001 From: mashazyu Date: Thu, 3 Jan 2019 17:14:15 +0100 Subject: [PATCH 03/13] refactor password page --- components/settings/password/index.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/components/settings/password/index.js b/components/settings/password/index.js index f5741e3..e263cae 100644 --- a/components/settings/password/index.js +++ b/components/settings/password/index.js @@ -3,6 +3,7 @@ import { View, ScrollView } from 'react-native' import CreatePassword from './create' import ChangePassword from './update' import DeletePassword from './delete' +import SettingsSegment from '../settings-segment' import AppText from '../../app-text' import { hasEncryptionObservable @@ -22,11 +23,7 @@ export default class PasswordSetting extends Component { render() { return ( - - - - {labels.passwordSettings.title} - + {this.state.showUpdateAndDelete ? {labels.passwordSettings.explainerEnabled} @@ -45,7 +42,7 @@ export default class PasswordSetting extends Component { } - + ) } From cb95c980310c220fa1648cd0e24ce7259f61089a Mon Sep 17 00:00:00 2001 From: mashazyu Date: Thu, 3 Jan 2019 23:37:15 +0100 Subject: [PATCH 04/13] adds delete app data functionality --- .../data-management/delete-data-dialog.js | 19 +++ .../settings/data-management/delete-data.js | 126 ++++++++++++++++++ components/settings/data-management/index.js | 68 ++++++---- components/settings/password/index.js | 1 - db/index.js | 10 ++ i18n/en/settings.js | 9 +- 6 files changed, 205 insertions(+), 28 deletions(-) create mode 100644 components/settings/data-management/delete-data-dialog.js create mode 100644 components/settings/data-management/delete-data.js diff --git a/components/settings/data-management/delete-data-dialog.js b/components/settings/data-management/delete-data-dialog.js new file mode 100644 index 0000000..0122576 --- /dev/null +++ b/components/settings/data-management/delete-data-dialog.js @@ -0,0 +1,19 @@ +import { Alert } from 'react-native' +import { shared as sharedLabels } from '../../../i18n/en/labels' +import labels from '../../../i18n/en/settings' + +export default function showDeleteDialog(okHandler) { + + const { question, message, confirmation } = labels.deleteSegment + + Alert.alert( + question, + message, + [{ + text: confirmation, + onPress: okHandler + }, { + text: sharedLabels.cancel, style: 'cancel', onPress: () => { } + }] + ) +} \ No newline at end of file diff --git a/components/settings/data-management/delete-data.js b/components/settings/data-management/delete-data.js new file mode 100644 index 0000000..3bd154d --- /dev/null +++ b/components/settings/data-management/delete-data.js @@ -0,0 +1,126 @@ +import React, { Component } from 'react' +import { View } from 'react-native' +import nodejs from 'nodejs-mobile-react-native' +import RNFS from 'react-native-fs' + +import settings from '../../../i18n/en/settings' +import { requestHash, clearDb } from '../../../db' +import { hasEncryptionObservable } from '../../../local-storage' +import PasswordField from '../password/password-field' +import SettingsButton from '../settings-button' +import showDeleteDialog from './delete-data-dialog' +import checkCurrentPassword from '../password/check-current-password' +import alertError from '../alert-error' + + +export default class DeleteData extends Component { + constructor() { + super() + this.state = { + isPasswordSet: hasEncryptionObservable.value, + currentPassword: null, + enteringCurrentPassword: false + } + + nodejs.channel.addListener( + 'pre-change-pw-check', + this.openNewPasswordField, + this + ) + } + + componentWillUnmount() { + nodejs.channel.removeListener('pre-change-pw-check', this.openNewPasswordField) + } + + openNewPasswordField = async hash => { + const passwordCorrect = await checkCurrentPassword({ + hash, + onTryAgain: () => this.setState({ currentPassword: null }), + onCancel: () => this.setState({ + enteringCurrentPassword: false, + currentPassword: null + }) + }) + + if (passwordCorrect) { + await this.deleteAppData() + } + } + + startDataDeletion = () => { + showDeleteDialog(() => { + if (this.state.isPasswordSet) { + this.setState({ enteringCurrentPassword: true }) + } else { + this.deleteAppData() + } + }) + } + + deleteExportedFile = async () => { + const path = RNFS.DocumentDirectoryPath + '/data.csv' + const isFileExist = await RNFS.exists(path) + if (isFileExist) { + await RNFS.unlink(path) + } + } + + deleteAppData = async () => { + const { errors } = settings.deleteSegment + try { + await clearDb() + await this.deleteExportedFile() + this.props.onDeleteData() + } catch (err) { + return alertError(errors.couldNotDeleteFile) + } + } + + handleCurrentPasswordInput = (currentPassword) => { + this.setState({ currentPassword }) + } + + checkCurrentPassword = () => { + requestHash('pre-change-pw-check', this.state.currentPassword) + } + + render() { + + const { + enteringCurrentPassword, + currentPassword + } = this.state + + const labels = settings.passwordSettings + + if (enteringCurrentPassword) { + return ( + + + + {settings.deleteSegment.title} + + + ) + } + + return ( + + + {settings.deleteSegment.title} + + + ) + } +} \ No newline at end of file diff --git a/components/settings/data-management/index.js b/components/settings/data-management/index.js index 52e4717..806052d 100644 --- a/components/settings/data-management/index.js +++ b/components/settings/data-management/index.js @@ -1,35 +1,51 @@ -import React from 'react' +import React, { Component } from 'react' import { ScrollView } from 'react-native' -import labels from '../../../i18n/en/settings' import AppText from '../../app-text' import SettingsSegment from '../settings-segment' import SettingsButton from '../settings-button' import openImportDialogAndImport from './import-dialog' import openShareDialogAndExport from './export-dialog' +import { isDbEmpty } from '../../../db' +import DeleteData from './delete-data' +import labels from '../../../i18n/en/settings' -const Settings = () => { - return ( - - - {labels.export.segmentExplainer} - - {labels.export.button} - - - - {labels.import.segmentExplainer} - - {labels.import.button} - - - - {labels.deleteSegment.explainer} - - {labels.deleteSegment.title} - - - - ) +class DataManagement extends Component { + constructor() { + super() + this.state = { + isDataEmpty: isDbEmpty() + } + } + onDeleteData = () => { + this.setState({ + isDataEmpty: true + }) + } + render() { + return ( + + + {labels.export.segmentExplainer} + + {labels.export.button} + + + + {labels.import.segmentExplainer} + + {labels.import.button} + + + {!isDbEmpty() && ( + + {labels.deleteSegment.explainer} + + + )} + + ) + } } -export default Settings + +export default DataManagement diff --git a/components/settings/password/index.js b/components/settings/password/index.js index e263cae..fca0966 100644 --- a/components/settings/password/index.js +++ b/components/settings/password/index.js @@ -8,7 +8,6 @@ import AppText from '../../app-text' import { hasEncryptionObservable } from '../../../local-storage' -import styles from '../../../styles/index' import labels from '../../../i18n/en/settings' export default class PasswordSetting extends Component { diff --git a/db/index.js b/db/index.js index ca9752e..47942d3 100644 --- a/db/index.js +++ b/db/index.js @@ -223,12 +223,22 @@ export async function changeEncryptionAndRestartApp(hash) { restart.Restart() } +export function isDbEmpty () { + return db.empty +} + export async function deleteDbAndOpenNew() { const exists = await fs.exists(Realm.defaultPath) if (exists) await fs.unlink(Realm.defaultPath) await openDb() } +export async function clearDb() { + await db.write(async () => { + await db.deleteAll() + }) +} + function hashToInt8Array(hash) { const key = new Uint8Array(64) for (let i = 0; i < key.length; i++) { diff --git a/i18n/en/settings.js b/i18n/en/settings.js index 4628f45..a4149a5 100644 --- a/i18n/en/settings.js +++ b/i18n/en/settings.js @@ -37,7 +37,14 @@ export default { }, deleteSegment: { title: 'Delete app data', - explainer: 'Delete all app data from this phone' + explainer: 'Delete all app data from this phone', + question: 'Do you want to delete data from this phone?', + message: 'Please note that deletion of the data is permanent and irreversible. We recomend to export existing data before deletion.', + confirmation: 'Yes, I want to delete data permanently', + errors: { + couldNotDeleteFile: 'Could not delete file', + postFix: 'No data was deleted or changed' + } }, tempScale: { segmentTitle: 'Temperature scale', From dd539e50fe09b37139d8f9cba126ba7887ae9904 Mon Sep 17 00:00:00 2001 From: mashazyu Date: Fri, 4 Jan 2019 19:13:17 +0100 Subject: [PATCH 05/13] Wording update --- i18n/en/settings.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/i18n/en/settings.js b/i18n/en/settings.js index a4149a5..4a2e7f3 100644 --- a/i18n/en/settings.js +++ b/i18n/en/settings.js @@ -36,13 +36,13 @@ export default { segmentExplainer: 'Import data in CSV format' }, deleteSegment: { - title: 'Delete app data', - explainer: 'Delete all app data from this phone', + title: 'Delete data', + explainer: 'Delete data from this phone', question: 'Do you want to delete data from this phone?', - message: 'Please note that deletion of the data is permanent and irreversible. We recomend to export existing data before deletion.', - confirmation: 'Yes, I want to delete data permanently', + message: 'Please note that deletion of the data is permanent and irreversible. We recommend exporting existing data before deletion.', + confirmation: 'Delete data permanently', errors: { - couldNotDeleteFile: 'Could not delete file', + couldNotDeleteFile: 'Could not delete data', postFix: 'No data was deleted or changed' } }, From 4e74dd9a5adea4ced0949a8c7932634dfce29b67 Mon Sep 17 00:00:00 2001 From: mashazyu Date: Sat, 5 Jan 2019 11:25:43 +0100 Subject: [PATCH 06/13] Password verification bug fix --- .../password/check-current-password.js | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/components/settings/password/check-current-password.js b/components/settings/password/check-current-password.js index 76ddc94..92d78bf 100644 --- a/components/settings/password/check-current-password.js +++ b/components/settings/password/check-current-password.js @@ -3,18 +3,21 @@ import { openDb } from '../../../db' import { shared } from '../../../i18n/en/labels' export default async function checkPassword({hash, onCancel, onTryAgain }) { - const connected = await openDb(hash) - if (connected) return true - Alert.alert( - shared.incorrectPassword, - shared.incorrectPasswordMessage, - [{ - text: shared.cancel, - onPress: onCancel - }, { - text: shared.tryAgain, - onPress: onTryAgain - }] - ) - return false + try { + await openDb(hash) + return true + } catch (err) { + Alert.alert( + shared.incorrectPassword, + shared.incorrectPasswordMessage, + [{ + text: shared.cancel, + onPress: onCancel + }, { + text: shared.tryAgain, + onPress: onTryAgain + }] + ) + return false + } } \ No newline at end of file From 558eb7d717cce2a34c66c399bd3b73c27060ae36 Mon Sep 17 00:00:00 2001 From: mashazyu Date: Sat, 5 Jan 2019 12:46:48 +0100 Subject: [PATCH 07/13] Adds new reusable component for confirmation with password --- .../data-management/confirm-with-password.js | 88 +++++++++++++ .../data-management/delete-data-dialog.js | 19 --- .../settings/data-management/delete-data.js | 117 ++++++------------ 3 files changed, 128 insertions(+), 96 deletions(-) create mode 100644 components/settings/data-management/confirm-with-password.js delete mode 100644 components/settings/data-management/delete-data-dialog.js diff --git a/components/settings/data-management/confirm-with-password.js b/components/settings/data-management/confirm-with-password.js new file mode 100644 index 0000000..fee296a --- /dev/null +++ b/components/settings/data-management/confirm-with-password.js @@ -0,0 +1,88 @@ +import React, { Component } from 'react' +import { View, Alert } from 'react-native' + +import nodejs from 'nodejs-mobile-react-native' +import { requestHash, openDb } from '../../../db' + +import PasswordField from '../password/password-field' +import SettingsButton from '../settings-button' + +import settings from '../../../i18n/en/settings' +import { shared } from '../../../i18n/en/labels' + +export default class ConfirmWithPassword extends Component { + constructor() { + super() + this.state = { + password: null + } + nodejs.channel.addListener( + 'password-check', + this.checkPassword, + this + ) + } + + componentWillUnmount() { + nodejs.channel.removeListener('password-check', this.checkPassword) + } + + resetPasswordInput = () => { + this.setState({ password: null }) + } + + + onIncorrectPassword = () => { + Alert.alert( + shared.incorrectPassword, + shared.incorrectPasswordMessage, + [{ + text: shared.cancel, + onPress: this.props.onCancel + }, { + text: shared.tryAgain, + onPress: this.resetPasswordInput + }] + ) + } + + checkPassword = async hash => { + try { + await openDb(hash) + this.props.onSuccess() + } catch (err) { + this.onIncorrectPassword() + } + } + + handlePasswordInput = (password) => { + this.setState({ password }) + } + + initPasswordCheck = () => { + requestHash('password-check', this.state.password) + } + + render() { + + const { password } = this.state + const labels = settings.passwordSettings + + return ( + + + + {settings.deleteSegment.title} + + + ) + + } +} \ No newline at end of file diff --git a/components/settings/data-management/delete-data-dialog.js b/components/settings/data-management/delete-data-dialog.js deleted file mode 100644 index 0122576..0000000 --- a/components/settings/data-management/delete-data-dialog.js +++ /dev/null @@ -1,19 +0,0 @@ -import { Alert } from 'react-native' -import { shared as sharedLabels } from '../../../i18n/en/labels' -import labels from '../../../i18n/en/settings' - -export default function showDeleteDialog(okHandler) { - - const { question, message, confirmation } = labels.deleteSegment - - Alert.alert( - question, - message, - [{ - text: confirmation, - onPress: okHandler - }, { - text: sharedLabels.cancel, style: 'cancel', onPress: () => { } - }] - ) -} \ No newline at end of file diff --git a/components/settings/data-management/delete-data.js b/components/settings/data-management/delete-data.js index 3bd154d..d04d291 100644 --- a/components/settings/data-management/delete-data.js +++ b/components/settings/data-management/delete-data.js @@ -1,63 +1,50 @@ import React, { Component } from 'react' -import { View } from 'react-native' -import nodejs from 'nodejs-mobile-react-native' import RNFS from 'react-native-fs' +import { Alert } from 'react-native' -import settings from '../../../i18n/en/settings' -import { requestHash, clearDb } from '../../../db' +import { clearDb } from '../../../db' import { hasEncryptionObservable } from '../../../local-storage' -import PasswordField from '../password/password-field' import SettingsButton from '../settings-button' -import showDeleteDialog from './delete-data-dialog' -import checkCurrentPassword from '../password/check-current-password' +import ConfirmWithPassword from './confirm-with-password' import alertError from '../alert-error' +import settings from '../../../i18n/en/settings' +import { shared as sharedLabels } from '../../../i18n/en/labels' export default class DeleteData extends Component { constructor() { super() this.state = { isPasswordSet: hasEncryptionObservable.value, - currentPassword: null, - enteringCurrentPassword: false + isConfirmingWithPassword: false } + } - nodejs.channel.addListener( - 'pre-change-pw-check', - this.openNewPasswordField, - this + onAlertConfirmation = () => { + if (this.state.isPasswordSet) { + this.setState({ isConfirmingWithPassword: true }) + } else { + this.deleteAppData() + } + } + + alertBeforeDeletion = () => { + const { question, message, confirmation } = settings.deleteSegment + + Alert.alert( + question, + message, + [{ + text: confirmation, + onPress: this.onAlertConfirmation + }, { + text: sharedLabels.cancel, + style: 'cancel', + onPress: this.cancelConfirmationWithPassword + }] ) } - componentWillUnmount() { - nodejs.channel.removeListener('pre-change-pw-check', this.openNewPasswordField) - } - - openNewPasswordField = async hash => { - const passwordCorrect = await checkCurrentPassword({ - hash, - onTryAgain: () => this.setState({ currentPassword: null }), - onCancel: () => this.setState({ - enteringCurrentPassword: false, - currentPassword: null - }) - }) - - if (passwordCorrect) { - await this.deleteAppData() - } - } - - startDataDeletion = () => { - showDeleteDialog(() => { - if (this.state.isPasswordSet) { - this.setState({ enteringCurrentPassword: true }) - } else { - this.deleteAppData() - } - }) - } - deleteExportedFile = async () => { const path = RNFS.DocumentDirectoryPath + '/data.csv' const isFileExist = await RNFS.exists(path) @@ -77,50 +64,26 @@ export default class DeleteData extends Component { } } - handleCurrentPasswordInput = (currentPassword) => { - this.setState({ currentPassword }) - } - - checkCurrentPassword = () => { - requestHash('pre-change-pw-check', this.state.currentPassword) + cancelConfirmationWithPassword = () => { + this.setState({ isConfirmingWithPassword: false }) } render() { + const { isConfirmingWithPassword } = this.state - const { - enteringCurrentPassword, - currentPassword - } = this.state - - const labels = settings.passwordSettings - - if (enteringCurrentPassword) { + if (isConfirmingWithPassword) { return ( - - - - {settings.deleteSegment.title} - - + ) } return ( - - - {settings.deleteSegment.title} - - + + {settings.deleteSegment.title} + ) } } \ No newline at end of file From a724f5f733d78a04101518faefea937d02d1f9e3 Mon Sep 17 00:00:00 2001 From: mashazyu Date: Sun, 6 Jan 2019 18:53:27 +0100 Subject: [PATCH 08/13] Move export file name to constant --- components/settings/data-management/constants.js | 1 + components/settings/data-management/delete-data.js | 3 ++- components/settings/data-management/export-dialog.js | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 components/settings/data-management/constants.js diff --git a/components/settings/data-management/constants.js b/components/settings/data-management/constants.js new file mode 100644 index 0000000..610cd03 --- /dev/null +++ b/components/settings/data-management/constants.js @@ -0,0 +1 @@ +export const EXPORT_FILE_NAME = 'data.csv' \ No newline at end of file diff --git a/components/settings/data-management/delete-data.js b/components/settings/data-management/delete-data.js index d04d291..d14fc76 100644 --- a/components/settings/data-management/delete-data.js +++ b/components/settings/data-management/delete-data.js @@ -10,6 +10,7 @@ import alertError from '../alert-error' import settings from '../../../i18n/en/settings' import { shared as sharedLabels } from '../../../i18n/en/labels' +import { EXPORT_FILE_NAME } from './constants' export default class DeleteData extends Component { constructor() { @@ -46,7 +47,7 @@ export default class DeleteData extends Component { } deleteExportedFile = async () => { - const path = RNFS.DocumentDirectoryPath + '/data.csv' + const path = `${RNFS.DocumentDirectoryPath}/${EXPORT_FILE_NAME}` const isFileExist = await RNFS.exists(path) if (isFileExist) { await RNFS.unlink(path) diff --git a/components/settings/data-management/export-dialog.js b/components/settings/data-management/export-dialog.js index 58301cf..cfc5f5d 100644 --- a/components/settings/data-management/export-dialog.js +++ b/components/settings/data-management/export-dialog.js @@ -4,6 +4,7 @@ import { getCycleDaysSortedByDate } from '../../../db' import getDataAsCsvDataUri from '../../../lib/import-export/export-to-csv' import alertError from '../alert-error' import settings from '../../../i18n/en/settings' +import { EXPORT_FILE_NAME } from './constants' import RNFS from 'react-native-fs' export default async function exportData() { @@ -24,7 +25,7 @@ export default async function exportData() { } try { - const path = RNFS.DocumentDirectoryPath + '/data.csv' + const path = `${RNFS.DocumentDirectoryPath}/${EXPORT_FILE_NAME}` await RNFS.writeFile(path, data) await Share.open({ From ee27ed6ca29b71964f8fa1a91f8680e5ac84ccae Mon Sep 17 00:00:00 2001 From: mashazyu Date: Sun, 6 Jan 2019 18:56:30 +0100 Subject: [PATCH 09/13] Wording fix to reflect that only app data is deleted --- i18n/en/settings.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/i18n/en/settings.js b/i18n/en/settings.js index 4a2e7f3..2e5ec83 100644 --- a/i18n/en/settings.js +++ b/i18n/en/settings.js @@ -36,11 +36,11 @@ export default { segmentExplainer: 'Import data in CSV format' }, deleteSegment: { - title: 'Delete data', - explainer: 'Delete data from this phone', - question: 'Do you want to delete data from this phone?', - message: 'Please note that deletion of the data is permanent and irreversible. We recommend exporting existing data before deletion.', - confirmation: 'Delete data permanently', + title: 'Delete app data', + explainer: 'Delete app data from this phone', + question: 'Do you want to delete app data from this phone?', + message: 'Please note that deletion of the app data is permanent and irreversible. We recommend exporting existing data before deletion.', + confirmation: 'Delete app data permanently', errors: { couldNotDeleteFile: 'Could not delete data', postFix: 'No data was deleted or changed' From ea25a9d7be763c64bb898c9860bd0ad3460a6ef8 Mon Sep 17 00:00:00 2001 From: mashazyu Date: Sun, 6 Jan 2019 19:15:36 +0100 Subject: [PATCH 10/13] Toast message added once data is deleted --- components/settings/data-management/delete-data.js | 5 +++-- i18n/en/settings.js | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/components/settings/data-management/delete-data.js b/components/settings/data-management/delete-data.js index d14fc76..d7a9693 100644 --- a/components/settings/data-management/delete-data.js +++ b/components/settings/data-management/delete-data.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import RNFS from 'react-native-fs' -import { Alert } from 'react-native' +import { Alert, ToastAndroid } from 'react-native' import { clearDb } from '../../../db' import { hasEncryptionObservable } from '../../../local-storage' @@ -55,10 +55,11 @@ export default class DeleteData extends Component { } deleteAppData = async () => { - const { errors } = settings.deleteSegment + const { errors, success } = settings.deleteSegment try { await clearDb() await this.deleteExportedFile() + ToastAndroid.show(success.message, ToastAndroid.LONG) this.props.onDeleteData() } catch (err) { return alertError(errors.couldNotDeleteFile) diff --git a/i18n/en/settings.js b/i18n/en/settings.js index 2e5ec83..247ae95 100644 --- a/i18n/en/settings.js +++ b/i18n/en/settings.js @@ -44,6 +44,9 @@ export default { errors: { couldNotDeleteFile: 'Could not delete data', postFix: 'No data was deleted or changed' + }, + success: { + message: 'App data successfully deleted' } }, tempScale: { From 2fc48e94b706f868e10e58985966cc5bf02c5f60 Mon Sep 17 00:00:00 2001 From: mashazyu Date: Sun, 6 Jan 2019 19:17:30 +0100 Subject: [PATCH 11/13] Data section naming update as discussed --- i18n/en/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/en/settings.js b/i18n/en/settings.js index 247ae95..5de7d10 100644 --- a/i18n/en/settings.js +++ b/i18n/en/settings.js @@ -2,7 +2,7 @@ export default { menuTitles: { reminders: 'Reminders', - dataManagement: 'Data management', + dataManagement: 'Manage your data', nfpSettings: 'NFP settings', password: 'Password', about: 'About' From 57a6d947903d650309b4c9451f15989861d3ad06 Mon Sep 17 00:00:00 2001 From: mashazyu Date: Sun, 6 Jan 2019 19:27:03 +0100 Subject: [PATCH 12/13] Realm sync functions call fix --- components/settings/data-management/delete-data.js | 2 +- db/index.js | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/components/settings/data-management/delete-data.js b/components/settings/data-management/delete-data.js index d7a9693..b6ecd12 100644 --- a/components/settings/data-management/delete-data.js +++ b/components/settings/data-management/delete-data.js @@ -57,7 +57,7 @@ export default class DeleteData extends Component { deleteAppData = async () => { const { errors, success } = settings.deleteSegment try { - await clearDb() + clearDb() await this.deleteExportedFile() ToastAndroid.show(success.message, ToastAndroid.LONG) this.props.onDeleteData() diff --git a/db/index.js b/db/index.js index 47942d3..5fcbab0 100644 --- a/db/index.js +++ b/db/index.js @@ -233,10 +233,8 @@ export async function deleteDbAndOpenNew() { await openDb() } -export async function clearDb() { - await db.write(async () => { - await db.deleteAll() - }) +export function clearDb() { + db.write(db.deleteAll) } function hashToInt8Array(hash) { From 52fe871614beac22c9ed6cf688e71e06587848da Mon Sep 17 00:00:00 2001 From: mashazyu Date: Sun, 6 Jan 2019 20:55:43 +0100 Subject: [PATCH 13/13] Bug fix for password field after data is deleted, add notification when there is no data to delete --- .../settings/data-management/delete-data.js | 52 +++++++------- components/settings/data-management/index.js | 67 ++++++++----------- i18n/en/settings.js | 3 +- 3 files changed, 58 insertions(+), 64 deletions(-) diff --git a/components/settings/data-management/delete-data.js b/components/settings/data-management/delete-data.js index b6ecd12..dd09c4a 100644 --- a/components/settings/data-management/delete-data.js +++ b/components/settings/data-management/delete-data.js @@ -2,7 +2,7 @@ import React, { Component } from 'react' import RNFS from 'react-native-fs' import { Alert, ToastAndroid } from 'react-native' -import { clearDb } from '../../../db' +import { clearDb, isDbEmpty } from '../../../db' import { hasEncryptionObservable } from '../../../local-storage' import SettingsButton from '../settings-button' import ConfirmWithPassword from './confirm-with-password' @@ -12,6 +12,8 @@ import settings from '../../../i18n/en/settings' import { shared as sharedLabels } from '../../../i18n/en/labels' import { EXPORT_FILE_NAME } from './constants' +const exportedFilePath = `${RNFS.DocumentDirectoryPath}/${EXPORT_FILE_NAME}` + export default class DeleteData extends Component { constructor() { super() @@ -29,41 +31,45 @@ export default class DeleteData extends Component { } } - alertBeforeDeletion = () => { - const { question, message, confirmation } = settings.deleteSegment - - Alert.alert( - question, - message, - [{ - text: confirmation, - onPress: this.onAlertConfirmation - }, { - text: sharedLabels.cancel, - style: 'cancel', - onPress: this.cancelConfirmationWithPassword - }] - ) + alertBeforeDeletion = async () => { + const { question, message, confirmation, errors } = settings.deleteSegment + if (isDbEmpty() && !await RNFS.exists(exportedFilePath)) { + alertError(errors.noData) + } else { + Alert.alert( + question, + message, + [{ + text: confirmation, + onPress: this.onAlertConfirmation + }, { + text: sharedLabels.cancel, + style: 'cancel', + onPress: this.cancelConfirmationWithPassword + }] + ) + } } deleteExportedFile = async () => { - const path = `${RNFS.DocumentDirectoryPath}/${EXPORT_FILE_NAME}` - const isFileExist = await RNFS.exists(path) - if (isFileExist) { - await RNFS.unlink(path) + if (await RNFS.exists(exportedFilePath)) { + await RNFS.unlink(exportedFilePath) } } deleteAppData = async () => { const { errors, success } = settings.deleteSegment + try { - clearDb() + if (!isDbEmpty()) { + clearDb() + } await this.deleteExportedFile() ToastAndroid.show(success.message, ToastAndroid.LONG) - this.props.onDeleteData() } catch (err) { - return alertError(errors.couldNotDeleteFile) + alertError(errors.couldNotDeleteFile) } + this.cancelConfirmationWithPassword() } cancelConfirmationWithPassword = () => { diff --git a/components/settings/data-management/index.js b/components/settings/data-management/index.js index 806052d..4199760 100644 --- a/components/settings/data-management/index.js +++ b/components/settings/data-management/index.js @@ -1,51 +1,38 @@ -import React, { Component } from 'react' +import React from 'react' import { ScrollView } from 'react-native' import AppText from '../../app-text' import SettingsSegment from '../settings-segment' import SettingsButton from '../settings-button' import openImportDialogAndImport from './import-dialog' import openShareDialogAndExport from './export-dialog' -import { isDbEmpty } from '../../../db' import DeleteData from './delete-data' import labels from '../../../i18n/en/settings' +import styles from '../../../styles/index' -class DataManagement extends Component { - constructor() { - super() - this.state = { - isDataEmpty: isDbEmpty() - } - } - onDeleteData = () => { - this.setState({ - isDataEmpty: true - }) - } - render() { - return ( - - - {labels.export.segmentExplainer} - - {labels.export.button} - - - - {labels.import.segmentExplainer} - - {labels.import.button} - - - {!isDbEmpty() && ( - - {labels.deleteSegment.explainer} - - - )} - - ) - } +const DataManagement = () => { + return ( + + + {labels.export.segmentExplainer} + + {labels.export.button} + + + + {labels.import.segmentExplainer} + + {labels.import.button} + + + + {labels.deleteSegment.explainer} + + + + ) } - -export default DataManagement +export default DataManagement \ No newline at end of file diff --git a/i18n/en/settings.js b/i18n/en/settings.js index 5de7d10..d3565d3 100644 --- a/i18n/en/settings.js +++ b/i18n/en/settings.js @@ -43,7 +43,8 @@ export default { confirmation: 'Delete app data permanently', errors: { couldNotDeleteFile: 'Could not delete data', - postFix: 'No data was deleted or changed' + postFix: 'No data was deleted or changed', + noData: 'There is no data to delete' }, success: { message: 'App data successfully deleted'