Merge branch 'master' into 82-chart-improvements

This commit is contained in:
Julia Friesel
2018-08-16 14:46:08 +02:00
16 changed files with 603 additions and 65 deletions
+2
View File
@@ -139,6 +139,8 @@ android {
dependencies { dependencies {
compile project(':react-native-vector-icons') compile project(':react-native-vector-icons')
compile project(':react-native-fs')
compile project(':react-native-document-picker')
compile project(':react-native-share') compile project(':react-native-share')
compile project(':realm') compile project(':realm')
compile fileTree(dir: "libs", include: ["*.jar"]) compile fileTree(dir: "libs", include: ["*.jar"])
@@ -4,6 +4,8 @@ import android.app.Application;
import com.facebook.react.ReactApplication; import com.facebook.react.ReactApplication;
import com.oblador.vectoricons.VectorIconsPackage; import com.oblador.vectoricons.VectorIconsPackage;
import com.rnfs.RNFSPackage;
import com.reactnativedocumentpicker.ReactNativeDocumentPicker;
import cl.json.RNSharePackage; import cl.json.RNSharePackage;
import cl.json.ShareApplication; import cl.json.ShareApplication;
import io.realm.react.RealmReactPackage; import io.realm.react.RealmReactPackage;
@@ -28,6 +30,8 @@ public class MainApplication extends Application implements ReactApplication, Sh
return Arrays.<ReactPackage>asList( return Arrays.<ReactPackage>asList(
new MainReactPackage(), new MainReactPackage(),
new VectorIconsPackage(), new VectorIconsPackage(),
new RNFSPackage(),
new ReactNativeDocumentPicker(),
new RNSharePackage(), new RNSharePackage(),
new RealmReactPackage() new RealmReactPackage()
); );
+4
View File
@@ -1,6 +1,10 @@
rootProject.name = 'drip' rootProject.name = 'drip'
include ':react-native-vector-icons' include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android')
include ':react-native-document-picker'
project(':react-native-document-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-document-picker/android')
include ':react-native-share' include ':react-native-share'
project(':react-native-share').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-share/android') project(':react-native-share').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-share/android')
include ':realm' include ':realm'
+31 -4
View File
@@ -98,11 +98,20 @@ export default class DayView extends Component {
<Text style={styles.symptomDayView}>Desire</Text> <Text style={styles.symptomDayView}>Desire</Text>
<View style={styles.symptomEditButton}> <View style={styles.symptomEditButton}>
<Button <Button
onPress={() => this.showView('desireEditView')} onPress={() => this.showView('DesireEditView')}
title={getLabel('desire', cycleDay.desire)}> title={getLabel('desire', cycleDay.desire)}>
</Button> </Button>
</View> </View>
</View> </View>
<View style={styles.symptomViewRowInline}>
<Text style={styles.symptomDayView}>Sex</Text>
<View style={styles.symptomEditButton}>
<Button
onPress={() => this.showView('SexEditView')}
title={getLabel('sex', cycleDay.sex)}>
</Button>
</View>
</View>
</View > </View >
) )
} }
@@ -132,15 +141,22 @@ function getLabel(symptomName, symptom) {
typeof mucus.texture === 'number' && typeof mucus.texture === 'number' &&
typeof mucus.value === 'number' typeof mucus.value === 'number'
) { ) {
let mucusLabel = `${feelingLabels[mucus.feeling]} + ${textureLabels[mucus.texture]} ( ${computeSensiplanMucusLabels[mucus.value]} )` let mucusLabel =
`${feelingLabels[mucus.feeling]} +
${textureLabels[mucus.texture]}
( ${computeSensiplanMucusLabels[mucus.value]} )`
if (mucus.exclude) mucusLabel = "( " + mucusLabel + " )" if (mucus.exclude) mucusLabel = "( " + mucusLabel + " )"
return mucusLabel return mucusLabel
} }
}, },
cervix: cervix => { cervix: cervix => {
if (cervix.opening > -1 && cervix.firmness > -1) { if (cervix.opening > -1 && cervix.firmness > -1) {
let cervixLabel = `${openingLabels[cervix.opening]} + ${firmnessLabels[cervix.firmness]}` let cervixLabel =
if (cervix.position > -1) cervixLabel += `+ ${positionLabels[cervix.position]}` `${openingLabels[cervix.opening]} +
${firmnessLabels[cervix.firmness]}`
if (cervix.position > -1) {
cervixLabel += `+ ${positionLabels[cervix.position]}`
}
if (cervix.exclude) cervixLabel = "( " + cervixLabel + " )" if (cervix.exclude) cervixLabel = "( " + cervixLabel + " )"
return cervixLabel return cervixLabel
} }
@@ -153,6 +169,17 @@ function getLabel(symptomName, symptom) {
const desireLabel = `${intensityLabels[desire.value]}` const desireLabel = `${intensityLabels[desire.value]}`
return desireLabel return desireLabel
} }
},
sex: sex => {
let sexLabel = ''
if ( sex.solo || sex.partner ) {
sexLabel += 'Activity '
}
if (sex.condom || sex.pill || sex.iud ||
sex.patch || sex.ring || sex.implant || sex.other) {
sexLabel += 'Contraceptive'
}
return sexLabel ? sexLabel : 'edit'
} }
} }
+13
View File
@@ -6,6 +6,19 @@ export const cervixOpening = ['closed', 'medium', 'open']
export const cervixFirmness = ['hard', 'soft'] export const cervixFirmness = ['hard', 'soft']
export const cervixPosition = ['low', 'medium', 'high'] export const cervixPosition = ['low', 'medium', 'high']
export const intensity = ['low', 'medium', 'high'] export const intensity = ['low', 'medium', 'high']
export const sexActivity = {
solo: 'Solo',
partner: 'Partner'
}
export const contraceptives = {
condom: 'Condom',
pill: 'Pill',
iud: 'IUD',
patch: 'Patch',
ring: 'Ring',
implant: 'Implant',
other: 'Other'
}
export const fertilityStatus = { export const fertilityStatus = {
fertile: 'fertile', fertile: 'fertile',
+3 -1
View File
@@ -4,6 +4,7 @@ import MucusEditView from './mucus'
import CervixEditView from './cervix' import CervixEditView from './cervix'
import NoteEditView from './note' import NoteEditView from './note'
import DesireEditView from './desire' import DesireEditView from './desire'
import SexEditView from './sex'
export default { export default {
BleedingEditView, BleedingEditView,
@@ -11,5 +12,6 @@ export default {
MucusEditView, MucusEditView,
CervixEditView, CervixEditView,
NoteEditView, NoteEditView,
DesireEditView DesireEditView,
SexEditView
} }
+156
View File
@@ -0,0 +1,156 @@
import React, { Component } from 'react'
import {
CheckBox,
Text,
TextInput,
View
} from 'react-native'
import styles from '../../../styles'
import { saveSymptom } from '../../../db'
import {
sexActivity as activityLabels,
contraceptives as contraceptiveLabels
} from '../labels/labels'
export default class Sex extends Component {
constructor(props) {
super(props)
this.cycleDay = props.cycleDay
this.state = {}
if (this.cycleDay.sex !== null ) {
Object.assign(this.state, this.cycleDay.sex)
// We make sure other is always true when there is a note,
// e.g. when import is messed up.
if (this.cycleDay.sex && this.cycleDay.sex.note) {
this.state.other = true
}
}
}
render() {
return (
<View style={styles.symptomEditView}>
<Text style={styles.symptomDayView}>SEX</Text>
<View style={styles.symptomViewRowInline}>
<Text style={styles.symptomDayView}>{activityLabels.solo}</Text>
<CheckBox
value={this.state.solo}
onValueChange={(val) => {
this.setState({solo: val})
}}
/>
<Text style={styles.symptomDayView}>{activityLabels.partner}</Text>
<CheckBox
value={this.state.partner}
onValueChange={(val) => {
this.setState({partner: val})
}}
/>
</View>
<Text style={styles.symptomDayView}>CONTRACEPTIVES</Text>
<View style={styles.symptomViewRowInline}>
<Text style={styles.symptomDayView}>
{contraceptiveLabels.condom}
</Text>
<CheckBox
value={this.state.condom}
onValueChange={(val) => {
this.setState({condom: val})
}}
/>
<Text style={styles.symptomDayView}>
{contraceptiveLabels.pill}
</Text>
<CheckBox
value={this.state.pill}
onValueChange={(val) => {
this.setState({pill: val})
}}
/>
</View>
<View style={styles.symptomViewRowInline}>
<Text style={styles.symptomDayView}>
{contraceptiveLabels.iud}
</Text>
<CheckBox
value={this.state.iud}
onValueChange={(val) => {
this.setState({iud: val})
}}
/>
<Text style={styles.symptomDayView}>
{contraceptiveLabels.patch}
</Text>
<CheckBox
value={this.state.patch}
onValueChange={(val) => {
this.setState({patch: val})
}}
/>
</View>
<View style={styles.symptomViewRowInline}>
<Text style={styles.symptomDayView}>
{contraceptiveLabels.ring}
</Text>
<CheckBox
value={this.state.ring}
onValueChange={(val) => {
this.setState({ring: val})
}}
/>
<Text style={styles.symptomDayView}>
{contraceptiveLabels.implant}
</Text>
<CheckBox
value={this.state.implant}
onValueChange={(val) => {
this.setState({implant: val})
}}
/>
</View>
<View style={styles.symptomViewRowInline}>
<Text style={styles.symptomDayView}>
{contraceptiveLabels.other}
</Text>
<CheckBox
value={this.state.other}
onValueChange={(val) => {
this.setState({
other: val,
focusTextArea: true
})
}}
/>
</View>
{ this.state.other &&
<TextInput
autoFocus={this.state.focusTextArea}
multiline={true}
placeholder="Enter"
value={this.state.note}
onChangeText={(val) => {
this.setState({note: val})
}}
/>
}
<View style={styles.actionButtonRow}>
{this.props.makeActionButtons(
{
symptom: 'sex',
cycleDay: this.cycleDay,
saveAction: () => {
const copyOfState = Object.assign({}, this.state)
if (!copyOfState.other) {
copyOfState.note = null
}
saveSymptom('sex', this.cycleDay, copyOfState)
},
saveDisabled: Object.values(this.state).every(value => !value)
}
)}
</View>
</View>
)
}
}
+26 -3
View File
@@ -1,10 +1,33 @@
export const settings = { export const settings = {
shared: {
cancel: 'Cancel',
errorTitle: 'Error',
successTitle: 'Success'
},
export: {
errors: { errors: {
noData: 'There is no data to export', noData: 'There is no data to export',
couldNotConvert: 'Could not convert data to CSV', couldNotConvert: 'Could not convert data to CSV',
problemSharing: 'There was a problem sharing the data export file' problemSharing: 'There was a problem sharing the data export file'
}, },
exportTitle: 'My Drip data export', title: 'My Drip data export',
exportSubject: 'My Drip data export', subject: 'My Drip data export',
buttonLabel: 'Export data' button: 'Export data',
},
import: {
button: 'Import data',
title: 'Keep existing data?',
message: `There are two options for the import:
1. Keep existing cycle days and replace only the ones in the import file.
2. Delete all existing cycle days and import cycle days from file.`,
replaceOption: 'Import and replace',
deleteOption: 'Import and delete existing',
errors: {
couldNotOpenFile: 'Could not open file',
postFix: 'No data was imported or changed'
},
success: {
message: 'Data successfully imported'
}
}
} }
+94 -27
View File
@@ -7,9 +7,12 @@ import {
} from 'react-native' } from 'react-native'
import Share from 'react-native-share' import Share from 'react-native-share'
import getDataAsCsvDataUri from '../lib/export-to-csv' import { DocumentPicker, DocumentPickerUtil } from 'react-native-document-picker'
import rnfs from 'react-native-fs'
import styles from '../styles/index' import styles from '../styles/index'
import { settings as labels } from './labels' import { settings as labels } from './labels'
import getDataAsCsvDataUri from '../lib/import-export/export-to-csv'
import importCsv from '../lib/import-export/import-from-csv'
export default class Settings extends Component { export default class Settings extends Component {
render() { render() {
@@ -18,32 +21,14 @@ export default class Settings extends Component {
<View style={styles.homeButtons}> <View style={styles.homeButtons}>
<View style={styles.homeButton}> <View style={styles.homeButton}>
<Button <Button
onPress={async () => { onPress={ openShareDialogAndExport }
let data title={labels.export.button}>
try { </Button>
data = getDataAsCsvDataUri() </View>
if (!data) { <View style={styles.homeButton}>
return Alert.alert(labels.errors.noData) <Button
} title={labels.import.button}
} catch (err) { onPress={ openImportDialogAndImport }>
console.error(err)
return Alert.alert(labels.errors.couldNotConvert)
}
try {
await Share.open({
title: labels.exportTitle,
url: data,
subject: labels.exportSubject,
type: 'text/csv',
showAppsToView: true
})
} catch (err) {
console.error(err)
return Alert.alert(labels.errors.problemSharing)
}
}}
title={labels.buttonLabel}>
</Button> </Button>
</View> </View>
</View> </View>
@@ -51,3 +36,85 @@ export default class Settings extends Component {
) )
} }
} }
async function openShareDialogAndExport() {
let data
try {
data = getDataAsCsvDataUri()
if (!data) {
return alertError(labels.errors.noData)
}
} catch (err) {
console.error(err)
return alertError(labels.errors.couldNotConvert)
}
try {
await Share.open({
title: labels.export.title,
url: data,
subject: labels.export.subject,
type: 'text/csv',
showAppsToView: true
})
} catch (err) {
console.error(err)
return alertError(labels.export.errors.problemSharing)
}
}
function openImportDialogAndImport() {
Alert.alert(
labels.import.title,
labels.import.message,
[{
text: labels.import.replaceOption,
onPress: () => getFileContentAndImport({ deleteExisting: false })
}, {
text: labels.import.deleteOption,
onPress: () => getFileContentAndImport({ deleteExisting: true })
}, {
text: labels.shared.cancel, style: 'cancel', onPress: () => { }
}]
)
}
async function getFileContentAndImport({ deleteExisting }) {
let fileInfo
try {
fileInfo = await new Promise((resolve, reject) => {
DocumentPicker.show({
filetype: [DocumentPickerUtil.allFiles()],
}, (err, res) => {
if (err) return reject(err)
resolve(res)
})
})
} catch (err) {
// because cancelling also triggers an error, we do nothing here
return
}
let fileContent
try {
fileContent = await rnfs.readFile(fileInfo.uri, 'utf8')
} catch (err) {
return importError(labels.import.errors.couldNotOpenFile)
}
try {
await importCsv(fileContent, deleteExisting)
Alert.alert(labels.import.success.title, labels.import.success.message)
} catch(err) {
importError(err.message)
}
}
function alertError(msg) {
Alert.alert(labels.shared.errorTitle, msg)
}
function importError(msg) {
const postFixed = `${msg}\n\n${labels.import.errors.postFix}`
alertError(postFixed)
}
+53 -17
View File
@@ -60,6 +60,22 @@ const DesireSchema = {
} }
} }
const SexSchema = {
name: 'Sex',
properties: {
solo: { type: 'bool', optional: true },
partner: { type: 'bool', optional: true },
condom: { type: 'bool', optional: true },
pill: { type: 'bool', optional: true },
iud: { type: 'bool', optional: true },
patch: { type: 'bool', optional: true },
ring: { type: 'bool', optional: true },
implant: { type: 'bool', optional: true },
other: { type: 'bool', optional: true },
note: { type: 'string', optional: true }
}
}
const CycleDaySchema = { const CycleDaySchema = {
name: 'CycleDay', name: 'CycleDay',
primaryKey: 'date', primaryKey: 'date',
@@ -88,6 +104,10 @@ const CycleDaySchema = {
desire: { desire: {
type: 'Desire', type: 'Desire',
optional: true optional: true
},
sex: {
type: 'Sex',
optional: true
} }
} }
} }
@@ -100,7 +120,8 @@ const realmConfig = {
MucusSchema, MucusSchema,
CervixSchema, CervixSchema,
NoteSchema, NoteSchema,
DesireSchema DesireSchema,
SexSchema
], ],
// we only want this in dev mode // we only want this in dev mode
deleteRealmIfMigrationNeeded: true deleteRealmIfMigrationNeeded: true
@@ -175,21 +196,17 @@ function getPreviousTemperature(cycleDay) {
return winner.temperature.value return winner.temperature.value
} }
function getColumnNamesForCsv() { const schema = db.schema.reduce((acc, curr) => {
return getPrefixedKeys('CycleDay') acc[curr.name] = curr.properties
return acc
}, {})
function getPrefixedKeys(schemaName, prefix) { function tryToCreateCycleDay(day, i) {
const schema = db.schema.find(x => x.name === schemaName).properties try {
return Object.keys(schema).reduce((acc, key) => { db.create('CycleDay', day)
const prefixedKey = prefix ? [prefix, key].join('.') : key } catch (err) {
const childSchemaName = schema[key].objectType const msg = `Line ${i + 1}(${day.date}): ${err.message}`
if (!childSchemaName) { throw new Error(msg)
acc.push(prefixedKey)
return acc
}
acc.push(...getPrefixedKeys(childSchemaName, prefixedKey))
return acc
}, [])
} }
} }
@@ -202,6 +219,23 @@ function getAmountOfCycleDays() {
return earliestAsLocalDate.until(today, ChronoUnit.DAYS) return earliestAsLocalDate.until(today, ChronoUnit.DAYS)
} }
function tryToImportWithDelete(cycleDays) {
db.write(() => {
db.delete(db.objects('CycleDay'))
cycleDays.forEach(tryToCreateCycleDay)
})
}
function tryToImportWithoutDelete(cycleDays) {
db.write(() => {
cycleDays.forEach((day, i) => {
const existing = getCycleDay(day.date)
if (existing) db.delete(existing)
tryToCreateCycleDay(day, i)
})
})
}
export { export {
saveSymptom, saveSymptom,
getOrCreateCycleDay, getOrCreateCycleDay,
@@ -212,6 +246,8 @@ export {
deleteAll, deleteAll,
getPreviousTemperature, getPreviousTemperature,
getCycleDay, getCycleDay,
getColumnNamesForCsv, getAmountOfCycleDays,
getAmountOfCycleDays schema,
tryToImportWithDelete,
tryToImportWithoutDelete
} }
+38
View File
@@ -56,6 +56,8 @@
DB91E6CCC3EB4A549D947797 /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4902D5DCD46748BD8DC403FD /* Octicons.ttf */; }; DB91E6CCC3EB4A549D947797 /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4902D5DCD46748BD8DC403FD /* Octicons.ttf */; };
3DF2498A20844F298CD84CC3 /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E954835D62BD45F0A5FFC523 /* SimpleLineIcons.ttf */; }; 3DF2498A20844F298CD84CC3 /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E954835D62BD45F0A5FFC523 /* SimpleLineIcons.ttf */; };
A1410AC4C98A49B2820D9E45 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B6F5078F7DEC470782757471 /* Zocial.ttf */; }; A1410AC4C98A49B2820D9E45 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B6F5078F7DEC470782757471 /* Zocial.ttf */; };
29DF0CCC1AEA4C92BCA0BCCD /* libRNDocumentPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D211D71BE5A8436A978770A9 /* libRNDocumentPicker.a */; };
17AD822C42A44BADA96BD860 /* libRNFS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 84CCEBD3B2C44758853BC941 /* libRNFS.a */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -383,6 +385,10 @@
4902D5DCD46748BD8DC403FD /* Octicons.ttf */ = {isa = PBXFileReference; name = "Octicons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; 4902D5DCD46748BD8DC403FD /* Octicons.ttf */ = {isa = PBXFileReference; name = "Octicons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
E954835D62BD45F0A5FFC523 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; name = "SimpleLineIcons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; E954835D62BD45F0A5FFC523 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; name = "SimpleLineIcons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
B6F5078F7DEC470782757471 /* Zocial.ttf */ = {isa = PBXFileReference; name = "Zocial.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; B6F5078F7DEC470782757471 /* Zocial.ttf */ = {isa = PBXFileReference; name = "Zocial.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
1F05FE29622E4F21AF70C2B7 /* RNDocumentPicker.xcodeproj */ = {isa = PBXFileReference; name = "RNDocumentPicker.xcodeproj"; path = "../node_modules/react-native-document-picker/ios/RNDocumentPicker.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
D211D71BE5A8436A978770A9 /* libRNDocumentPicker.a */ = {isa = PBXFileReference; name = "libRNDocumentPicker.a"; path = "libRNDocumentPicker.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
49089E09BFCF4F3DB209B6E9 /* RNFS.xcodeproj */ = {isa = PBXFileReference; name = "RNFS.xcodeproj"; path = "../node_modules/react-native-fs/RNFS.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
84CCEBD3B2C44758853BC941 /* libRNFS.a */ = {isa = PBXFileReference; name = "libRNFS.a"; path = "libRNFS.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -416,6 +422,8 @@
D91133DCE120440893E2FD2E /* libz.tbd in Frameworks */, D91133DCE120440893E2FD2E /* libz.tbd in Frameworks */,
26DC04B498C64CE5AAA0C4F8 /* libRNShare.a in Frameworks */, 26DC04B498C64CE5AAA0C4F8 /* libRNShare.a in Frameworks */,
AED64B7892744F21B3A156BB /* libRNVectorIcons.a in Frameworks */, AED64B7892744F21B3A156BB /* libRNVectorIcons.a in Frameworks */,
29DF0CCC1AEA4C92BCA0BCCD /* libRNDocumentPicker.a in Frameworks */,
17AD822C42A44BADA96BD860 /* libRNFS.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -608,6 +616,8 @@
7F6C9FA9B66B453CA602B334 /* RealmReact.xcodeproj */, 7F6C9FA9B66B453CA602B334 /* RealmReact.xcodeproj */,
4E6AB77B55F2491487B6124E /* RNShare.xcodeproj */, 4E6AB77B55F2491487B6124E /* RNShare.xcodeproj */,
D1E5ACC4B66345868F556374 /* RNVectorIcons.xcodeproj */, D1E5ACC4B66345868F556374 /* RNVectorIcons.xcodeproj */,
1F05FE29622E4F21AF70C2B7 /* RNDocumentPicker.xcodeproj */,
49089E09BFCF4F3DB209B6E9 /* RNFS.xcodeproj */,
); );
name = Libraries; name = Libraries;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1272,12 +1282,16 @@
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
); );
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-share/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager", "$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fs/**",
); );
}; };
name = Debug; name = Debug;
@@ -1299,12 +1313,16 @@
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
); );
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-share/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager", "$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fs/**",
); );
}; };
name = Release; name = Release;
@@ -1329,6 +1347,8 @@
"$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-share/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager", "$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fs/**",
); );
}; };
name = Debug; name = Debug;
@@ -1352,6 +1372,8 @@
"$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-share/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager", "$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fs/**",
); );
}; };
name = Release; name = Release;
@@ -1382,12 +1404,16 @@
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
); );
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-share/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager", "$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fs/**",
); );
}; };
name = Debug; name = Debug;
@@ -1418,12 +1444,16 @@
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
); );
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-share/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager", "$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fs/**",
); );
}; };
name = Release; name = Release;
@@ -1453,12 +1483,16 @@
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
); );
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-share/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager", "$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fs/**",
); );
}; };
name = Debug; name = Debug;
@@ -1488,12 +1522,16 @@
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
); );
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-share/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager", "$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
"$(SRCROOT)/../node_modules/react-native-fs/**",
); );
}; };
name = Release; name = Release;
@@ -1,7 +1,7 @@
import objectPath from 'object-path' import objectPath from 'object-path'
import { Base64 } from 'js-base64' import { Base64 } from 'js-base64'
import { cycleDaysSortedByDate } from '../../db'
import { getColumnNamesForCsv, cycleDaysSortedByDate } from '../db' import getColumnNamesForCsv from './get-csv-column-names'
export default function makeDataURI() { export default function makeDataURI() {
if (!cycleDaysSortedByDate.length) return null if (!cycleDaysSortedByDate.length) return null
+19
View File
@@ -0,0 +1,19 @@
import { schema } from '../../db'
export default function getColumnNamesForCsv() {
return getPrefixedKeys('CycleDay')
function getPrefixedKeys(schemaName, prefix) {
const model = schema[schemaName]
return Object.keys(model).reduce((acc, key) => {
const prefixedKey = prefix ? [prefix, key].join('.') : key
const childSchemaName = model[key].objectType
if (!childSchemaName) {
acc.push(prefixedKey)
return acc
}
acc.push(...getPrefixedKeys(childSchemaName, prefixedKey))
return acc
}, [])
}
}
+82
View File
@@ -0,0 +1,82 @@
import csvParser from 'csvtojson'
import isObject from 'isobject'
import { schema, tryToImportWithDelete, tryToImportWithoutDelete } from '../../db'
import getColumnNamesForCsv from './get-csv-column-names'
export default async function importCsv(csv, deleteFirst) {
const parseFuncs = {
bool: val => {
if (val.toLowerCase() === 'true') return true
if (val.toLowerCase() === 'false') return false
return val
},
int: parseNumberIfPossible,
float: parseNumberIfPossible,
double: parseNumberIfPossible,
string: val => val
}
function parseNumberIfPossible(val) {
// Number and parseFloat catch different cases of weirdness,
// so we test them both
if (isNaN(Number(val)) || isNaN(parseFloat(val))) return val
return Number(val)
}
const config = {
ignoreEmpty: true,
colParser: getColumnNamesForCsv().reduce((acc, colName) => {
const path = colName.split('.')
const dbType = getDbType(schema.CycleDay, path)
acc[colName] = item => {
if (item === '') return null
return parseFuncs[dbType](item)
}
return acc
}, {})
}
const cycleDays = await csvParser(config)
.fromString(csv)
.on('header', validateHeaders)
//remove symptoms where all fields are null
putNullForEmptySymptoms(cycleDays)
if (deleteFirst) {
tryToImportWithDelete(cycleDays)
} else {
tryToImportWithoutDelete(cycleDays)
}
}
function validateHeaders(headers) {
const expectedHeaders = getColumnNamesForCsv()
if (!headers.every(header => {
return expectedHeaders.indexOf(header) > -1
})) {
const msg = `Expected CSV column titles to be ${expectedHeaders.join()}`
throw new Error(msg)
}
}
function putNullForEmptySymptoms(data) {
data.forEach(replaceWithNullIfAllPropertiesAreNull)
function replaceWithNullIfAllPropertiesAreNull(obj) {
Object.keys(obj).forEach((key) => {
if (!isObject(obj[key])) return
if (Object.values(obj[key]).every(val => val === null)) {
obj[key] = null
return
}
replaceWithNullIfAllPropertiesAreNull(obj[key])
})
}
}
function getDbType(modelProperties, path) {
if (path.length === 1) return modelProperties[path[0]].type
const modelName = modelProperties[path[0]].objectType
return getDbType(schema[modelName], path.slice(1))
}
+67 -6
View File
@@ -2015,6 +2015,11 @@
} }
} }
}, },
"base-64": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
"integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
},
"base64-js": { "base64-js": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
@@ -2066,6 +2071,11 @@
"inherits": "~2.0.0" "inherits": "~2.0.0"
} }
}, },
"bluebird": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
"integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA=="
},
"boom": { "boom": {
"version": "2.10.1", "version": "2.10.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
@@ -2545,6 +2555,26 @@
"boom": "2.x.x" "boom": "2.x.x"
} }
}, },
"csvtojson": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.8.tgz",
"integrity": "sha512-DC6YFtsJiA7t/Yz+KjzT6GXuKtU/5gRbbl7HJqvDVVir+dxdw2/1EgwfgJdnsvUT7lOnON5DvGftKuYWX1nMOQ==",
"requires": {
"bluebird": "^3.5.1",
"lodash": "^4.17.3",
"strip-bom": "^2.0.0"
},
"dependencies": {
"strip-bom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
"requires": {
"is-utf8": "^0.2.0"
}
}
}
},
"dashdash": { "dashdash": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -3341,6 +3371,16 @@
"randomatic": "^3.0.0", "randomatic": "^3.0.0",
"repeat-element": "^1.1.2", "repeat-element": "^1.1.2",
"repeat-string": "^1.5.2" "repeat-string": "^1.5.2"
},
"dependencies": {
"isobject": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
"integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
"requires": {
"isarray": "1.0.0"
}
}
} }
}, },
"finalhandler": { "finalhandler": {
@@ -4657,6 +4697,11 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
}, },
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
},
"is-windows": { "is-windows": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
@@ -4673,12 +4718,9 @@
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
}, },
"isobject": { "isobject": {
"version": "2.1.0", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
"requires": {
"isarray": "1.0.0"
}
}, },
"isomorphic-fetch": { "isomorphic-fetch": {
"version": "2.2.1", "version": "2.2.1",
@@ -6351,6 +6393,11 @@
"resolved": "https://registry.npmjs.org/react-native-dismiss-keyboard/-/react-native-dismiss-keyboard-1.0.0.tgz", "resolved": "https://registry.npmjs.org/react-native-dismiss-keyboard/-/react-native-dismiss-keyboard-1.0.0.tgz",
"integrity": "sha1-MohiQrPyMX4SHzrrmwpYXiuHm0k=" "integrity": "sha1-MohiQrPyMX4SHzrrmwpYXiuHm0k="
}, },
"react-native-document-picker": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-2.1.0.tgz",
"integrity": "sha512-BFCBXwz8xuLvHLVFVeQM+RhaY8yZ38PEWt9WSbq5VIoZ/VssP6uu51XxOfdwaMALOrAHIojK0SiYnd155upZAg=="
},
"react-native-drawer-layout": { "react-native-drawer-layout": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-1.3.2.tgz", "resolved": "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-1.3.2.tgz",
@@ -6367,6 +6414,15 @@
"react-native-drawer-layout": "1.3.2" "react-native-drawer-layout": "1.3.2"
} }
}, },
"react-native-fs": {
"version": "2.10.14",
"resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.10.14.tgz",
"integrity": "sha512-4bCzkg4dE/xUyXkMVz0AiyqLKAgTZPlZl/nEzRiSr2q6VnWDgO229MSgHLHhUtD2cqZkV0Z83WEbGpvXxWOAHA==",
"requires": {
"base-64": "^0.1.0",
"utf8": "^2.1.1"
}
},
"react-native-modal": { "react-native-modal": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-3.1.0.tgz", "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-3.1.0.tgz",
@@ -8323,6 +8379,11 @@
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
}, },
"utf8": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz",
"integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY="
},
"util": { "util": {
"version": "0.10.3", "version": "0.10.3",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+4
View File
@@ -16,7 +16,9 @@
}, },
"dependencies": { "dependencies": {
"assert": "^1.4.1", "assert": "^1.4.1",
"csvtojson": "^2.0.8",
"date-range": "0.0.2", "date-range": "0.0.2",
"isobject": "^3.0.1",
"js-base64": "^2.4.8", "js-base64": "^2.4.8",
"js-joda": "^1.8.2", "js-joda": "^1.8.2",
"moment": "^2.22.1", "moment": "^2.22.1",
@@ -24,6 +26,8 @@
"react": "16.4.1", "react": "16.4.1",
"react-native": "^0.56.0", "react-native": "^0.56.0",
"react-native-calendars": "^1.19.3", "react-native-calendars": "^1.19.3",
"react-native-document-picker": "^2.1.0",
"react-native-fs": "^2.10.14",
"react-native-modal-datetime-picker-nevo": "^4.11.0", "react-native-modal-datetime-picker-nevo": "^4.11.0",
"react-native-share": "^1.1.0", "react-native-share": "^1.1.0",
"react-native-simple-radio-button": "^2.7.1", "react-native-simple-radio-button": "^2.7.1",