diff --git a/components/chart/chart.js b/components/chart/chart.js index ebb0d85..30ed5d7 100644 --- a/components/chart/chart.js +++ b/components/chart/chart.js @@ -1,5 +1,5 @@ import React, { Component } from 'react' -import { Text as ReactNativeText, View, FlatList } from 'react-native' +import { Text as ReactNativeText, View, FlatList, ScrollView } from 'react-native' import range from 'date-range' import Svg,{ G, @@ -109,6 +109,13 @@ export default class CycleChart extends Component { this.drawDotAndLines(y, cycleDay.temperature.exclude, index) : null } + {cycleDay && cycleDay.mucus ? + : null} + + {y ? this.drawDotAndLines(y, cycleDay.temperature.exclude, index) : null} ) } @@ -160,7 +167,7 @@ export default class CycleChart extends Component { render() { return ( - + {yAxis.labels} item.dateString} > - + ) } } diff --git a/components/chart/styles.js b/components/chart/styles.js index 2c101e6..8c2ab2e 100644 --- a/components/chart/styles.js +++ b/components/chart/styles.js @@ -45,9 +45,21 @@ const styles = { bleedingIcon: { fill: '#fb2e01', scale: 0.6, - x: 7, + x: 6, y: 3 }, + mucusIcon: { + cx: config.columnWidth / 2, + cy: 50, + r: 10 + }, + mucusIconShades: [ + '#cc99cc', + '#bf7fbf', + '#b266b2', + '#a64ca6', + '#993299' + ], yAxis: { height: config.chartHeight, width: config.columnWidth, diff --git a/components/cycle-day/cycle-day-overview.js b/components/cycle-day/cycle-day-overview.js index a4174a4..526fe82 100644 --- a/components/cycle-day/cycle-day-overview.js +++ b/components/cycle-day/cycle-day-overview.js @@ -10,6 +10,9 @@ import { mucusFeeling as feelingLabels, mucusTexture as textureLabels, mucusNFP as computeSensiplanMucusLabels, + cervixOpening as openingLabels, + cervixFirmness as firmnessLabels, + cervixPosition as positionLabels } from './labels/labels' import cycleDayModule from '../../lib/cycle' import { bleedingDaysSortedByDate } from '../../db' @@ -41,36 +44,53 @@ export default class DayView extends Component { } render() { - const bleedingValue = this.cycleDay.bleeding && this.cycleDay.bleeding.value let bleedingLabel - if (typeof bleedingValue === 'number') { - bleedingLabel = `${bleedingLabels[bleedingValue]}` - if (this.cycleDay.bleeding.exclude) bleedingLabel = "( " + bleedingLabel + " )" + if (this.cycleDay.bleeding) { + const bleeding = this.cycleDay.bleeding + if (typeof bleeding === 'number') { + bleedingLabel = `${bleedingLabels[bleeding]}` + if (bleeding.exclude) bleedingLabel = "( " + bleedingLabel + " )" + } } else { bleedingLabel = 'edit' } - const temperatureValue = this.cycleDay.temperature && this.cycleDay.temperature.value + let temperatureLabel - if (typeof temperatureValue === 'number') { - temperatureLabel = `${temperatureValue} °C - ${this.cycleDay.temperature.time}` - if (this.cycleDay.temperature.exclude) { - temperatureLabel = "( " + temperatureLabel + " )" + if (this.cycleDay.temperature) { + const temperature = this.cycleDay.temperature + if (typeof temperature === 'number') { + temperatureLabel = `${temperature} °C - ${temperature.time}` + if (temperature.exclude) { + temperatureLabel = "( " + temperatureLabel + " )" + } } } else { temperatureLabel = 'edit' } - const mucusFeelingValue = this.cycleDay.mucus && this.cycleDay.mucus.feeling - const mucusTextureValue = this.cycleDay.mucus && this.cycleDay.mucus.texture - const mucusComputedValue = this.cycleDay.mucus && this.cycleDay.mucus.value let mucusLabel - if (typeof mucusFeelingValue === 'number' && typeof mucusTextureValue === 'number') { - mucusLabel = `${feelingLabels[mucusFeelingValue]} + ${textureLabels[mucusTextureValue]} ( ${computeSensiplanMucusLabels[mucusComputedValue]} )` - if (this.cycleDay.mucus.exclude) mucusLabel = "( " + mucusLabel + " )" + if (this.cycleDay.mucus) { + const mucus = this.cycleDay.mucus + if (typeof mucus.feeling === 'number' && typeof mucus.texture === 'number') { + mucusLabel = `${feelingLabels[mucus.feeling]} + ${textureLabels[mucus.texture]} ( ${computeSensiplanMucusLabels[mucus.computedNfp]} )` + if (mucus.exclude) mucusLabel = "( " + mucusLabel + " )" + } } else { mucusLabel = 'edit' } + let cervixLabel + if (this.cycleDay.cervix) { + const cervix = this.cycleDay.cervix + if (cervix.opening > -1 && cervix.firmness > -1) { + cervixLabel = `${openingLabels[cervix.opening]} + ${firmnessLabels[cervix.firmness]}` + if (cervix.position > -1) cervixLabel += `+ ${positionLabels[cervix.position]}` + if (cervix.exclude) cervixLabel = "( " + cervixLabel + " )" + } + } else { + cervixLabel = 'edit' + } + return ( @@ -100,6 +120,15 @@ export default class DayView extends Component { + + Cervix + + + + ) } diff --git a/components/cycle-day/index.js b/components/cycle-day/index.js index 78c3293..e0eb666 100644 --- a/components/cycle-day/index.js +++ b/components/cycle-day/index.js @@ -1,7 +1,8 @@ import React, { Component } from 'react' import { View, - Text + Text, + ScrollView } from 'react-native' import cycleModule from '../../lib/cycle' import { getFertilityStatusStringForDay } from '../../lib/sympto-adapter' @@ -10,6 +11,7 @@ import BleedingEditView from './symptoms/bleeding' import TemperatureEditView from './symptoms/temperature' import MucusEditView from './symptoms/mucus' import { formatDateForViewHeader } from './labels/format' +import CervixEditView from './symptoms/cervix' import styles from '../../styles' import actionButtonModule from './action-buttons' @@ -35,7 +37,7 @@ export default class Day extends Component { const cycleDayNumber = getCycleDayNumber(this.cycleDay.date) const fertilityStatus = getFertilityStatusStringForDay(this.cycleDay.date) return ( - + {formatDateForViewHeader(this.cycleDay.date)} @@ -56,11 +58,12 @@ export default class Day extends Component { { dayView: , bleedingEditView: , temperatureEditView: , - mucusEditView: + mucusEditView: , + cervixEditView: }[this.state.visibleComponent] } - + ) } } diff --git a/components/cycle-day/labels/labels.js b/components/cycle-day/labels/labels.js index 459370b..25909df 100644 --- a/components/cycle-day/labels/labels.js +++ b/components/cycle-day/labels/labels.js @@ -2,10 +2,16 @@ const bleeding = ['spotting', 'light', 'medium', 'heavy'] const mucusFeeling = ['dry', 'nothing', 'wet', 'slippery'] const mucusTexture = ['nothing', 'creamy', 'egg white'] const mucusNFP = ['t', 'Ø', 'f', 'S', '+S'] +const cervixOpening = ['closed', 'medium', 'open'] +const cervixFirmness = ['hard', 'soft'] +const cervixPosition = ['low', 'medium', 'high'] export { bleeding, mucusFeeling, mucusTexture, - mucusNFP + mucusNFP, + cervixOpening, + cervixFirmness, + cervixPosition } diff --git a/components/cycle-day/symptoms/cervix.js b/components/cycle-day/symptoms/cervix.js new file mode 100644 index 0000000..59d9d8b --- /dev/null +++ b/components/cycle-day/symptoms/cervix.js @@ -0,0 +1,121 @@ +import React, { Component } from 'react' +import { + View, + Text, + Switch +} from 'react-native' +import RadioForm from 'react-native-simple-radio-button' +import styles from '../../../styles' +import { saveSymptom } from '../../../db' +import { + cervixOpening as openingLabels, + cervixFirmness as firmnessLabels, + cervixPosition as positionLabels +} from '../labels/labels' + +export default class Cervix extends Component { + constructor(props) { + super(props) + this.cycleDay = props.cycleDay + this.makeActionButtons = props.makeActionButtons + this.state = { + exclude: this.cycleDay.cervix ? this.cycleDay.cervix.exclude : false + }; + + /* eslint-disable react/no-direct-mutation-state */ + ['opening', 'firmness', 'position'].forEach(label => { + this.state[label] = this.cycleDay.cervix && this.cycleDay.cervix[label] + if (typeof this.state[label] !== 'number') { + this.state[label] = -1 + } + }) + /* eslint-enable react/no-direct-mutation-state */ + } + + render() { + const cervixOpeningRadioProps = [ + {label: openingLabels[0], value: 0}, + {label: openingLabels[1], value: 1}, + {label: openingLabels[2], value: 2} + ] + const cervixFirmnessRadioProps = [ + {label: firmnessLabels[0], value: 0 }, + {label: firmnessLabels[1], value: 1 } + ] + const cervixPositionRadioProps = [ + {label: positionLabels[0], value: 0 }, + {label: positionLabels[1], value: 1 }, + {label: positionLabels[2], value: 2 } + ] + return( + + Cervix + Opening + + { + this.setState({opening: itemValue}) + }} + /> + + Firmness + + { + this.setState({firmness: itemValue}) + }} + /> + + Position + + { + this.setState({position: itemValue}) + }} + /> + + + Exclude + { + this.setState({ exclude: val }) + }} + value={this.state.exclude} + /> + + + {this.makeActionButtons( + { + symptom: 'cervix', + cycleDay: this.cycleDay, + saveAction: () => { + saveSymptom('cervix', this.cycleDay, { + opening: this.state.opening, + firmness: this.state.firmness, + position: this.state.position, + exclude: this.state.exclude + }) + }, + saveDisabled: this.state.opening === -1 || this.state.firmness === -1 + } + )} + + + ) + } +} diff --git a/components/cycle-day/symptoms/mucus.js b/components/cycle-day/symptoms/mucus.js index 68ff292..abafe56 100644 --- a/components/cycle-day/symptoms/mucus.js +++ b/components/cycle-day/symptoms/mucus.js @@ -21,17 +21,17 @@ export default class Mucus extends Component { this.makeActionButtons = props.makeActionButtons this.state = { exclude: this.cycleDay.mucus ? this.cycleDay.mucus.exclude : false - } + }; - this.state.currentFeelingValue = this.cycleDay.mucus && this.cycleDay.mucus.feeling - if (typeof this.state.currentFeelingValue !== 'number') { - this.state.currentFeelingValue = -1 - } + /* eslint-disable react/no-direct-mutation-state */ + ['feeling', 'texture'].forEach(label => { + this.state[label] = this.cycleDay.mucus && this.cycleDay.mucus[label] + if (typeof this.state[label] !== 'number') { + this.state[label] = -1 + } + }) + /* eslint-enable react/no-direct-mutation-state */ - this.state.currentTextureValue = this.cycleDay.mucus && this.cycleDay.mucus.texture - if (typeof this.state.currentTextureValue !== 'number') { - this.state.currentTextureValue = -1 - } } render() { @@ -53,12 +53,12 @@ export default class Mucus extends Component { { - this.setState({ currentFeelingValue: itemValue }) + this.setState({feeling: itemValue }) }} /> @@ -66,12 +66,12 @@ export default class Mucus extends Component { { - this.setState({ currentTextureValue: itemValue }) + this.setState({texture: itemValue }) }} /> @@ -92,13 +92,13 @@ export default class Mucus extends Component { cycleDay: this.cycleDay, saveAction: () => { saveSymptom('mucus', this.cycleDay, { - feeling: this.state.currentFeelingValue, - texture: this.state.currentTextureValue, - value: computeSensiplanValue(this.state.currentFeelingValue, this.state.currentTextureValue), + feeling: this.state.feeling, + texture: this.state.texture, + value: computeSensiplanValue(this.state.feeling, this.state.texture), exclude: this.state.exclude }) }, - saveDisabled: this.state.currentFeelingValue === -1 || this.state.currentTextureValue === -1 + saveDisabled: this.state.feeling === -1 || this.state.texture === -1 } )} diff --git a/components/cycle-day/symptoms/temperature.js b/components/cycle-day/symptoms/temperature.js index b9a3d5b..11961a9 100644 --- a/components/cycle-day/symptoms/temperature.js +++ b/components/cycle-day/symptoms/temperature.js @@ -3,8 +3,10 @@ import { View, Text, TextInput, - Switch + Switch, + Keyboard } from 'react-native' +import DateTimePicker from 'react-native-modal-datetime-picker-nevo' import { getPreviousTemperature, saveSymptom } from '../../../db' import styles from '../../../styles' @@ -17,9 +19,11 @@ export default class Temp extends Component { this.makeActionButtons = props.makeActionButtons let initialValue - if (this.cycleDay.temperature) { - initialValue = this.cycleDay.temperature.value.toString() - this.time = this.cycleDay.temperature.time + const temp = this.cycleDay.temperature + + if (temp) { + initialValue = temp.value.toString() + this.time = temp.time } else { const prevTemp = getPreviousTemperature(this.cycleDay) initialValue = prevTemp ? prevTemp.toString() : '' @@ -27,7 +31,9 @@ export default class Temp extends Component { this.state = { currentValue: initialValue, - exclude: this.cycleDay.temperature ? this.cycleDay.temperature.exclude : false + exclude: temp ? temp.exclude : false, + time: this.time || LocalTime.now().truncatedTo(ChronoUnit.MINUTES).toString(), + isTimePickerVisible: false } } @@ -47,6 +53,28 @@ export default class Temp extends Component { value={this.state.currentValue} /> + + Time + { + Keyboard.dismiss() + this.setState({isTimePickerVisible: true}) + }} + value={this.state.time} + /> + + { + this.setState({ + time: `${jsDate.getHours()}:${jsDate.getMinutes()}`, + isTimePickerVisible: false + }) + }} + onCancel={() => this.setState({isTimePickerVisible: false})} + /> Exclude { const dataToSave = { value: Number(this.state.currentValue), - exclude: this.state.exclude - } - if (!cycleDay.temperature || cycleDay.temperature && !cycleDay.temperature.time) { - const now = LocalTime.now().truncatedTo(ChronoUnit.MINUTES).toString() - dataToSave.time = now + exclude: this.state.exclude, + time: this.state.time } saveSymptom('temperature', cycleDay, dataToSave) }, - saveDisabled: this.state.currentValue === '' + saveDisabled: this.state.currentValue === '' || isInvalidTime(this.state.time) })} ) } } + +function isInvalidTime(timeString) { + try { + LocalTime.parse(timeString) + } catch (err) { + return true + } + return false +} \ No newline at end of file diff --git a/components/home.js b/components/home.js index 07b50db..48af5f5 100644 --- a/components/home.js +++ b/components/home.js @@ -2,7 +2,8 @@ import React, { Component } from 'react' import { View, Button, - Text + Text, + ScrollView } from 'react-native' import { LocalDate } from 'js-joda' import styles from '../styles/index' @@ -46,7 +47,7 @@ export default class Home extends Component { render() { const navigate = this.props.navigation.navigate return ( - + {this.state.welcomeText} @@ -80,7 +81,7 @@ export default class Home extends Component { - + ) } } diff --git a/db/index.js b/db/index.js index 948d805..ed3e275 100644 --- a/db/index.js +++ b/db/index.js @@ -10,7 +10,11 @@ const TemperatureSchema = { name: 'Temperature', properties: { value: 'double', - exclude: 'bool' + exclude: 'bool', + time: { + type: 'string', + optional: true + }, } } @@ -32,6 +36,16 @@ const MucusSchema = { } } +const CervixSchema = { + name: 'Cervix', + properties: { + opening: 'int', + firmness: 'int', + position: {type: 'int', optional: true }, + exclude: 'bool' + } +} + const CycleDaySchema = { name: 'CycleDay', primaryKey: 'date', @@ -48,6 +62,10 @@ const CycleDaySchema = { mucus: { type: 'Mucus', optional: true + }, + cervix: { + type: 'Cervix', + optional: true } } } @@ -57,7 +75,8 @@ const realmConfig = { CycleDaySchema, TemperatureSchema, BleedingSchema, - MucusSchema + MucusSchema, + CervixSchema ], // we only want this in dev mode deleteRealmIfMigrationNeeded: true diff --git a/package-lock.json b/package-lock.json index c93bbd1..995d84f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3482,13 +3482,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3501,18 +3499,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -3615,8 +3610,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -3626,7 +3620,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3639,20 +3632,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.2.4", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -3669,7 +3659,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3742,8 +3731,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -3753,7 +3741,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3859,7 +3846,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6272,6 +6258,14 @@ "yargs": "^9.0.0" } }, + "react-native-animatable": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.3.0.tgz", + "integrity": "sha512-GGYEYvderfzPZcPnw7xov4nlRmi9d6oqcIzx0fGkUUsMshOQEtq5IEzFp3np0uTB9n8/gZIZcdbUPggVlVydMg==", + "requires": { + "prop-types": "^15.5.10" + } + }, "react-native-calendars": { "version": "1.19.3", "resolved": "https://registry.npmjs.org/react-native-calendars/-/react-native-calendars-1.19.3.tgz", @@ -6304,6 +6298,47 @@ "react-native-drawer-layout": "1.3.2" } }, + "react-native-modal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-3.1.0.tgz", + "integrity": "sha512-DsF4r8ScW0y+bn+7ThzBLP4az/hsi+e9ge79vExkjpw6uNFwNWQPY21BRE4uyip7PpsqEDSyvVb8GH3UXZIYcA==", + "requires": { + "prop-types": "15.5.10", + "react-native-animatable": "^1.2.3" + }, + "dependencies": { + "prop-types": { + "version": "15.5.10", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz", + "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=", + "requires": { + "fbjs": "^0.8.9", + "loose-envify": "^1.3.1" + } + } + } + }, + "react-native-modal-datetime-picker-nevo": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/react-native-modal-datetime-picker-nevo/-/react-native-modal-datetime-picker-nevo-4.11.0.tgz", + "integrity": "sha512-nDUlHyUoRHO+fzt0cc2g+a8kYx+RZQZnjVY01jDIspAbRGTJuCt9x+LjTvhuDQpoXEJYvBEw1W7dz9mn3tNsoQ==", + "requires": { + "moment": "^2.19.0", + "prop-types": "15.5.10", + "react-native-modal": "3.1.0" + }, + "dependencies": { + "prop-types": { + "version": "15.5.10", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz", + "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=", + "requires": { + "fbjs": "^0.8.9", + "loose-envify": "^1.3.1" + } + } + } + }, "react-native-safe-area-view": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/react-native-safe-area-view/-/react-native-safe-area-view-0.8.0.tgz", diff --git a/package.json b/package.json index 21a8c26..10fa366 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "react": "16.3.1", "react-native": "0.55.4", "react-native-calendars": "^1.19.3", + "react-native-modal-datetime-picker-nevo": "^4.11.0", "react-native-simple-radio-button": "^2.7.1", "react-native-svg": "^6.3.1", "react-navigation": "^2.0.4",