Merge branch 'delete-app-data' into 'master'
Delete app data Closes #259 See merge request bloodyhealth/drip!146
This commit is contained in:
@@ -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 (
|
||||
<ScrollView>
|
||||
<View style={styles.settingsSegment}>
|
||||
<AppText style={styles.settingsSegmentTitle}>{`${labels.aboutSection.title} `}</AppText>
|
||||
<SettingsSegment title={`${labels.aboutSection.title} `}>
|
||||
<AppText>{`${labels.aboutSection.segmentExplainer} `}</AppText>
|
||||
</View>
|
||||
<View style={[styles.settingsSegment, styles.settingsSegmentLast]}>
|
||||
<AppText style={styles.settingsSegmentTitle}>{`${labels.credits.title} `}</AppText>
|
||||
</SettingsSegment>
|
||||
<SettingsSegment title={`${labels.credits.title} `} style={styles.settingsSegmentLast}>
|
||||
<AppText>{`${labels.credits.note}`}</AppText>
|
||||
</View>
|
||||
</SettingsSegment>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<View>
|
||||
<PasswordField
|
||||
placeholder={labels.enterCurrent}
|
||||
value={password}
|
||||
onChangeText={this.handlePasswordInput}
|
||||
/>
|
||||
<SettingsButton
|
||||
onPress={this.initPasswordCheck}
|
||||
disabled={!password}
|
||||
>
|
||||
{settings.deleteSegment.title}
|
||||
</SettingsButton>
|
||||
</View>
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const EXPORT_FILE_NAME = 'data.csv'
|
||||
@@ -0,0 +1,97 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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({
|
||||
@@ -0,0 +1,38 @@
|
||||
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
|
||||
@@ -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 (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -3,11 +3,11 @@ 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
|
||||
} from '../../../local-storage'
|
||||
import styles from '../../../styles/index'
|
||||
import labels from '../../../i18n/en/settings'
|
||||
|
||||
export default class PasswordSetting extends Component {
|
||||
@@ -22,11 +22,7 @@ export default class PasswordSetting extends Component {
|
||||
render() {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.settingsSegment}>
|
||||
|
||||
<AppText style={styles.settingsSegmentTitle}>
|
||||
{labels.passwordSettings.title}
|
||||
</AppText>
|
||||
<SettingsSegment title={labels.passwordSettings.title}>
|
||||
|
||||
{this.state.showUpdateAndDelete ?
|
||||
<AppText>{labels.passwordSettings.explainerEnabled}</AppText>
|
||||
@@ -45,7 +41,7 @@ export default class PasswordSetting extends Component {
|
||||
<CreatePassword/>
|
||||
}
|
||||
|
||||
</View>
|
||||
</SettingsSegment>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
+2
-2
@@ -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 (
|
||||
@@ -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'}
|
||||
]
|
||||
|
||||
@@ -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 (
|
||||
<View style={[styles.settingsSegment, props.style]}>
|
||||
<AppText style={styles.settingsSegmentTitle}>{props.title}</AppText>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
SettingsSegment.propTypes = {
|
||||
title: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default SettingsSegment
|
||||
@@ -223,12 +223,20 @@ 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 function clearDb() {
|
||||
db.write(db.deleteAll)
|
||||
}
|
||||
|
||||
function hashToInt8Array(hash) {
|
||||
const key = new Uint8Array(64)
|
||||
for (let i = 0; i < key.length; i++) {
|
||||
|
||||
+1
-1
@@ -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',
|
||||
|
||||
+16
-1
@@ -2,7 +2,7 @@
|
||||
export default {
|
||||
menuTitles: {
|
||||
reminders: 'Reminders',
|
||||
importExport: 'Import and Export',
|
||||
dataManagement: 'Manage your data',
|
||||
nfpSettings: 'NFP settings',
|
||||
password: 'Password',
|
||||
about: 'About'
|
||||
@@ -35,6 +35,21 @@ export default {
|
||||
},
|
||||
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: {
|
||||
segmentTitle: 'Temperature scale',
|
||||
segmentExplainer: 'Change the minimum and maximum value for the temperature chart',
|
||||
|
||||
Reference in New Issue
Block a user