Skip to content
Snippets Groups Projects
Commit 300d7178 authored by Julia Friesel's avatar Julia Friesel
Browse files

Merge branch 'delete-app-data' into 'master'

Delete app data

Closes #259

See merge request bloodyhealth/drip!146
parents fb6dc9b0 52fe8716
No related branches found
No related tags found
No related merge requests found
Showing
with 307 additions and 91 deletions
import React, { Component } from 'react' import React, { Component } from 'react'
import { View, ScrollView } from 'react-native' import { ScrollView } from 'react-native'
import AppText from '../app-text' import AppText from '../app-text'
import SettingsSegment from './settings-segment'
import styles from '../../styles/index' import styles from '../../styles/index'
import labels from '../../i18n/en/settings' import labels from '../../i18n/en/settings'
export default class AboutSection extends Component { export default class AboutSection extends Component {
render() { render() {
return ( return (
<ScrollView> <ScrollView>
<View style={styles.settingsSegment}> <SettingsSegment title={`${labels.aboutSection.title} `}>
<AppText style={styles.settingsSegmentTitle}>{`${labels.aboutSection.title} `}</AppText>
<AppText>{`${labels.aboutSection.segmentExplainer} `}</AppText> <AppText>{`${labels.aboutSection.segmentExplainer} `}</AppText>
</View> </SettingsSegment>
<View style={[styles.settingsSegment, styles.settingsSegmentLast]}> <SettingsSegment title={`${labels.credits.title} `} style={styles.settingsSegmentLast}>
<AppText style={styles.settingsSegmentTitle}>{`${labels.credits.title} `}</AppText>
<AppText>{`${labels.credits.note}`}</AppText> <AppText>{`${labels.credits.note}`}</AppText>
</View> </SettingsSegment>
</ScrollView> </ScrollView>
) )
} }
......
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 (
<View>
<PasswordField
placeholder={labels.enterCurrent}
value={password}
onChangeText={this.handlePasswordInput}
/>
<SettingsButton
onPress={this.initPasswordCheck}
disabled={!password}
>
{settings.deleteSegment.title}
</SettingsButton>
</View>
)
}
}
\ No newline at end of file
export const EXPORT_FILE_NAME = 'data.csv'
\ No newline at end of file
import React, { Component } from 'react'
import RNFS from 'react-native-fs'
import { Alert, ToastAndroid } from 'react-native'
import { clearDb, isDbEmpty } from '../../../db'
import { hasEncryptionObservable } from '../../../local-storage'
import SettingsButton from '../settings-button'
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'
import { EXPORT_FILE_NAME } from './constants'
const exportedFilePath = `${RNFS.DocumentDirectoryPath}/${EXPORT_FILE_NAME}`
export default class DeleteData extends Component {
constructor() {
super()
this.state = {
isPasswordSet: hasEncryptionObservable.value,
isConfirmingWithPassword: false
}
}
onAlertConfirmation = () => {
if (this.state.isPasswordSet) {
this.setState({ isConfirmingWithPassword: true })
} else {
this.deleteAppData()
}
}
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 () => {
if (await RNFS.exists(exportedFilePath)) {
await RNFS.unlink(exportedFilePath)
}
}
deleteAppData = async () => {
const { errors, success } = settings.deleteSegment
try {
if (!isDbEmpty()) {
clearDb()
}
await this.deleteExportedFile()
ToastAndroid.show(success.message, ToastAndroid.LONG)
} catch (err) {
alertError(errors.couldNotDeleteFile)
}
this.cancelConfirmationWithPassword()
}
cancelConfirmationWithPassword = () => {
this.setState({ isConfirmingWithPassword: false })
}
render() {
const { isConfirmingWithPassword } = this.state
if (isConfirmingWithPassword) {
return (
<ConfirmWithPassword
onSuccess={this.deleteAppData}
onCancel={this.cancelConfirmationWithPassword}
/>
)
}
return (
<SettingsButton onPress={this.alertBeforeDeletion}>
{settings.deleteSegment.title}
</SettingsButton>
)
}
}
\ No newline at end of file
...@@ -4,6 +4,7 @@ import { getCycleDaysSortedByDate } from '../../../db' ...@@ -4,6 +4,7 @@ import { getCycleDaysSortedByDate } from '../../../db'
import getDataAsCsvDataUri from '../../../lib/import-export/export-to-csv' import getDataAsCsvDataUri from '../../../lib/import-export/export-to-csv'
import alertError from '../alert-error' import alertError from '../alert-error'
import settings from '../../../i18n/en/settings' import settings from '../../../i18n/en/settings'
import { EXPORT_FILE_NAME } from './constants'
import RNFS from 'react-native-fs' import RNFS from 'react-native-fs'
export default async function exportData() { export default async function exportData() {
...@@ -24,7 +25,7 @@ export default async function exportData() { ...@@ -24,7 +25,7 @@ export default async function exportData() {
} }
try { try {
const path = RNFS.DocumentDirectoryPath + '/data.csv' const path = `${RNFS.DocumentDirectoryPath}/${EXPORT_FILE_NAME}`
await RNFS.writeFile(path, data) await RNFS.writeFile(path, data)
await Share.open({ await Share.open({
......
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 DeleteData from './delete-data'
import labels from '../../../i18n/en/settings'
import styles from '../../../styles/index'
const DataManagement = () => {
return (
<ScrollView>
<SettingsSegment title={labels.export.button}>
<AppText>{labels.export.segmentExplainer}</AppText>
<SettingsButton onPress={openShareDialogAndExport}>
{labels.export.button}
</SettingsButton>
</SettingsSegment>
<SettingsSegment title={labels.import.button}>
<AppText>{labels.import.segmentExplainer}</AppText>
<SettingsButton onPress={openImportDialogAndImport}>
{labels.import.button}
</SettingsButton>
</SettingsSegment>
<SettingsSegment
title={labels.deleteSegment.title}
style={styles.settingsSegmentLast}
>
<AppText>{labels.deleteSegment.explainer}</AppText>
<DeleteData />
</SettingsSegment>
</ScrollView>
)
}
export default DataManagement
\ No newline at end of file
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 (
<ScrollView>
<View style={styles.settingsSegment}>
<AppText style={styles.settingsSegmentTitle}>
{labels.export.button}
</AppText>
<AppText>{labels.export.segmentExplainer}</AppText>
<TouchableOpacity
onPress={openShareDialogAndExport}
style={styles.settingsButton}>
<AppText style={styles.settingsButtonText}>
{labels.export.button}
</AppText>
</TouchableOpacity>
</View>
<View style={styles.settingsSegment}>
<AppText style={styles.settingsSegmentTitle}>
{labels.import.button}
</AppText>
<AppText>{labels.import.segmentExplainer}</AppText>
<TouchableOpacity
onPress={openImportDialogAndImport}
style={styles.settingsButton}>
<AppText style={styles.settingsButtonText}>
{labels.import.button}
</AppText>
</TouchableOpacity>
</View>
</ScrollView>
)
}
}
import Reminders from './reminders' import Reminders from './reminders'
import NfpSettings from './nfp-settings' import NfpSettings from './nfp-settings'
import ImportExport from './import-export' import DataManagement from './data-management'
import Password from './password' import Password from './password'
import About from './about' import About from './about'
export default { export default {
Reminders, NfpSettings, ImportExport, Password, About Reminders, NfpSettings, DataManagement, Password, About
} }
...@@ -3,18 +3,21 @@ import { openDb } from '../../../db' ...@@ -3,18 +3,21 @@ import { openDb } from '../../../db'
import { shared } from '../../../i18n/en/labels' import { shared } from '../../../i18n/en/labels'
export default async function checkPassword({hash, onCancel, onTryAgain }) { export default async function checkPassword({hash, onCancel, onTryAgain }) {
const connected = await openDb(hash) try {
if (connected) return true await openDb(hash)
Alert.alert( return true
shared.incorrectPassword, } catch (err) {
shared.incorrectPasswordMessage, Alert.alert(
[{ shared.incorrectPassword,
text: shared.cancel, shared.incorrectPasswordMessage,
onPress: onCancel [{
}, { text: shared.cancel,
text: shared.tryAgain, onPress: onCancel
onPress: onTryAgain }, {
}] text: shared.tryAgain,
) onPress: onTryAgain
return false }]
)
return false
}
} }
\ No newline at end of file
...@@ -2,7 +2,7 @@ import React, { Component } from 'react' ...@@ -2,7 +2,7 @@ import React, { Component } from 'react'
import { View } from 'react-native' import { View } from 'react-native'
import settings from '../../../i18n/en/settings' import settings from '../../../i18n/en/settings'
import EnterNewPassword from './enter-new-password' import EnterNewPassword from './enter-new-password'
import SettingsButton from './settings-button' import SettingsButton from '../settings-button'
import showBackUpReminder from './show-backup-reminder' import showBackUpReminder from './show-backup-reminder'
export default class CreatePassword extends Component { export default class CreatePassword extends Component {
......
...@@ -5,7 +5,7 @@ import nodejs from 'nodejs-mobile-react-native' ...@@ -5,7 +5,7 @@ import nodejs from 'nodejs-mobile-react-native'
import { requestHash, changeEncryptionAndRestartApp } from '../../../db' import { requestHash, changeEncryptionAndRestartApp } from '../../../db'
import AppText from '../../app-text' import AppText from '../../app-text'
import PasswordField from './password-field' import PasswordField from './password-field'
import SettingsButton from './settings-button' import SettingsButton from '../settings-button'
import styles from '../../../styles' import styles from '../../../styles'
import settings from '../../../i18n/en/settings' import settings from '../../../i18n/en/settings'
......
...@@ -3,11 +3,11 @@ import { View, ScrollView } from 'react-native' ...@@ -3,11 +3,11 @@ import { View, ScrollView } from 'react-native'
import CreatePassword from './create' import CreatePassword from './create'
import ChangePassword from './update' import ChangePassword from './update'
import DeletePassword from './delete' import DeletePassword from './delete'
import SettingsSegment from '../settings-segment'
import AppText from '../../app-text' import AppText from '../../app-text'
import { import {
hasEncryptionObservable hasEncryptionObservable
} from '../../../local-storage' } from '../../../local-storage'
import styles from '../../../styles/index'
import labels from '../../../i18n/en/settings' import labels from '../../../i18n/en/settings'
export default class PasswordSetting extends Component { export default class PasswordSetting extends Component {
...@@ -22,11 +22,7 @@ export default class PasswordSetting extends Component { ...@@ -22,11 +22,7 @@ export default class PasswordSetting extends Component {
render() { render() {
return ( return (
<ScrollView> <ScrollView>
<View style={styles.settingsSegment}> <SettingsSegment title={labels.passwordSettings.title}>
<AppText style={styles.settingsSegmentTitle}>
{labels.passwordSettings.title}
</AppText>
{this.state.showUpdateAndDelete ? {this.state.showUpdateAndDelete ?
<AppText>{labels.passwordSettings.explainerEnabled}</AppText> <AppText>{labels.passwordSettings.explainerEnabled}</AppText>
...@@ -45,7 +41,7 @@ export default class PasswordSetting extends Component { ...@@ -45,7 +41,7 @@ export default class PasswordSetting extends Component {
<CreatePassword/> <CreatePassword/>
} }
</View> </SettingsSegment>
</ScrollView> </ScrollView>
) )
} }
......
...@@ -6,7 +6,7 @@ import settings from '../../../i18n/en/settings' ...@@ -6,7 +6,7 @@ import settings from '../../../i18n/en/settings'
import { requestHash } from '../../../db' import { requestHash } from '../../../db'
import EnterNewPassword from './enter-new-password' import EnterNewPassword from './enter-new-password'
import PasswordField from './password-field' import PasswordField from './password-field'
import SettingsButton from './settings-button' import SettingsButton from '../settings-button'
import showBackUpReminder from './show-backup-reminder' import showBackUpReminder from './show-backup-reminder'
import checkCurrentPassword from './check-current-password' import checkCurrentPassword from './check-current-password'
......
...@@ -2,8 +2,8 @@ import React from 'react' ...@@ -2,8 +2,8 @@ import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { TouchableOpacity } from 'react-native' import { TouchableOpacity } from 'react-native'
import AppText from '../../app-text' import AppText from '../app-text'
import styles from '../../../styles' import styles from '../../styles'
const SettingsButton = ({ children, ...props }) => { const SettingsButton = ({ children, ...props }) => {
return ( return (
......
...@@ -7,14 +7,12 @@ import styles from '../../styles/index' ...@@ -7,14 +7,12 @@ import styles from '../../styles/index'
import settingsLabels from '../../i18n/en/settings' import settingsLabels from '../../i18n/en/settings'
import AppText from '../app-text' import AppText from '../app-text'
console.log(settingsLabels.menuTitles)
const labels = settingsLabels.menuTitles const labels = settingsLabels.menuTitles
console.log(settingsLabels.menuTitles)
const menu = [ const menu = [
{title: labels.reminders, component: 'Reminders'}, {title: labels.reminders, component: 'Reminders'},
{title: labels.nfpSettings, component: 'NfpSettings'}, {title: labels.nfpSettings, component: 'NfpSettings'},
{title: labels.importExport, component: 'ImportExport'}, {title: labels.dataManagement, component: 'DataManagement'},
{title: labels.password, component: 'Password'}, {title: labels.password, component: 'Password'},
{title: labels.about, component: 'About'} {title: labels.about, component: 'About'}
] ]
......
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 (
<View style={[styles.settingsSegment, props.style]}>
<AppText style={styles.settingsSegmentTitle}>{props.title}</AppText>
{children}
</View>
)
}
SettingsSegment.propTypes = {
title: PropTypes.string.isRequired
}
export default SettingsSegment
\ No newline at end of file
...@@ -223,12 +223,20 @@ export async function changeEncryptionAndRestartApp(hash) { ...@@ -223,12 +223,20 @@ export async function changeEncryptionAndRestartApp(hash) {
restart.Restart() restart.Restart()
} }
export function isDbEmpty () {
return db.empty
}
export async function deleteDbAndOpenNew() { export async function deleteDbAndOpenNew() {
const exists = await fs.exists(Realm.defaultPath) const exists = await fs.exists(Realm.defaultPath)
if (exists) await fs.unlink(Realm.defaultPath) if (exists) await fs.unlink(Realm.defaultPath)
await openDb() await openDb()
} }
export function clearDb() {
db.write(db.deleteAll)
}
function hashToInt8Array(hash) { function hashToInt8Array(hash) {
const key = new Uint8Array(64) const key = new Uint8Array(64)
for (let i = 0; i < key.length; i++) { for (let i = 0; i < key.length; i++) {
......
...@@ -27,7 +27,7 @@ export const headerTitles = { ...@@ -27,7 +27,7 @@ export const headerTitles = {
SettingsMenu: 'Settings', SettingsMenu: 'Settings',
Reminders: settingsTitles.reminders, Reminders: settingsTitles.reminders,
NfpSettings: settingsTitles.nfpSettings, NfpSettings: settingsTitles.nfpSettings,
ImportExport: settingsTitles.importExport, DataManagement: settingsTitles.dataManagement,
Password: settingsTitles.password, Password: settingsTitles.password,
About: settingsTitles.about, About: settingsTitles.about,
BleedingEditView: 'Bleeding', BleedingEditView: 'Bleeding',
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
export default { export default {
menuTitles: { menuTitles: {
reminders: 'Reminders', reminders: 'Reminders',
importExport: 'Import and Export', dataManagement: 'Manage your data',
nfpSettings: 'NFP settings', nfpSettings: 'NFP settings',
password: 'Password', password: 'Password',
about: 'About' about: 'About'
...@@ -35,6 +35,21 @@ export default { ...@@ -35,6 +35,21 @@ export default {
}, },
segmentExplainer: 'Import data in CSV format' segmentExplainer: 'Import data in CSV format'
}, },
deleteSegment: {
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',
noData: 'There is no data to delete'
},
success: {
message: 'App data successfully deleted'
}
},
tempScale: { tempScale: {
segmentTitle: 'Temperature scale', segmentTitle: 'Temperature scale',
segmentExplainer: 'Change the minimum and maximum value for the temperature chart', segmentExplainer: 'Change the minimum and maximum value for the temperature chart',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment