Merge branch '363-alert-for-cervix-and-mucus-when-trying-to-navigate-away-and-one-value-is-missing' into 'master'

Basic auto save

Closes #363

See merge request bloodyhealth/drip!214
This commit is contained in:
Julia Friesel
2019-05-13 18:56:40 +00:00
22 changed files with 696 additions and 558 deletions
+5 -12
View File
@@ -125,19 +125,12 @@ export default class App extends Component {
goBack={this.handleBackButtonPress} goBack={this.handleBackButtonPress}
/> />
} }
{this.isSymptomView() &&
<Header
title={title}
isSymptomView={true}
goBack={this.handleBackButtonPress}
date={currentProps.date}
goToSymptomInfo={() => this.navigate(INFO_SYMPTOM_PAGE, {
symptomView: currentPage,
...currentProps
})}
/>}
<Page navigate={this.navigate} {...currentProps} /> <Page
navigate={this.navigate}
{...currentProps}
handleBackButtonPress={this.handleBackButtonPress}
/>
{!this.isSymptomView() && {!this.isSymptomView() &&
<Menu navigate={this.navigate} currentPage={currentPage} /> <Menu navigate={this.navigate} currentPage={currentPage} />
@@ -1,7 +1,6 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { import {
View, TouchableOpacity, Text, Alert, ToastAndroid View, TouchableOpacity, Text, Alert} from 'react-native'
} from 'react-native'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons' import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
import { saveSymptom } from '../../../db' import { saveSymptom } from '../../../db'
import styles, {iconStyles} from '../../../styles' import styles, {iconStyles} from '../../../styles'
@@ -14,10 +13,8 @@ export default class ActionButtonFooter extends Component {
symptom, symptom,
currentSymptomValue, currentSymptomValue,
date, date,
saveAction,
saveDisabled,
navigate, navigate,
autoShowDayView = true} }
= this.props = this.props
const navigateToOverView = () => navigate('CycleDay', {date}) const navigateToOverView = () => navigate('CycleDay', {date})
const buttons = [ const buttons = [
@@ -44,43 +41,19 @@ export default class ActionButtonFooter extends Component {
(Object.values(currentSymptomValue).every(x => !x) && currentSymptomValue.constructor === Object) (Object.values(currentSymptomValue).every(x => !x) && currentSymptomValue.constructor === Object)
), ),
icon: 'delete-outline' icon: 'delete-outline'
}, {
title: labels.save,
action: () => {
if(saveDisabled) {
ToastAndroid.show(labels.disabledInfo, ToastAndroid.LONG)
} else {
saveAction()
if (autoShowDayView) navigateToOverView()
}
},
disabledCondition: saveDisabled,
icon: 'content-save-outline'
} }
] ]
return ( return (
<View style={styles.actionButtonFooter}> <View style={styles.menu}>
{buttons.map(({ title, action, disabledCondition, icon }, i) => { {buttons.map(({ title, action, icon }, i) => {
const textStyle = [styles.menuText] const textStyle = [styles.menuText]
if (disabledCondition) {
textStyle.push(styles.menuTextInActive)
}
const iconStyle = disabledCondition ?
Object.assign(
{},
iconStyles.menuIcon,
iconStyles.menuIconInactive
)
:
iconStyles.menuIcon
return ( return (
<TouchableOpacity <TouchableOpacity
onPress={action} onPress={action}
style={styles.actionButtonItem} style={styles.actionButtonItem}
key={i.toString()} key={i.toString()}
> >
<Icon name={icon} {...iconStyle} /> <Icon name={icon} {...iconStyles.menuIcon} />
<Text style={textStyle}> <Text style={textStyle}>
{title.toLowerCase()} {title.toLowerCase()}
</Text> </Text>
+41 -46
View File
@@ -1,29 +1,39 @@
import React, { Component } from 'react' import React from 'react'
import { import {
View,
Switch, Switch,
ScrollView ScrollView
} from 'react-native' } from 'react-native'
import styles from '../../../styles' import styles from '../../../styles'
import { saveSymptom } from '../../../db'
import { bleeding } from '../../../i18n/en/cycle-day' import { bleeding } from '../../../i18n/en/cycle-day'
import ActionButtonFooter from './action-button-footer'
import SelectTabGroup from '../select-tab-group' import SelectTabGroup from '../select-tab-group'
import SymptomSection from './symptom-section' import SymptomSection from './symptom-section'
import SymptomView from './symptom-view'
export default class Bleeding extends Component { export default class Bleeding extends SymptomView {
constructor(props) { constructor(props) {
super(props) super(props)
const cycleDay = props.cycleDay const cycleDay = props.cycleDay
this.bleeding = cycleDay && cycleDay.bleeding this.bleeding = cycleDay && cycleDay.bleeding
this.makeActionButtons = props.makeActionButtons
this.state = { this.state = {
currentValue: this.bleeding && this.bleeding.value, currentValue: this.bleeding && this.bleeding.value,
exclude: this.bleeding ? this.bleeding.exclude : false exclude: this.bleeding ? this.bleeding.exclude : false
} }
} }
render() { symptomName = 'bleeding'
onBackButtonPress() {
if (typeof this.state.currentValue != 'number') {
this.deleteSymptomEntry()
return
}
this.saveSymptomEntry({
value: this.state.currentValue,
exclude: this.state.exclude
})
}
renderContent() {
const bleedingRadioProps = [ const bleedingRadioProps = [
{ label: bleeding.labels[0], value: 0 }, { label: bleeding.labels[0], value: 0 },
{ label: bleeding.labels[1], value: 1 }, { label: bleeding.labels[1], value: 1 },
@@ -31,45 +41,30 @@ export default class Bleeding extends Component {
{ label: bleeding.labels[3], value: 3 }, { label: bleeding.labels[3], value: 3 },
] ]
return ( return (
<View style={{ flex: 1 }}> <ScrollView style={styles.page}>
<ScrollView style={styles.page}> <SymptomSection
<SymptomSection header={bleeding.heaviness.header}
header={bleeding.heaviness.header} explainer={bleeding.heaviness.explainer}
explainer={bleeding.heaviness.explainer} >
> <SelectTabGroup
<SelectTabGroup buttons={bleedingRadioProps}
buttons={bleedingRadioProps} active={this.state.currentValue}
active={this.state.currentValue} onSelect={val => this.setState({ currentValue: val })}
onSelect={val => this.setState({ currentValue: val })} />
/> </SymptomSection>
</SymptomSection> <SymptomSection
<SymptomSection header={bleeding.exclude.header}
header={bleeding.exclude.header} explainer={bleeding.exclude.explainer}
explainer={bleeding.exclude.explainer} inline={true}
inline={true} >
> <Switch
<Switch onValueChange={(val) => {
onValueChange={(val) => { this.setState({ exclude: val })
this.setState({ exclude: val }) }}
}} value={this.state.exclude}
value={this.state.exclude} />
/> </SymptomSection>
</SymptomSection> </ScrollView>
</ScrollView>
<ActionButtonFooter
symptom='bleeding'
date={this.props.date}
currentSymptomValue={this.bleeding}
saveAction={() => {
saveSymptom('bleeding', this.props.date, {
value: this.state.currentValue,
exclude: this.state.exclude
})
}}
saveDisabled={typeof this.state.currentValue != 'number'}
navigate={this.props.navigate}
/>
</View>
) )
} }
} }
+67 -71
View File
@@ -1,27 +1,40 @@
import React, { Component } from 'react' import React from 'react'
import { import {
View,
Switch, Switch,
ScrollView ScrollView
} from 'react-native' } from 'react-native'
import styles from '../../../styles' import styles from '../../../styles'
import { saveSymptom } from '../../../db'
import { cervix as labels } from '../../../i18n/en/cycle-day' import { cervix as labels } from '../../../i18n/en/cycle-day'
import ActionButtonFooter from './action-button-footer'
import SelectTabGroup from '../select-tab-group' import SelectTabGroup from '../select-tab-group'
import SymptomSection from './symptom-section' import SymptomSection from './symptom-section'
import { ActionHint } from '../../app-text' import SymptomView from './symptom-view'
export default class Cervix extends Component { export default class Cervix extends SymptomView {
constructor(props) { constructor(props) {
super(props) super(props)
const cycleDay = props.cycleDay const cycleDay = props.cycleDay
this.cervix = cycleDay && cycleDay.cervix this.cervix = cycleDay && cycleDay.cervix
this.makeActionButtons = props.makeActionButtons
this.state = this.cervix ? this.cervix : {} this.state = this.cervix ? this.cervix : {}
} }
render() { symptomName = 'cervix'
onBackButtonPress() {
const nothingEntered = ['opening', 'firmness', 'position'].every(val => typeof this.state[val] != 'number')
if (nothingEntered) {
this.deleteSymptomEntry()
return
}
this.saveSymptomEntry({
opening: this.state.opening,
firmness: this.state.firmness,
position: this.state.position,
exclude: Boolean(this.state.exclude)
})
}
renderContent() {
const cervixOpeningRadioProps = [ const cervixOpeningRadioProps = [
{ label: labels.opening.categories[0], value: 0 }, { label: labels.opening.categories[0], value: 0 },
{ label: labels.opening.categories[1], value: 1 }, { label: labels.opening.categories[1], value: 1 },
@@ -36,70 +49,53 @@ export default class Cervix extends Component {
{ label: labels.position.categories[1], value: 1 }, { label: labels.position.categories[1], value: 1 },
{ label: labels.position.categories[2], value: 2 } { label: labels.position.categories[2], value: 2 }
] ]
const mandatoryNotCompletedYet = typeof this.state.opening != 'number' || typeof this.state.firmness != 'number' // TODO saving this info for notice when leaving incomplete data
// const mandatoryNotCompleted = typeof this.state.opening != 'number' || typeof this.state.firmness != 'number'
return ( return (
<View style={{ flex: 1 }}> <ScrollView style={styles.page}>
<ScrollView style={styles.page}> <SymptomSection
<SymptomSection header="Opening"
header="Opening" explainer={labels.opening.explainer}
explainer={labels.opening.explainer} >
> <SelectTabGroup
<SelectTabGroup buttons={cervixOpeningRadioProps}
buttons={cervixOpeningRadioProps} active={this.state.opening}
active={this.state.opening} onSelect={val => this.setState({ opening: val })}
onSelect={val => this.setState({ opening: val })} />
/> </SymptomSection>
</SymptomSection> <SymptomSection
<SymptomSection header="Firmness"
header="Firmness" explainer={labels.firmness.explainer}
explainer={labels.firmness.explainer} >
> <SelectTabGroup
<SelectTabGroup buttons={cervixFirmnessRadioProps}
buttons={cervixFirmnessRadioProps} active={this.state.firmness}
active={this.state.firmness} onSelect={val => this.setState({ firmness: val })}
onSelect={val => this.setState({ firmness: val })} />
/> </SymptomSection>
</SymptomSection> <SymptomSection
<SymptomSection header="Position"
header="Position" explainer={labels.position.explainer}
explainer={labels.position.explainer} >
> <SelectTabGroup
<SelectTabGroup buttons={cervixPositionRadioProps}
buttons={cervixPositionRadioProps} active={this.state.position}
active={this.state.position} onSelect={val => this.setState({ position: val })}
onSelect={val => this.setState({ position: val })} />
/> </SymptomSection>
</SymptomSection> <SymptomSection
<SymptomSection header="Exclude"
header="Exclude" explainer="You can exclude this value if you don't want to use it for fertility detection"
explainer="You can exclude this value if you don't want to use it for fertility detection" inline={true}
inline={true} >
> <Switch
<Switch onValueChange={(val) => {
onValueChange={(val) => { this.setState({ exclude: val })
this.setState({ exclude: val }) }}
}} value={this.state.exclude}
value={this.state.exclude} />
/> </SymptomSection>
</SymptomSection> </ScrollView>
</ScrollView>
<ActionHint isVisible={mandatoryNotCompletedYet}>{labels.actionHint}</ActionHint>
<ActionButtonFooter
symptom='cervix'
date={this.props.date}
currentSymptomValue={this.cervix}
saveAction={() => {
saveSymptom('cervix', this.props.date, {
opening: this.state.opening,
firmness: this.state.firmness,
position: this.state.position,
exclude: Boolean(this.state.exclude)
})
}}
saveDisabled={mandatoryNotCompletedYet}
navigate={this.props.navigate}
/>
</View>
) )
} }
} }
+26 -31
View File
@@ -1,56 +1,51 @@
import React, { Component } from 'react' import React from 'react'
import { import {
View,
ScrollView ScrollView
} from 'react-native' } from 'react-native'
import styles from '../../../styles' import styles from '../../../styles'
import { saveSymptom } from '../../../db'
import { intensity, desire } from '../../../i18n/en/cycle-day' import { intensity, desire } from '../../../i18n/en/cycle-day'
import ActionButtonFooter from './action-button-footer'
import SelectTabGroup from '../select-tab-group' import SelectTabGroup from '../select-tab-group'
import SymptomSection from './symptom-section' import SymptomSection from './symptom-section'
import SymptomView from './symptom-view'
export default class Desire extends Component { export default class Desire extends SymptomView {
constructor(props) { constructor(props) {
super(props) super(props)
const cycleDay = props.cycleDay const cycleDay = props.cycleDay
this.desire = cycleDay && cycleDay.desire this.desire = cycleDay && cycleDay.desire
this.makeActionButtons = props.makeActionButtons
const desireValue = this.desire && this.desire.value const desireValue = this.desire && this.desire.value
this.state = { currentValue: desireValue } this.state = { currentValue: desireValue }
} }
render() { symptomName = 'desire'
onBackButtonPress() {
if (typeof this.state.currentValue != 'number') {
this.deleteSymptomEntry()
return
}
this.saveSymptomEntry({ value: this.state.currentValue })
}
renderContent() {
const desireRadioProps = [ const desireRadioProps = [
{ label: intensity[0], value: 0 }, { label: intensity[0], value: 0 },
{ label: intensity[1], value: 1 }, { label: intensity[1], value: 1 },
{ label: intensity[2], value: 2 } { label: intensity[2], value: 2 }
] ]
return ( return (
<View style={{ flex: 1 }}> <ScrollView style={styles.page}>
<ScrollView style={styles.page}> <SymptomSection
<SymptomSection header={desire.header}
header={desire.header} explainer={desire.explainer}
explainer={desire.explainer} >
> <SelectTabGroup
<SelectTabGroup buttons={desireRadioProps}
buttons={desireRadioProps} active={this.state.currentValue}
active={this.state.currentValue} onSelect={val => this.setState({ currentValue: val })}
onSelect={val => this.setState({ currentValue: val })} />
/> </SymptomSection>
</SymptomSection> </ScrollView>
</ScrollView>
<ActionButtonFooter
symptom='desire'
date={this.props.date}
currentSymptomValue={this.desire}
saveAction={() => {
saveSymptom('desire', this.props.date, { value: this.state.currentValue })
}}
saveDisabled={typeof this.state.currentValue != 'number'}
navigate={this.props.navigate}
/>
</View>
) )
} }
} }
+33 -37
View File
@@ -1,17 +1,14 @@
import React, { Component } from 'react' import React from 'react'
import { import {
ScrollView, ScrollView,
TextInput, TextInput} from 'react-native'
View
} from 'react-native'
import { saveSymptom } from '../../../db'
import { mood as labels } from '../../../i18n/en/cycle-day' import { mood as labels } from '../../../i18n/en/cycle-day'
import ActionButtonFooter from './action-button-footer'
import SelectBoxGroup from '../select-box-group' import SelectBoxGroup from '../select-box-group'
import SymptomSection from './symptom-section' import SymptomSection from './symptom-section'
import styles from '../../../styles' import styles from '../../../styles'
import SymptomView from './symptom-view'
export default class Mood extends Component { export default class Mood extends SymptomView {
constructor(props) { constructor(props) {
super(props) super(props)
const cycleDay = props.cycleDay const cycleDay = props.cycleDay
@@ -25,6 +22,21 @@ export default class Mood extends Component {
} }
} }
symptomName = "mood"
onBackButtonPress() {
const nothingEntered = Object.values(this.state).every(val => !val)
if (nothingEntered) {
this.deleteSymptomEntry()
return
}
const copyOfState = Object.assign({}, this.state)
if (!copyOfState.other) {
copyOfState.note = null
}
this.saveSymptomEntry(copyOfState)
}
toggleState = (key) => { toggleState = (key) => {
const curr = this.state[key] const curr = this.state[key]
this.setState({[key]: !curr}) this.setState({[key]: !curr})
@@ -33,19 +45,18 @@ export default class Mood extends Component {
} }
} }
render() { renderContent() {
return ( return (
<View style={{ flex: 1 }}> <ScrollView style={styles.page}>
<ScrollView style={styles.page}> <SymptomSection
<SymptomSection explainer={labels.explainer}
explainer={labels.explainer} >
> <SelectBoxGroup
<SelectBoxGroup labels={labels.categories}
labels={labels.categories} onSelect={this.toggleState}
onSelect={this.toggleState} optionsState={this.state}
optionsState={this.state} />
/> { this.state.other &&
{ this.state.other &&
<TextInput <TextInput
autoFocus={this.state.focusTextArea} autoFocus={this.state.focusTextArea}
multiline={true} multiline={true}
@@ -55,24 +66,9 @@ export default class Mood extends Component {
this.setState({note: val}) this.setState({note: val})
}} }}
/> />
} }
</SymptomSection> </SymptomSection>
</ScrollView> </ScrollView>
<ActionButtonFooter
symptom='mood'
date={this.props.date}
currentSymptomValue={this.state}
saveAction={() => {
const copyOfState = Object.assign({}, this.state)
if (!copyOfState.other) {
copyOfState.note = null
}
saveSymptom('mood', this.props.date, copyOfState)
}}
saveDisabled={Object.values(this.state).every(value => !value)}
navigate={this.props.navigate}
/>
</View>
) )
} }
} }
+59 -63
View File
@@ -1,28 +1,43 @@
import React, { Component } from 'react' import React from 'react'
import { import {
View,
Switch, Switch,
ScrollView ScrollView
} from 'react-native' } from 'react-native'
import styles from '../../../styles' import styles from '../../../styles'
import { saveSymptom } from '../../../db'
import { mucus as labels } from '../../../i18n/en/cycle-day' import { mucus as labels } from '../../../i18n/en/cycle-day'
import computeNfpValue from '../../../lib/nfp-mucus' import computeNfpValue from '../../../lib/nfp-mucus'
import ActionButtonFooter from './action-button-footer'
import SelectTabGroup from '../select-tab-group' import SelectTabGroup from '../select-tab-group'
import SymptomSection from './symptom-section' import SymptomSection from './symptom-section'
import { ActionHint } from '../../app-text' import SymptomView from './symptom-view'
export default class Mucus extends Component { export default class Mucus extends SymptomView {
constructor(props) { constructor(props) {
super(props) super(props)
const cycleDay = props.cycleDay const cycleDay = props.cycleDay
this.mucus = cycleDay && cycleDay.mucus this.mucus = cycleDay && cycleDay.mucus
this.makeActionButtons = props.makeActionButtons
this.state = this.mucus ? this.mucus : {} this.state = this.mucus ? this.mucus : {}
} }
render() { symptomName = 'mucus'
onBackButtonPress() {
const nothingEntered = ['feeling', 'texture'].every(val => typeof this.state[val] != 'number')
if (nothingEntered) {
this.deleteSymptomEntry()
return
}
const feeling = this.state.feeling
const texture = this.state.texture
this.saveSymptomEntry({
feeling,
texture,
value: computeNfpValue(feeling, texture),
exclude: Boolean(this.state.exclude)
})
}
renderContent() {
const mucusFeeling = [ const mucusFeeling = [
{ label: labels.feeling.categories[0], value: 0 }, { label: labels.feeling.categories[0], value: 0 },
{ label: labels.feeling.categories[1], value: 1 }, { label: labels.feeling.categories[1], value: 1 },
@@ -34,62 +49,43 @@ export default class Mucus extends Component {
{ label: labels.texture.categories[1], value: 1 }, { label: labels.texture.categories[1], value: 1 },
{ label: labels.texture.categories[2], value: 2 } { label: labels.texture.categories[2], value: 2 }
] ]
const mandatoryNotCompletedYet = typeof this.state.feeling != 'number' || typeof this.state.texture != 'number' // TODO leaving this info for notice when leaving incomplete data
// const mandatoryNotCompletedYet = typeof this.state.feeling != 'number' || typeof this.state.texture != 'number'
return ( return (
<View style={{ flex: 1 }}> <ScrollView style={styles.page}>
<ScrollView style={styles.page}> <SymptomSection
<SymptomSection header='Feeling'
header='Feeling' explainer={labels.feeling.explainer}
explainer={labels.feeling.explainer} >
> <SelectTabGroup
<SelectTabGroup buttons={mucusFeeling}
buttons={mucusFeeling} onSelect={val => this.setState({ feeling: val })}
onSelect={val => this.setState({ feeling: val })} active={this.state.feeling}
active={this.state.feeling} />
/> </SymptomSection>
</SymptomSection> <SymptomSection
<SymptomSection header='Texture'
header='Texture' explainer={labels.texture.explainer}
explainer={labels.texture.explainer} >
> <SelectTabGroup
<SelectTabGroup buttons={mucusTexture}
buttons={mucusTexture} onSelect={val => this.setState({ texture: val })}
onSelect={val => this.setState({ texture: val })} active={this.state.texture}
active={this.state.texture} />
/> </SymptomSection>
</SymptomSection> <SymptomSection
<SymptomSection header="Exclude"
header="Exclude" explainer={labels.excludeExplainer}
explainer={labels.excludeExplainer} inline={true}
inline={true} >
> <Switch
<Switch onValueChange={(val) => {
onValueChange={(val) => { this.setState({ exclude: val })
this.setState({ exclude: val }) }}
}} value={this.state.exclude}
value={this.state.exclude} />
/> </SymptomSection>
</SymptomSection> </ScrollView>
</ScrollView>
<ActionHint isVisible={mandatoryNotCompletedYet}>{labels.actionHint}</ActionHint>
<ActionButtonFooter
symptom='mucus'
date={this.props.date}
currentSymptomValue={this.mucus}
saveAction={() => {
const feeling = this.state.feeling
const texture = this.state.texture
saveSymptom('mucus', this.props.date, {
feeling,
texture,
value: computeNfpValue(feeling, texture),
exclude: Boolean(this.state.exclude)
})
}}
saveDisabled={mandatoryNotCompletedYet}
navigate={this.props.navigate}
/>
</View>
) )
} }
} }
+31 -36
View File
@@ -1,60 +1,55 @@
import React, { Component } from 'react' import React from 'react'
import { import {
View,
ScrollView, ScrollView,
TextInput, TextInput,
} from 'react-native' } from 'react-native'
import styles from '../../../styles' import styles from '../../../styles'
import { saveSymptom } from '../../../db'
import ActionButtonFooter from './action-button-footer'
import SymptomSection from './symptom-section' import SymptomSection from './symptom-section'
import { noteExplainer } from '../../../i18n/en/cycle-day' import { noteExplainer } from '../../../i18n/en/cycle-day'
import { shared as sharedLabels } from '../../../i18n/en/labels' import { shared as sharedLabels } from '../../../i18n/en/labels'
import SymptomView from './symptom-view'
export default class Note extends Component { export default class Note extends SymptomView {
constructor(props) { constructor(props) {
super(props) super(props)
const cycleDay = props.cycleDay const cycleDay = props.cycleDay
this.note = cycleDay && cycleDay.note this.note = cycleDay && cycleDay.note
this.makeActionButtons = props.makeActionButtons
this.state = { this.state = {
currentValue: this.note && this.note.value || '' currentValue: this.note && this.note.value || ''
} }
} }
render() { symptomName = 'note'
onBackButtonPress() {
if (!this.state.currentValue) {
this.deleteSymptomEntry()
return
}
this.saveSymptomEntry({
value: this.state.currentValue
})
}
renderContent() {
return ( return (
<View style={{ flex: 1 }}> <ScrollView style={styles.page}>
<ScrollView style={styles.page}> <SymptomSection
<SymptomSection explainer={noteExplainer}
explainer={noteExplainer} >
> <TextInput
<TextInput autoFocus={!this.state.currentValue}
autoFocus={!this.state.currentValue} multiline={true}
multiline={true} placeholder={sharedLabels.enter}
placeholder={sharedLabels.enter} onChangeText={(val) => {
onChangeText={(val) => { this.setState({ currentValue: val })
this.setState({ currentValue: val }) }}
}} value={this.state.currentValue}
value={this.state.currentValue} />
/> </SymptomSection>
</SymptomSection> </ScrollView>
</ScrollView>
<ActionButtonFooter
symptom='note'
date={this.props.date}
currentSymptomValue={this.note}
saveAction={() => {
saveSymptom('note', this.props.date, {
value: this.state.currentValue
})
}}
saveDisabled={!this.state.currentValue}
navigate={this.props.navigate}
/>
</View>
) )
} }
} }
+33 -36
View File
@@ -1,18 +1,16 @@
import React, { Component } from 'react' import React from 'react'
import { import {
ScrollView, ScrollView,
TextInput, TextInput,
View
} from 'react-native' } from 'react-native'
import { saveSymptom } from '../../../db'
import { pain as labels } from '../../../i18n/en/cycle-day' import { pain as labels } from '../../../i18n/en/cycle-day'
import { shared as sharedLabels } from '../../../i18n/en/labels' import { shared as sharedLabels } from '../../../i18n/en/labels'
import ActionButtonFooter from './action-button-footer'
import SelectBoxGroup from '../select-box-group' import SelectBoxGroup from '../select-box-group'
import SymptomSection from './symptom-section' import SymptomSection from './symptom-section'
import styles from '../../../styles' import styles from '../../../styles'
import SymptomView from './symptom-view'
export default class Pain extends Component { export default class Pain extends SymptomView {
constructor(props) { constructor(props) {
super(props) super(props)
const cycleDay = props.cycleDay const cycleDay = props.cycleDay
@@ -26,6 +24,22 @@ export default class Pain extends Component {
} }
} }
symptomName = 'pain'
onBackButtonPress() {
const nothingEntered = Object.values(this.state).every(val => !val)
if (nothingEntered) {
this.deleteSymptomEntry()
return
}
const copyOfState = Object.assign({}, this.state)
if (!copyOfState.other) {
copyOfState.note = null
}
this.saveSymptomEntry(copyOfState)
}
toggleState = (key) => { toggleState = (key) => {
const curr = this.state[key] const curr = this.state[key]
this.setState({[key]: !curr}) this.setState({[key]: !curr})
@@ -34,19 +48,18 @@ export default class Pain extends Component {
} }
} }
render() { renderContent() {
return ( return (
<View style={{ flex: 1 }}> <ScrollView style={styles.page}>
<ScrollView style={styles.page}> <SymptomSection
<SymptomSection explainer={labels.explainer}
explainer={labels.explainer} >
> <SelectBoxGroup
<SelectBoxGroup labels={labels.categories}
labels={labels.categories} onSelect={this.toggleState}
onSelect={this.toggleState} optionsState={this.state}
optionsState={this.state} />
/> { this.state.other &&
{ this.state.other &&
<TextInput <TextInput
autoFocus={this.state.focusTextArea} autoFocus={this.state.focusTextArea}
multiline={true} multiline={true}
@@ -56,24 +69,8 @@ export default class Pain extends Component {
this.setState({note: val}) this.setState({note: val})
}} }}
/> />
} }
</SymptomSection> </SymptomSection>
</ScrollView> </ScrollView>)
<ActionButtonFooter
symptom='pain'
date={this.props.date}
currentSymptomValue={this.state}
saveAction={() => {
const copyOfState = Object.assign({}, this.state)
if (!copyOfState.other) {
copyOfState.note = null
}
saveSymptom('pain', this.props.date, copyOfState)
}}
saveDisabled={Object.values(this.state).every(value => !value)}
navigate={this.props.navigate}
/>
</View>
)
} }
} }
+44 -46
View File
@@ -1,18 +1,16 @@
import React, { Component } from 'react' import React from 'react'
import { import {
TextInput, TextInput,
View,
ScrollView ScrollView
} from 'react-native' } from 'react-native'
import styles from '../../../styles' import styles from '../../../styles'
import { saveSymptom } from '../../../db'
import { sex as sexLabels, contraceptives as contraceptivesLabels } from '../../../i18n/en/cycle-day' import { sex as sexLabels, contraceptives as contraceptivesLabels } from '../../../i18n/en/cycle-day'
import { shared as sharedLabels } from '../../../i18n/en/labels' import { shared as sharedLabels } from '../../../i18n/en/labels'
import ActionButtonFooter from './action-button-footer'
import SelectBoxGroup from '../select-box-group' import SelectBoxGroup from '../select-box-group'
import SymptomSection from './symptom-section' import SymptomSection from './symptom-section'
import SymptomView from './symptom-view'
export default class Sex extends Component { export default class Sex extends SymptomView {
constructor(props) { constructor(props) {
super(props) super(props)
const cycleDay = props.cycleDay const cycleDay = props.cycleDay
@@ -26,6 +24,22 @@ export default class Sex extends Component {
if (this.state.note) this.state.other = true if (this.state.note) this.state.other = true
} }
symptomName = "sex"
onBackButtonPress() {
const nothingEntered = Object.values(this.state).every(val => !val)
if (nothingEntered) {
this.deleteSymptomEntry()
return
}
const copyOfState = Object.assign({}, this.state)
if (!copyOfState.other) {
copyOfState.note = null
}
this.saveSymptomEntry(copyOfState)
}
toggleState = (key) => { toggleState = (key) => {
const curr = this.state[key] const curr = this.state[key]
this.setState({[key]: !curr}) this.setState({[key]: !curr})
@@ -34,32 +48,31 @@ export default class Sex extends Component {
} }
} }
render() { renderContent() {
return ( return (
<View style={{ flex: 1 }}> <ScrollView style={styles.page}>
<ScrollView style={styles.page}> <SymptomSection
<SymptomSection header={sexLabels.header}
header={sexLabels.header} explainer={sexLabels.explainer}
explainer={sexLabels.explainer} >
> <SelectBoxGroup
<SelectBoxGroup labels={sexLabels.categories}
labels={sexLabels.categories} onSelect={this.toggleState}
onSelect={this.toggleState} optionsState={this.state}
optionsState={this.state} />
/> </SymptomSection>
</SymptomSection> <SymptomSection
<SymptomSection header={contraceptivesLabels.header}
header={contraceptivesLabels.header} explainer={contraceptivesLabels.explainer}
explainer={contraceptivesLabels.explainer} >
> <SelectBoxGroup
<SelectBoxGroup labels={contraceptivesLabels.categories}
labels={contraceptivesLabels.categories} onSelect={this.toggleState}
onSelect={this.toggleState} optionsState={this.state}
optionsState={this.state} />
/> </SymptomSection>
</SymptomSection>
{this.state.other && {this.state.other &&
<TextInput <TextInput
autoFocus={this.state.focusTextArea} autoFocus={this.state.focusTextArea}
multiline={true} multiline={true}
@@ -69,23 +82,8 @@ export default class Sex extends Component {
this.setState({ note: val }) this.setState({ note: val })
}} }}
/> />
} }
</ScrollView> </ScrollView>
<ActionButtonFooter
symptom='sex'
date={this.props.date}
currentSymptomValue={this.state}
saveAction={() => {
const copyOfState = Object.assign({}, this.state)
if (!copyOfState.other) {
copyOfState.note = null
}
saveSymptom('sex', this.props.date, copyOfState)
}}
saveDisabled={Object.values(this.state).every(value => !value)}
navigate={this.props.navigate}
/>
</View>
) )
} }
} }
@@ -0,0 +1,47 @@
import React, { Component } from 'react'
import { BackHandler, View } from 'react-native'
import { saveSymptom } from '../../../db'
import Header from '../../header/symptom-view'
import { headerTitles } from '../../../i18n/en/labels'
export default class SymptomView extends Component {
constructor(props) {
super()
// every specific symptom view provides their own onBackButtonPress method
this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPress.bind(this))
this.globalBackhandler = props.handleBackButtonPress
this.date = props.date
}
saveSymptomEntry(entry) {
saveSymptom(this.symptomName, this.date, entry)
}
deleteSymptomEntry() {
saveSymptom(this.symptomName, this.date)
}
componentWillUnmount() {
this.backHandler.remove()
}
render() {
return (
<View style={{flex: 1}}>
<Header
title={headerTitles[this.symptomName].toLowerCase()}
date={this.date}
goBack={() => {
this.onBackButtonPress()
this.globalBackhandler()
}}
deleteEntry={() => {
this.deleteSymptomEntry()
this.globalBackhandler()
}}
/>
{this.renderContent()}
</View>
)
}
}
+81 -94
View File
@@ -1,4 +1,4 @@
import React, { Component } from 'react' import React from 'react'
import { import {
View, View,
Switch, Switch,
@@ -9,21 +9,21 @@ import {
import DateTimePicker from 'react-native-modal-datetime-picker-nevo' import DateTimePicker from 'react-native-modal-datetime-picker-nevo'
import padWithZeros from '../../helpers/pad-time-with-zeros' import padWithZeros from '../../helpers/pad-time-with-zeros'
import { getPreviousTemperature, saveSymptom } from '../../../db' import { getPreviousTemperature } from '../../../db'
import styles from '../../../styles' import styles from '../../../styles'
import { LocalTime, ChronoUnit } from 'js-joda' import { LocalTime, ChronoUnit } from 'js-joda'
import { temperature as labels } from '../../../i18n/en/cycle-day' import { temperature as labels } from '../../../i18n/en/cycle-day'
import { scaleObservable } from '../../../local-storage' import { scaleObservable } from '../../../local-storage'
import { shared as sharedLabels } from '../../../i18n/en/labels' import { shared as sharedLabels } from '../../../i18n/en/labels'
import ActionButtonFooter from './action-button-footer'
import config from '../../../config' import config from '../../../config'
import AppTextInput from '../../app-text-input' import AppTextInput from '../../app-text-input'
import AppText from '../../app-text' import AppText from '../../app-text'
import SymptomSection from './symptom-section' import SymptomSection from './symptom-section'
import SymptomView from './symptom-view'
const minutes = ChronoUnit.MINUTES const minutes = ChronoUnit.MINUTES
export default class Temp extends Component { export default class Temp extends SymptomView {
constructor(props) { constructor(props) {
super(props) super(props)
const cycleDay = props.cycleDay const cycleDay = props.cycleDay
@@ -45,7 +45,7 @@ export default class Temp extends Component {
this.state.temperature = `${this.state.temperature}.0` this.state.temperature = `${this.state.temperature}.0`
} }
} else { } else {
const prevTemp = getPreviousTemperature(this.props.date) const prevTemp = getPreviousTemperature(props.date)
if (prevTemp) { if (prevTemp) {
this.state.temperature = prevTemp.toString() this.state.temperature = prevTemp.toString()
this.state.isSuggestion = true this.state.isSuggestion = true
@@ -53,6 +53,17 @@ export default class Temp extends Component {
} }
} }
symptomName = 'temperature'
onBackButtonPress() {
if (typeof this.state.temperature != 'string' || this.state.temperature === '') {
this.deleteSymptomEntry()
return
}
this.checkRangeAndSave()
}
saveTemperature = () => { saveTemperature = () => {
const dataToSave = { const dataToSave = {
value: Number(this.state.temperature), value: Number(this.state.temperature),
@@ -60,8 +71,8 @@ export default class Temp extends Component {
time: this.state.time, time: this.state.time,
note: this.state.note note: this.state.note
} }
saveSymptom('temperature', this.props.date, dataToSave)
this.props.navigate('CycleDay', {date: this.props.date}) this.saveSymptomEntry(dataToSave)
} }
checkRangeAndSave = () => { checkRangeAndSave = () => {
@@ -105,102 +116,78 @@ export default class Temp extends Component {
this.setState({ isTimePickerVisible: true }) this.setState({ isTimePickerVisible: true })
} }
render() { renderContent() {
const inputStyle = [styles.temperatureTextInput] const inputStyle = [styles.temperatureTextInput]
if (this.state.isSuggestion) { if (this.state.isSuggestion) {
inputStyle.push(styles.temperatureTextInputSuggestion) inputStyle.push(styles.temperatureTextInputSuggestion)
} }
return ( return (
<View style={{ flex: 1 }}> <ScrollView style={styles.page}>
<ScrollView style={styles.page}> <SymptomSection
<SymptomSection header={labels.temperature.header}
header={labels.temperature.header} explainer={labels.temperature.explainer}
explainer={labels.temperature.explainer} >
> <View style={styles.framedSegmentInlineChildren}>
<View style={styles.framedSegmentInlineChildren}>
<AppTextInput
style={[inputStyle]}
autoFocus={true}
placeholder={this.state.temperature}
value={this.state.temperature}
onChangeText={this.setTemperature}
keyboardType='numeric'
maxLength={5}
onBlur={this.checkRange}
/>
<AppText style={{ marginLeft: 5 }}>°C</AppText>
</View>
</SymptomSection>
<SymptomSection
header={labels.time}
>
<View style={styles.framedSegmentInlineChildren}>
<AppTextInput
style={[styles.temperatureTextInput]}
onFocus={this.showTimePicker}
value={this.state.time}
/>
<DateTimePicker
mode="time"
isVisible={this.state.isTimePickerVisible}
onConfirm={jsDate => {
this.setState({
time: padWithZeros(jsDate),
isTimePickerVisible: false
})
}}
onCancel={() => this.setState({ isTimePickerVisible: false })}
/>
</View>
</SymptomSection>
<SymptomSection
header={labels.note.header}
explainer={labels.note.explainer}
>
<AppTextInput <AppTextInput
multiline={true} style={[inputStyle]}
autoFocus={this.state.focusTextArea} autoFocus={true}
placeholder={sharedLabels.enter} placeholder={this.state.temperature}
value={this.state.note} value={this.state.temperature}
onChangeText={this.setNote} onChangeText={this.setTemperature}
keyboardType='numeric'
maxLength={5}
onBlur={this.checkRange}
/> />
</SymptomSection> <AppText style={{ marginLeft: 5 }}>°C</AppText>
<SymptomSection </View>
header={labels.exclude.header} </SymptomSection>
explainer={labels.exclude.explainer} <SymptomSection
inline={true} header={labels.time}
> >
<Switch <View style={styles.framedSegmentInlineChildren}>
onValueChange={(val) => { <AppTextInput
this.setState({ exclude: val }) style={[styles.temperatureTextInput]}
onFocus={this.showTimePicker}
value={this.state.time}
/>
<DateTimePicker
mode="time"
isVisible={this.state.isTimePickerVisible}
onConfirm={jsDate => {
this.setState({
time: padWithZeros(jsDate),
isTimePickerVisible: false
})
}} }}
value={this.state.exclude} onCancel={() => this.setState({ isTimePickerVisible: false })}
/> />
</SymptomSection> </View>
</ScrollView> </SymptomSection>
<ActionButtonFooter <SymptomSection
symptom='temperature' header={labels.note.header}
date={this.props.date} explainer={labels.note.explainer}
currentSymptomValue={this.temperature} >
saveAction={() => this.checkRangeAndSave()} <AppTextInput
saveDisabled={ multiline={true}
this.state.temperature === '' || autoFocus={this.state.focusTextArea}
isNaN(Number(this.state.temperature)) || placeholder={sharedLabels.enter}
isInvalidTime(this.state.time) value={this.state.note}
} onChangeText={this.setNote}
navigate={this.props.navigate} />
autoShowDayView={false} </SymptomSection>
/> <SymptomSection
</View> header={labels.exclude.header}
explainer={labels.exclude.explainer}
inline={true}
>
<Switch
onValueChange={(val) => {
this.setState({ exclude: val })
}}
value={this.state.exclude}
/>
</SymptomSection>
</ScrollView>
) )
} }
} }
function isInvalidTime(timeString) {
try {
LocalTime.parse(timeString)
} catch (err) {
return true
}
return false
}
+1 -6
View File
@@ -3,7 +3,6 @@ import { Dimensions } from 'react-native'
import CycleDayHeader from './cycle-day' import CycleDayHeader from './cycle-day'
import DefaultHeader from './default' import DefaultHeader from './default'
import BackButtonHeader from './back-button' import BackButtonHeader from './back-button'
import SymptomViewHeader from './symptom-view'
export default function Header(p) { export default function Header(p) {
const middle = Dimensions.get('window').width / 2 const middle = Dimensions.get('window').width / 2
@@ -11,11 +10,7 @@ export default function Header(p) {
if (props.isCycleDayOverView) { if (props.isCycleDayOverView) {
return (<CycleDayHeader {...props} />) return (<CycleDayHeader {...props} />)
} } else if (props.showBackButton) {
else if (props.isSymptomView) {
return (<SymptomViewHeader {...props} />)
}
else if (props.showBackButton) {
return (<BackButtonHeader {...props} />) return (<BackButtonHeader {...props} />)
} }
else { else {
+5 -3
View File
@@ -2,7 +2,8 @@ import React from 'react'
import { import {
View, View,
Text, Text,
TouchableOpacity TouchableOpacity,
Dimensions
} from 'react-native' } from 'react-native'
import styles, { iconStyles } from '../../styles' import styles, { iconStyles } from '../../styles'
import FeatherIcon from 'react-native-vector-icons/Feather' import FeatherIcon from 'react-native-vector-icons/Feather'
@@ -10,11 +11,12 @@ import NavigationArrow from './navigation-arrow'
import formatDate from '../helpers/format-date' import formatDate from '../helpers/format-date'
export default function SymptomViewHeader(props) { export default function SymptomViewHeader(props) {
const middle = Dimensions.get('window').width / 2
return ( return (
<View style={[styles.header, styles.headerCycleDay, styles.headerSymptom]}> <View style={[styles.header, styles.headerCycleDay, styles.headerSymptom]}>
<View <View
style={styles.accentCircle} style={styles.accentCircle}
left={props.middle - styles.accentCircle.width / 2} left={middle - styles.accentCircle.width / 2}
/> />
<NavigationArrow <NavigationArrow
direction='left' direction='left'
@@ -29,7 +31,7 @@ export default function SymptomViewHeader(props) {
</Text> </Text>
</View > </View >
<TouchableOpacity <TouchableOpacity
onPress={() => props.goToSymptomInfo()} onPress={props.deleteEntry}
style={styles.infoButton} style={styles.infoButton}
> >
<FeatherIcon <FeatherIcon
+171
View File
@@ -0,0 +1,171 @@
const TemperatureSchema = {
name: 'Temperature',
properties: {
value: 'double',
exclude: 'bool',
time: {
type: 'string',
optional: true
},
note: {
type: 'string',
optional: true
}
}
}
const BleedingSchema = {
name: 'Bleeding',
properties: {
value: 'int',
exclude: 'bool'
}
}
const MucusSchema = {
name: 'Mucus',
properties: {
feeling: { type: 'int', optional: true },
texture: { type: 'int', optional: true },
value: { type: 'int', optional: true },
exclude: 'bool'
}
}
const CervixSchema = {
name: 'Cervix',
properties: {
opening: { type: 'int', optional: true },
firmness: { type: 'int', optional: true },
position: {type: 'int', optional: true },
exclude: 'bool'
}
}
const NoteSchema = {
name: 'Note',
properties: {
value: 'string'
}
}
const DesireSchema = {
name: 'Desire',
properties: {
value: 'int'
}
}
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 },
diaphragm: { type: 'bool', optional: true },
none: { type: 'bool', optional: true },
other: { type: 'bool', optional: true },
note: { type: 'string', optional: true }
}
}
const PainSchema = {
name: 'Pain',
properties: {
cramps: { type: 'bool', optional: true },
ovulationPain: { type: 'bool', optional: true },
headache: { type: 'bool', optional: true },
backache: { type: 'bool', optional: true },
nausea: { type: 'bool', optional: true },
tenderBreasts: { type: 'bool', optional: true },
migraine: { type: 'bool', optional: true },
other: { type: 'bool', optional: true },
note: { type: 'string', optional: true }
}
}
const MoodSchema = {
name: 'Mood',
properties: {
happy: { type: 'bool', optional: true },
sad: { type: 'bool', optional: true },
stressed: { type: 'bool', optional: true },
balanced: { type: 'bool', optional: true },
fine: { type: 'bool', optional: true },
anxious: { type: 'bool', optional: true },
energetic: { type: 'bool', optional: true },
fatigue: { type: 'bool', optional: true },
angry: { type: 'bool', optional: true },
other: { type: 'bool', optional: true },
note: { type: 'string', optional: true }
}
}
const CycleDaySchema = {
name: 'CycleDay',
primaryKey: 'date',
properties: {
date: 'string',
temperature: {
type: 'Temperature',
optional: true
},
isCycleStart: 'bool',
bleeding: {
type: 'Bleeding',
optional: true
},
mucus: {
type: 'Mucus',
optional: true
},
cervix: {
type: 'Cervix',
optional: true
},
note: {
type: 'Note',
optional: true
},
desire: {
type: 'Desire',
optional: true
},
sex: {
type: 'Sex',
optional: true
},
pain: {
type: 'Pain',
optional: true
},
mood: {
type: 'Mood',
optional: true
}
}
}
export default {
schema: [
CycleDaySchema,
TemperatureSchema,
BleedingSchema,
MucusSchema,
CervixSchema,
NoteSchema,
DesireSchema,
SexSchema,
PainSchema,
MoodSchema
],
schemaVersion: 4,
migration: (oldRealm) => {
if (oldRealm.schemaVersion >= 4) return
}
}
+2 -1
View File
@@ -2,5 +2,6 @@ import schema0 from './0.js'
import schema1 from './1.js' import schema1 from './1.js'
import schema2 from './2.js' import schema2 from './2.js'
import schema3 from './3.js' import schema3 from './3.js'
import schema4 from './4.js'
export default [schema0, schema1, schema2, schema3] export default [schema0, schema1, schema2, schema3, schema4]
+9 -9
View File
@@ -32,15 +32,15 @@ export const headerTitles = {
Password: settingsTitles.password, Password: settingsTitles.password,
About: settingsTitles.about, About: settingsTitles.about,
License: settingsTitles.license, License: settingsTitles.license,
BleedingEditView: 'Bleeding', bleeding: 'Bleeding',
TemperatureEditView: 'Temperature', temperature: 'Temperature',
MucusEditView: 'Mucus', mucus: 'Mucus',
CervixEditView: 'Cervix', cervix: 'Cervix',
NoteEditView: 'Note', note: 'Note',
DesireEditView: 'Desire', desire: 'Desire',
SexEditView: 'Sex', sex: 'Sex',
PainEditView: 'Pain', pain: 'Pain',
MoodEditView: 'Mood', mood: 'Mood',
InfoSymptom: 'Info' InfoSymptom: 'Info'
} }
+3
View File
@@ -1,4 +1,7 @@
export default function (feeling, texture) { export default function (feeling, texture) {
if (typeof feeling != 'number' || typeof texture != 'number') return null
const feelingMapping = { const feelingMapping = {
0: 0, 0: 0,
1: 1, 1: 1,
+13 -1
View File
@@ -108,7 +108,15 @@ function formatCycleForSympto(cycle) {
if (day[symptomName] && day[symptomName].exclude) { if (day[symptomName] && day[symptomName].exclude) {
delete day[symptomName] delete day[symptomName]
} }
}); })
// remove days with incomplete cervix values
if (hasIncompleteCervixValue(day)) {
delete day.cervix
}
// remove days with incomplete mucus value (because nfp-mucus returns null when that's the case)
if (day.mucus && day.mucus.value === null) {
delete day.mucus
}
// change format // change format
['bleeding', 'temperature', 'mucus'].forEach(symptomName => { ['bleeding', 'temperature', 'mucus'].forEach(symptomName => {
if (day[symptomName]) day[symptomName] = day[symptomName].value if (day[symptomName]) day[symptomName] = day[symptomName].value
@@ -119,4 +127,8 @@ function formatCycleForSympto(cycle) {
// we get earliest last, but sympto wants earliest first // we get earliest last, but sympto wants earliest first
formatted.reverse() formatted.reverse()
return formatted return formatted
}
function hasIncompleteCervixValue(day) {
return day.cervix && (typeof day.cervix.opening != 'number' || typeof day.cervix.firmness != 'number')
} }
+14 -33
View File
@@ -3229,8 +3229,7 @@
}, },
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@@ -3248,13 +3247,11 @@
}, },
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -3267,18 +3264,15 @@
}, },
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@@ -3381,8 +3375,7 @@
}, },
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@@ -3392,7 +3385,6 @@
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@@ -3405,20 +3397,17 @@
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
}, },
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.3.5", "version": "2.3.5",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@@ -3435,7 +3424,6 @@
"mkdirp": { "mkdirp": {
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@@ -3508,8 +3496,7 @@
}, },
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@@ -3519,7 +3506,6 @@
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@@ -3595,8 +3581,7 @@
}, },
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@@ -3626,7 +3611,6 @@
"string-width": { "string-width": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@@ -3644,7 +3628,6 @@
"strip-ansi": { "strip-ansi": {
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@@ -3683,13 +3666,11 @@
}, },
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.3", "version": "3.0.3",
"bundled": true, "bundled": true
"optional": true
} }
} }
}, },
@@ -8097,9 +8078,9 @@
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
}, },
"sympto": { "sympto": {
"version": "1.0.1", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/sympto/-/sympto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/sympto/-/sympto-1.0.4.tgz",
"integrity": "sha512-wLDpugvScuXhSBhgJHZTGU9gTd5uDnuZDJuNz7aUSj1N28VOe2RhKwMF4RLwjy3s6i+BG1Lfa3uckNPCFPkUvA==", "integrity": "sha512-FSdSwPeE3BKvnJlPkHzVusGMTz4r6dW2eEEJbgPrgdkmPWmAFWTD7Hf7OhqQSbTLjiZY7jBeWDWuizb4UZMk1g==",
"requires": { "requires": {
"js-joda": "^1.9.2" "js-joda": "^1.9.2"
} }
+1 -1
View File
@@ -46,7 +46,7 @@
"react-native-share": "^1.1.3", "react-native-share": "^1.1.3",
"react-native-vector-icons": "^5.0.0", "react-native-vector-icons": "^5.0.0",
"realm": "^2.22.0", "realm": "^2.22.0",
"sympto": "^1.0.0" "sympto": "^1.0.4"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.2.2", "@babel/core": "^7.2.2",
+5
View File
@@ -7,6 +7,11 @@ chai.use(dirtyChai)
import getSensiplanMucus from '../lib/nfp-mucus' import getSensiplanMucus from '../lib/nfp-mucus'
describe('getSensiplanMucus', () => { describe('getSensiplanMucus', () => {
it('returns null if there is no value for feeling or texture', () => {
expect(getSensiplanMucus()).to.be.null()
expect(getSensiplanMucus(undefined, 3)).to.be.null()
expect(getSensiplanMucus(2, undefined)).to.be.null()
})
describe('results in t for:', () => { describe('results in t for:', () => {
it('dry feeling and no texture', function () { it('dry feeling and no texture', function () {