diff --git a/components/app.js b/components/app.js
index 37ba5c5..505f7bb 100644
--- a/components/app.js
+++ b/components/app.js
@@ -1,5 +1,5 @@
import React, { Component } from 'react'
-import { View, BackHandler } from 'react-native'
+import { BackHandler, StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
@@ -10,7 +10,7 @@ import { getNavigation, navigate, goBack } from '../slices/navigation'
import Header from './header'
import Menu from './menu'
import { viewsList } from './views'
-import { isSymptomView, isSettingsView } from './pages'
+import { isSettingsView } from './pages'
import { headerTitles } from '../i18n/en/labels'
import setupNotifications from '../lib/notifications'
@@ -64,9 +64,7 @@ class App extends Component {
const Page = viewsList[currentPage]
const title = headerTitles[currentPage]
- const isSymptomEditView = isSymptomView(currentPage)
const isSettingsSubView = isSettingsView(currentPage)
- const isCycleDayView = currentPage === 'CycleDay'
const headerProps = {
title,
@@ -79,21 +77,21 @@ class App extends Component {
}
return (
-
- {
- !isSymptomEditView &&
- !isCycleDayView &&
-
- }
-
+
+
-
- { !isSymptomEditView && }
+
)
}
}
+const styles = StyleSheet.create({
+ container: {
+ flex: 1
+ }
+})
+
const mapStateToProps = (state) => {
return({
date: getDate(state),
diff --git a/components/chart/chart.js b/components/chart/chart.js
index 5f6039b..525682f 100644
--- a/components/chart/chart.js
+++ b/components/chart/chart.js
@@ -73,7 +73,7 @@ class CycleChart extends Component {
prepareSymptomData = () => {
this.symptomRowSymptoms = SYMPTOMS.filter((symptomName) => {
return this.cycleDaysSortedByDate.some(cycleDay => {
- return cycleDay[symptomName]
+ return (symptomName !== 'temperature') && cycleDay[symptomName]
})
})
this.chartSymptoms = [...this.symptomRowSymptoms]
diff --git a/components/chart/tutorial.js b/components/chart/tutorial.js
index 49dec5c..4a0de53 100644
--- a/components/chart/tutorial.js
+++ b/components/chart/tutorial.js
@@ -1,11 +1,11 @@
import React from 'react'
import PropTypes from 'prop-types'
-import { Image, StyleSheet, TouchableOpacity, View } from 'react-native'
+import { Image, StyleSheet, View } from 'react-native'
-import AppIcon from '../common/app-icon'
import AppText from '../common/app-text'
+import CloseIcon from '../common/close-icon'
-import { Colors, Containers, Sizes, Spacing } from '../../styles/redesign'
+import { Containers, Spacing } from '../../styles/redesign'
import { chart } from '../../i18n/en/labels'
const image = require('../../assets/swipe.png')
@@ -17,9 +17,7 @@ const Tutorial = ({ onClose }) => {
{chart.tutorial}
-
-
-
+
)
}
@@ -33,10 +31,6 @@ const styles = StyleSheet.create({
...Containers.rowContainer,
padding: Spacing.large
},
- iconContainer: {
- alignSelf: 'flex-start',
- marginBottom: Sizes.base
- },
image: {
height: 40
},
diff --git a/components/common/app-modal.js b/components/common/app-modal.js
new file mode 100644
index 0000000..0f28882
--- /dev/null
+++ b/components/common/app-modal.js
@@ -0,0 +1,32 @@
+import React from 'react'
+import { Modal, StyleSheet, TouchableOpacity } from 'react-native'
+import PropTypes from 'prop-types'
+
+const AppModal = ({ children, onClose }) => {
+ return(
+
+
+ {children}
+
+ )
+}
+
+AppModal.propTypes = {
+ children: PropTypes.node,
+ onClose: PropTypes.func
+}
+
+const styles = StyleSheet.create({
+ blackBackground: {
+ backgroundColor: 'black',
+ flex: 1,
+ opacity: 0.5,
+ }
+})
+
+export default AppModal
diff --git a/components/common/app-switch.js b/components/common/app-switch.js
index 3cb30e6..2717535 100644
--- a/components/common/app-switch.js
+++ b/components/common/app-switch.js
@@ -20,7 +20,7 @@ const AppSwitch = ({ onToggle, text, value }) => {
AppSwitch.propTypes = {
onToggle: PropTypes.func.isRequired,
text: PropTypes.string,
- value: PropTypes.bool.isRequired
+ value: PropTypes.bool
}
const styles = StyleSheet.create({
diff --git a/components/common/app-text-input.js b/components/common/app-text-input.js
index a1c8f99..3b55071 100644
--- a/components/common/app-text-input.js
+++ b/components/common/app-text-input.js
@@ -1,10 +1,15 @@
import React from 'react'
import { StyleSheet, TextInput } from 'react-native'
+import PropTypes from 'prop-types'
import { Colors, Spacing, Typography } from '../../styles/redesign'
-const AppTextInput = ({ ...props }) => {
- return
+const AppTextInput = ({ style, ...props }) => {
+ return
+}
+
+AppTextInput.propTypes = {
+ style: PropTypes.object
}
const styles = StyleSheet.create({
@@ -16,6 +21,7 @@ const styles = StyleSheet.create({
borderWidth: 1,
color: Colors.greyDark,
marginTop: Spacing.base,
+ minWidth: '80%',
paddingHorizontal: Spacing.base,
...Typography.mainText
}
diff --git a/components/common/button.js b/components/common/button.js
index 2e98588..28d0036 100644
--- a/components/common/button.js
+++ b/components/common/button.js
@@ -2,14 +2,23 @@ import React from 'react'
import PropTypes from 'prop-types'
import { StyleSheet, TouchableOpacity } from 'react-native'
+import AppIcon from './app-icon'
import AppText from './app-text'
import { Colors, Fonts, Spacing } from '../../styles/redesign'
-const Button = ({ children, isCTA, isSmall, onPress, testID, ...props }) => {
+const Button = ({
+ children,
+ iconName,
+ isCTA,
+ isSmall,
+ onPress,
+ testID,
+ ...props
+}) => {
const buttonStyle = isCTA ? styles.cta : styles.regular
const textCTA = isCTA ? styles.buttonTextBold : styles.buttonTextRegular
- const textStyle = [ textCTA, isSmall ? textSmall : text]
+ const textStyle = [textCTA, isSmall ? textSmall : text]
return (
{
{...props}
>
{children}
+ {iconName && }
)
}
Button.propTypes = {
children: PropTypes.node,
+ iconName: PropTypes.string,
isCTA: PropTypes.bool,
isSmall: PropTypes.bool,
onPress: PropTypes.func,
@@ -48,8 +59,10 @@ const textSmall = {
const button = {
alignItems: 'center',
+ flexDirection: 'row',
justifyContent: 'center',
- margin: Spacing.base
+ margin: Spacing.base,
+ minWidth: '15%'
}
const styles = StyleSheet.create({
diff --git a/components/common/close-icon.js b/components/common/close-icon.js
new file mode 100644
index 0000000..1930fb3
--- /dev/null
+++ b/components/common/close-icon.js
@@ -0,0 +1,32 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { StyleSheet, TouchableOpacity } from 'react-native'
+
+import AppIcon from './app-icon'
+
+import { Colors, Sizes } from '../../styles/redesign'
+
+const CloseIcon = ({ onClose, ...props }) => {
+ return (
+
+
+
+ )
+}
+
+CloseIcon.propTypes = {
+ onClose: PropTypes.func.isRequired
+}
+
+const styles = StyleSheet.create({
+ container: {
+ alignSelf: 'flex-start',
+ marginBottom: Sizes.base
+ }
+})
+
+export default CloseIcon
\ No newline at end of file
diff --git a/components/common/segment.js b/components/common/segment.js
index 97cea4f..004562a 100644
--- a/components/common/segment.js
+++ b/components/common/segment.js
@@ -33,7 +33,7 @@ const styles = StyleSheet.create({
container: {
borderStyle: 'solid',
borderBottomWidth: 2,
- borderBottomColor: Colors.grey,
+ borderBottomColor: Colors.greyLight,
paddingBottom: Spacing.base,
...segmentContainer
},
diff --git a/components/cycle-day/FillerBoxes.js b/components/cycle-day/FillerBoxes.js
deleted file mode 100644
index f0e0c02..0000000
--- a/components/cycle-day/FillerBoxes.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import React, { Component } from 'react'
-import { View, Dimensions } from 'react-native'
-import styles from '../../styles'
-
-export default class FillerBoxes extends Component {
- render() {
- const n = Dimensions.get('window').width / styles.symptomBox.width
- const fillerBoxes = []
- for (let i = 0; i < Math.ceil(n); i++) {
- fillerBoxes.push(
-
- )
- }
- return fillerBoxes
- }
-}
\ No newline at end of file
diff --git a/components/cycle-day/SymptomBox.js b/components/cycle-day/SymptomBox.js
deleted file mode 100644
index 71bc042..0000000
--- a/components/cycle-day/SymptomBox.js
+++ /dev/null
@@ -1,174 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { View, TouchableOpacity } from 'react-native'
-
-import AppText from '../common/app-text'
-import DripIcon from '../../assets/drip-icons'
-
-import styles from '../../styles'
-
-import { headerTitles as symptomTitles } from '../../i18n/en/labels'
-import * as labels from '../../i18n/en/cycle-day'
-const bleedingLabels = labels.bleeding.labels
-const intensityLabels = labels.intensity
-const sexLabels = labels.sex.categories
-const contraceptiveLabels = labels.contraceptives.categories
-const painLabels = labels.pain.categories
-const moodLabels = labels.mood.categories
-
-function isNumber(val) {
- return typeof val === 'number'
-}
-
-const l = {
- bleeding: ({ value, exclude }) => {
- if (isNumber(value)) {
- const bleedingLabel = bleedingLabels[value]
- return exclude ? `(${bleedingLabel})` : bleedingLabel
- }
- },
- temperature: ({ value, time, exclude }) => {
- if (isNumber(value)) {
- let temperatureLabel = `${value} °C`
- if (time) {
- temperatureLabel += ` - ${time}`
- }
- if (exclude) {
- temperatureLabel = `(${temperatureLabel})`
- }
- return temperatureLabel
- }
- },
- mucus: mucus => {
- const filledCategories = ['feeling', 'texture'].filter(c => isNumber(mucus[c]))
- let label = filledCategories.map(category => {
- return labels.mucus.subcategories[category] + ': ' + labels.mucus[category].categories[mucus[category]]
- }).join(', ')
-
- if (isNumber(mucus.value)) label += `\n => ${labels.mucusNFP[mucus.value]}`
- if (mucus.exclude) label = `(${label})`
-
- return label
- },
- cervix: cervix => {
- const filledCategories = ['opening', 'firmness', 'position'].filter(c => isNumber(cervix[c]))
- let label = filledCategories.map(category => {
- return labels.cervix.subcategories[category] + ': ' + labels.cervix[category].categories[cervix[category]]
- }).join(', ')
-
- if (cervix.exclude) label = `(${label})`
-
- return label
- },
- note: note => note.value,
- desire: ({ value }) => {
- if (isNumber(value)) {
- return intensityLabels[value]
- }
- },
- sex: sex => {
- const sexLabel = []
- if (sex && Object.values({...sex}).some(val => val)){
- Object.keys(sex).forEach(key => {
- if(sex[key] && key !== 'other' && key !== 'note') {
- sexLabel.push(
- sexLabels[key] ||
- contraceptiveLabels[key]
- )
- }
- if(key === 'other' && sex.other) {
- let label = contraceptiveLabels[key]
- if(sex.note) {
- label = `${label} (${sex.note})`
- }
- sexLabel.push(label)
- }
- })
- return sexLabel.join(', ')
- }
- },
- pain: pain => {
- const painLabel = []
- if (pain && Object.values({...pain}).some(val => val)){
- Object.keys(pain).forEach(key => {
- if(pain[key] && key !== 'other' && key !== 'note') {
- painLabel.push(painLabels[key])
- }
- if(key === 'other' && pain.other) {
- let label = painLabels[key]
- if(pain.note) {
- label = `${label} (${pain.note})`
- }
- painLabel.push(label)
- }
- })
- return painLabel.join(', ')
- }
- },
- mood: mood => {
- const moodLabel = []
- if (mood && Object.values({...mood}).some(val => val)){
- Object.keys(mood).forEach(key => {
- if(mood[key] && key !== 'other' && key !== 'note') {
- moodLabel.push(moodLabels[key])
- }
- if(key === 'other' && mood.other) {
- let label = moodLabels[key]
- if(mood.note) {
- label = `${label} (${mood.note})`
- }
- moodLabel.push(label)
- }
- })
- return moodLabel.join(', ')
- }
- }
-}
-
-const getLabel = (symptom, symptomData) => {
- return symptomData && l[symptom](symptomData)
-}
-
-export default function SymptomBox(
- { disabled, onPress, symptom, symptomData }) {
-
- const data = getLabel(symptom, symptomData)
- const iconName = `drip-icon-${symptom}`
-
- const disabledStyle = disabled ? styles.symptomInFuture : null
- const containerStyle = [
- styles.symptomBox,
- data && styles.symptomBoxActive,
- disabledStyle
- ]
- const titleStyle = [
- data && styles.symptomTextActive,
- disabledStyle,
- {fontSize: 15}
- ]
- const dataBoxStyle = [styles.symptomDataBox, disabledStyle]
- const iconColor = data ? 'white' : 'black'
-
- return (
-
-
-
-
- {symptomTitles[symptom].toLowerCase()}
-
-
-
-
- {data}
-
-
-
- )
-}
-
-SymptomBox.propTypes = {
- disabled: PropTypes.bool.isRequired,
- onPress: PropTypes.func.isRequired,
- symptom: PropTypes.string.isRequired,
- symptomData: PropTypes.object
-}
\ No newline at end of file
diff --git a/components/cycle-day/cycle-day-overview.js b/components/cycle-day/cycle-day-overview.js
index ceab0f5..427b63a 100644
--- a/components/cycle-day/cycle-day-overview.js
+++ b/components/cycle-day/cycle-day-overview.js
@@ -1,118 +1,89 @@
import React, { Component } from 'react'
-import { ScrollView, View } from 'react-native'
+import { StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
+import AppPage from '../common/app-page'
+import SymptomBox from './symptom-box'
+import SymptomPageTitle from './symptom-page-title'
+
import { connect } from 'react-redux'
import { getDate, setDate } from '../../slices/date'
import { navigate } from '../../slices/navigation'
-import { LocalDate } from 'js-joda'
-import Header from '../header'
-import FillerBoxes from './FillerBoxes'
-import SymptomBox from './SymptomBox'
-
import cycleModule from '../../lib/cycle'
-import formatDate from '../helpers/format-date'
+import { dateToTitle } from '../helpers/format-date'
import { getCycleDay } from '../../db'
-import styles from '../../styles'
+import { getData } from '../helpers/cycle-day'
+
+import { general as labels} from '../../i18n/en/cycle-day'
+import { Spacing } from '../../styles/redesign'
+import { SYMPTOMS } from '../../config'
class CycleDayOverView extends Component {
static propTypes = {
navigate: PropTypes.func,
setDate: PropTypes.func,
- // The following are not being used,
- // we could see if it's possible to not pass them from the
cycleDay: PropTypes.object,
date: PropTypes.string,
}
constructor(props) {
super(props)
- this.state = {
- cycleDay: getCycleDay(props.date)
- }
+
+ this.state = { cycleDay: getCycleDay(props.date), data: null }
}
updateCycleDay = (date) => {
this.props.setDate(date)
- this.setState({
- cycleDay: getCycleDay(date)
- })
- }
-
- goToPrevDay = () => {
- const { date } = this.props
- const prevDate = LocalDate.parse(date).minusDays(1).toString()
- this.updateCycleDay(prevDate)
- }
-
- goToNextDay = () => {
- const { date } = this.props
- const nextDate = LocalDate.parse(date).plusDays(1).toString()
- this.updateCycleDay(nextDate)
+ this.setState({ cycleDay: getCycleDay(date) })
}
render() {
const { cycleDay } = this.state
const { date } = this.props
- const dateInFuture = LocalDate.now().isBefore(LocalDate.parse(date))
-
- const symptomBoxesList = [
- 'bleeding',
- 'temperature',
- 'mucus',
- 'cervix',
- 'desire',
- 'sex',
- 'pain',
- 'mood',
- 'note',
- ]
-
const { getCycleDayNumber } = cycleModule()
const cycleDayNumber = getCycleDayNumber(date)
- const headerSubtitle = cycleDayNumber && `Cycle day ${cycleDayNumber}`
+ const subtitle = cycleDayNumber && `${labels.cycleDayNumber}${cycleDayNumber}`
return (
-
-
+
-
-
- {
- symptomBoxesList.map(symptom => {
- const symptomEditView =
- `${symptom[0].toUpperCase() + symptom.substring(1)}EditView`
- const symptomData =
- cycleDay && cycleDay[symptom] ? cycleDay[symptom] : null
- return(
- this.props.navigate(symptomEditView)}
- disabled={dateInFuture && symptom !== 'note'}
- />)
- })
- }
- {
- // this is just to make the last row adhere to the grid
- // (and) because there are no pseudo properties in RN
- }
-
-
-
-
+
+ {SYMPTOMS.map(symptom => {
+ const symptomData = cycleDay && cycleDay[symptom]
+ ? cycleDay[symptom] : null
+
+ return(
+
+ )
+ })}
+
+
)
}
}
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ justifyContent: 'space-between',
+ padding: Spacing.base
+ }
+})
+
const mapStateToProps = (state) => {
return({
date: getDate(state),
diff --git a/components/cycle-day/select-box-group.js b/components/cycle-day/select-box-group.js
index 94a2607..d8c5808 100644
--- a/components/cycle-day/select-box-group.js
+++ b/components/cycle-day/select-box-group.js
@@ -1,29 +1,26 @@
import React from 'react'
import PropTypes from 'prop-types'
-import { View, TouchableOpacity } from 'react-native'
+import { StyleSheet, TouchableOpacity, View } from 'react-native'
import AppText from '../common/app-text'
-import styles from '../../styles'
+import { Colors, Containers } from '../../styles/redesign'
-export default function SelectBoxGroup({ labels, onSelect, optionsState }) {
+const SelectBoxGroup = ({ labels, optionsState, onSelect }) => {
return (
-
+
{Object.keys(labels).map(key => {
- const style = [styles.selectBox]
- const textStyle = []
- if (optionsState[key]) {
- style.push(styles.selectBoxActive)
- textStyle.push(styles.selectBoxTextActive)
- }
+ const isActive = optionsState[key]
+ const boxStyle = [styles.box, isActive && styles.boxActive]
+ const textStyle = [styles.text, isActive && styles.textActive]
+
return (
onSelect(key)}
key={key}
+ onPress={() => onSelect(key)}
+ style={boxStyle}
>
-
- {labels[key]}
-
+ {labels[key]}
)
})}
@@ -36,3 +33,23 @@ SelectBoxGroup.propTypes = {
onSelect: PropTypes.func.isRequired,
optionsState: PropTypes.object.isRequired
}
+
+const styles = StyleSheet.create({
+ box: {
+ ...Containers.box
+ },
+ boxActive: {
+ ...Containers.boxActive
+ },
+ container: {
+ ...Containers.selectGroupContainer
+ },
+ text: {
+ color: Colors.orange
+ },
+ textActive: {
+ color: 'white'
+ }
+})
+
+export default SelectBoxGroup
diff --git a/components/cycle-day/select-tab-group.js b/components/cycle-day/select-tab-group.js
index 77dcc25..8caea5a 100644
--- a/components/cycle-day/select-tab-group.js
+++ b/components/cycle-day/select-tab-group.js
@@ -1,40 +1,27 @@
import React from 'react'
import PropTypes from 'prop-types'
-import { View, TouchableOpacity } from 'react-native'
+import { StyleSheet, TouchableOpacity, View } from 'react-native'
import AppText from '../common/app-text'
-import styles from '../../styles'
+import { Colors, Containers } from '../../styles/redesign'
-export default function SelectTabGroup({ active, buttons, onSelect }) {
+export default function SelectTabGroup({ activeButton, buttons, onSelect }) {
return (
-
+
{
buttons.map(({ label, value }, i) => {
- let firstOrLastStyle
- if (i === buttons.length - 1) {
- firstOrLastStyle = styles.selectTabLast
- } else if (i === 0) {
- firstOrLastStyle = styles.selectTabFirst
- }
- let activeStyle
- const isActive = value === active
- if (isActive) activeStyle = styles.selectTabActive
+ const isActive = value === activeButton
+ const boxStyle = [styles.box, isActive && styles.boxActive]
+ const textStyle = [styles.text, isActive && styles.textActive]
+
return (
onSelect(isActive ? null : value)}
+ onPress={() => onSelect(value)}
key={i}
- activeOpacity={1}
+ style={boxStyle}
>
-
-
- {label}
-
-
+ {label}
)
})
@@ -44,7 +31,25 @@ export default function SelectTabGroup({ active, buttons, onSelect }) {
}
SelectTabGroup.propTypes = {
- active: PropTypes.number,
+ activeButton: PropTypes.number,
buttons: PropTypes.array.isRequired,
onSelect: PropTypes.func.isRequired
-}
\ No newline at end of file
+}
+
+const styles = StyleSheet.create({
+ box: {
+ ...Containers.box
+ },
+ boxActive: {
+ ...Containers.boxActive
+ },
+ container: {
+ ...Containers.selectGroupContainer
+ },
+ text: {
+ color: Colors.orange
+ },
+ textActive: {
+ color: 'white'
+ }
+})
\ No newline at end of file
diff --git a/components/cycle-day/symptom-box.js b/components/cycle-day/symptom-box.js
new file mode 100644
index 0000000..82f9fe7
--- /dev/null
+++ b/components/cycle-day/symptom-box.js
@@ -0,0 +1,167 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import { StyleSheet, View, TouchableOpacity } from 'react-native'
+
+import AppText from '../common/app-text'
+import DripIcon from '../../assets/drip-icons'
+import SymptomEditView from './symptom-edit-view'
+
+import { connect } from 'react-redux'
+import { getDate } from '../../slices/date'
+import { isDateInFuture } from '../helpers/cycle-day'
+
+import { Colors, Sizes, Spacing } from '../../styles/redesign'
+import { headerTitles as symptomTitles } from '../../i18n/en/labels'
+
+class SymptomBox extends Component {
+
+ static propTypes = {
+ date: PropTypes.string.isRequired,
+ symptom: PropTypes.string.isRequired,
+ symptomData: PropTypes.object,
+ symptomDataToDisplay: PropTypes.string,
+ updateCycleDayData: PropTypes.func.isRequired
+ }
+
+ constructor(props) {
+ super(props)
+
+ this.state = { isSymptomEdited: false }
+ }
+
+ onFinishEditing = () => {
+ const { date, updateCycleDayData } = this.props
+
+ updateCycleDayData(date)
+ this.setState({ isSymptomEdited: false })
+ }
+
+ onEditSymptom = () => {
+ this.setState({ isSymptomEdited: true })
+ }
+
+ render() {
+ const { date, symptom, symptomData, symptomDataToDisplay } = this.props
+ const { isSymptomEdited } = this.state
+ const isSymptomDisabled = isDateInFuture(date) && symptom !== 'note'
+ const isExcluded = symptomData !== null ? symptomData.exclude : false
+
+ const iconColor = isSymptomDisabled ? Colors.greyLight : Colors.grey
+ const iconName = `drip-icon-${symptom}`
+ const symptomNameStyle = [
+ styles.symptomName,
+ (isSymptomDisabled && styles.symptomNameDisabled),
+ (isExcluded && styles.symptomNameExcluded)
+ ]
+ const textStyle = [
+ styles.text,
+ (isSymptomDisabled && styles.textDisabled),
+ (isExcluded && styles.textExcluded)
+ ]
+
+ return (
+
+ {isSymptomEdited &&
+
+ }
+
+
+
+
+
+ {symptomTitles[symptom].toLowerCase()}
+
+ {symptomDataToDisplay &&
+
+ {symptomDataToDisplay}
+
+ }
+
+
+
+ )
+ }
+}
+
+const excluded = {
+ textDecorationLine: 'line-through'
+}
+
+const hint = {
+ fontSize: Sizes.small,
+ fontStyle: 'italic'
+}
+
+const main = {
+ fontSize: Sizes.base,
+ height: Sizes.base * 2,
+ lineHeight: Sizes.base,
+ marginBottom: (-1) * Sizes.tiny,
+ textAlignVertical: 'center'
+}
+
+const styles = StyleSheet.create({
+ container: {
+ alignItems: 'center',
+ backgroundColor: 'white',
+ borderRadius: 10,
+ elevation: 4,
+ flexDirection: 'row',
+ height: 110,
+ marginBottom: Spacing.base,
+ paddingHorizontal: Spacing.small,
+ paddingVertical: Spacing.base,
+ width: Spacing.symptomTileWidth
+ },
+ symptomName: {
+ color: Colors.purple,
+ ...main
+ },
+ symptomNameDisabled: {
+ color: Colors.grey
+ },
+ symptomNameExcluded: {
+ color: Colors.greyDark,
+ ...excluded
+ },
+ textContainer: {
+ flexDirection: 'column',
+ marginLeft: Spacing.small,
+ maxWidth: Spacing.textWidth
+ },
+ text: {
+ ...hint
+ },
+ textDisabled: {
+ color: Colors.greyLight
+ },
+ textExcluded: {
+ color: Colors.grey,
+ ...excluded
+ }
+})
+
+const mapStateToProps = (state) => {
+ return({
+ date: getDate(state),
+ })
+}
+
+export default connect(
+ mapStateToProps,
+ null,
+)(SymptomBox)
\ No newline at end of file
diff --git a/components/cycle-day/symptom-edit-view.js b/components/cycle-day/symptom-edit-view.js
new file mode 100644
index 0000000..eb50e29
--- /dev/null
+++ b/components/cycle-day/symptom-edit-view.js
@@ -0,0 +1,281 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import { ScrollView, StyleSheet, View } from 'react-native'
+
+import AppModal from '../common/app-modal'
+import AppSwitch from '../common/app-switch'
+import AppText from '../common/app-text'
+import AppTextInput from '../common/app-text-input'
+import Button from '../common/button'
+import CloseIcon from '../common/close-icon'
+import Segment from '../common/segment'
+import SelectBoxGroup from './select-box-group'
+import SelectTabGroup from './select-tab-group'
+import Temperature from './temperature'
+
+import { connect } from 'react-redux'
+import { getDate } from '../../slices/date'
+import { blank, save, shouldShow, symtomPage } from '../helpers/cycle-day'
+
+import { shared as sharedLabels } from '../../i18n/en/labels'
+import info from '../../i18n/en/symptom-info'
+import { Containers, Sizes } from '../../styles/redesign'
+
+class SymptomEditView extends Component {
+
+ static propTypes = {
+ date: PropTypes.string.isRequired,
+ onClose: PropTypes.func.isRequired,
+ symptom: PropTypes.string.isRequired,
+ symptomData: PropTypes.object
+ }
+
+ constructor(props) {
+ super(props)
+
+ const { symptomData, symptom } = this.props
+ const data = symptomData ? symptomData : blank[symptom]
+
+ const symptomConfig = symtomPage[symptom]
+ const shouldShowExclude = shouldShow(symptomConfig.excludeText)
+ const shouldShowNote = shouldShow(symptomConfig.note)
+ const shouldBoxGroup = shouldShow(symptomConfig.selectBoxGroups)
+ const shouldTabGroup = shouldShow(symptomConfig.selectTabGroups)
+
+ this.state = {
+ data,
+ shouldShowExclude,
+ shouldShowInfo: false,
+ shouldShowNote,
+ shouldBoxGroup,
+ shouldTabGroup
+ }
+ }
+
+ componentDidUpdate() {
+ this.saveData()
+ }
+
+ getParsedData = () => JSON.parse(JSON.stringify(this.state.data))
+
+ onEditNote = (note) => {
+ const data = this.getParsedData()
+ const { symptom } = this.props
+
+ if (symptom === 'note') {
+ Object.assign(data, { value: note })
+ } else {
+ data['note'] = note
+ }
+
+ this.setState({ data })
+ }
+
+ onExcludeToggle = () => {
+ const data = this.getParsedData()
+ Object.assign(data, { exclude: !data.exclude })
+
+ this.setState({ data })
+ }
+
+ onPressLearnMore = () => {
+ this.setState({ shouldShowInfo: !this.state.shouldShowInfo })
+ }
+
+ onRemove = () => {
+ this.saveData(true)
+ this.props.onClose()
+ }
+
+ onSave = () => {
+ this.saveData()
+ this.props.onClose()
+ }
+
+ onSaveTemperature = (value, field) => {
+ const data = this.getParsedData()
+ const dataToSave = field === 'value'
+ ? { [field]: Number(value) } : { [field]: value }
+ Object.assign(data, { ...dataToSave })
+
+ this.setState({ data })
+ }
+
+ onSelectBox = (key) => {
+ const data = this.getParsedData()
+ if (key === "other") {
+ Object.assign(data, {
+ note: null,
+ [key]: !this.state.data[key]
+ })
+ } else {
+ Object.assign(data, { [key]: !this.state.data[key] })
+ }
+
+ this.setState({ data })
+ }
+
+ onSelectBoxNote= (value) => {
+ const data = this.getParsedData()
+ Object.assign(data, { note: value !== '' ? value : null })
+
+ this.setState({ data })
+ }
+
+ onSelectTab = (group, value) => {
+ const data = this.getParsedData()
+ Object.assign(data, { [group.key]: value })
+
+ this.setState({ data })
+ }
+
+ saveData = (shouldDeleteData) => {
+ const { date, symptom } = this.props
+ const { data } = this.state
+ save[symptom](data, date, shouldDeleteData)
+ }
+
+ render() {
+ const { onClose, symptom } = this.props
+ const { data,
+ shouldShowExclude,
+ shouldShowInfo,
+ shouldShowNote,
+ shouldBoxGroup,
+ shouldTabGroup
+ } = this.state
+ const iconName = shouldShowInfo ? "chevron-down" : "chevron-up"
+ const noteText = symptom === 'note' ? data.value : data.note
+
+ return (
+
+
+
+
+
+ {symptom === 'temperature' &&
+ this.onSaveTemperature(value, field)}
+ />
+ }
+ {shouldTabGroup && symtomPage[symptom].selectTabGroups.map(group => {
+ return (
+
+ {group.title}
+ this.onSelectTab(group, value)}
+ />
+
+ )
+ })
+ }
+ {shouldBoxGroup && symtomPage[symptom].selectBoxGroups.map(group => {
+ const isOtherSelected =
+ data['other'] !== null
+ && data['other'] !== false
+ && Object.keys(group.options).includes('other')
+
+ return (
+
+ {group.title}
+ this.onSelectBox(value)}
+ optionsState={data}
+ />
+ {isOtherSelected &&
+ this.onSelectBoxNote(value)}
+ />
+ }
+
+ )
+ })
+ }
+ {shouldShowExclude &&
+
+
+
+ }
+ {shouldShowNote &&
+
+ {symtomPage[symptom].note}
+
+
+ }
+
+
+
+
+
+ {shouldShowInfo &&
+
+ {info[symptom].text}
+
+ }
+
+
+ )
+ }
+}
+
+const styles = StyleSheet.create({
+ buttonsContainer: {
+ ...Containers.rowContainer
+ },
+ headerContainer: {
+ flexDirection: 'row',
+ justifyContent: 'flex-end',
+ paddingVertical: Sizes.tiny,
+ },
+ modalContainer: {
+ flexGrow: 1,
+ padding: Sizes.small
+ },
+ modalWindow: {
+ backgroundColor: 'white',
+ borderRadius: 10,
+ marginVertical: Sizes.huge * 2,
+ minHeight: '40%',
+ height: '70%',
+ position: 'absolute'
+ },
+ title: {
+ fontSize: Sizes.subtitle
+ }
+})
+
+const mapStateToProps = (state) => {
+ return({
+ date: getDate(state),
+ })
+}
+
+export default connect(
+ mapStateToProps,
+ null,
+)(SymptomEditView)
\ No newline at end of file
diff --git a/components/cycle-day/symptom-page-title.js b/components/cycle-day/symptom-page-title.js
new file mode 100644
index 0000000..3b84869
--- /dev/null
+++ b/components/cycle-day/symptom-page-title.js
@@ -0,0 +1,88 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { StyleSheet, TouchableOpacity, View } from 'react-native'
+
+import AppIcon from '../common/app-icon'
+import AppText from '../common/app-text'
+
+import { connect } from 'react-redux'
+import { getDate, setDate } from '../../slices/date'
+
+import {
+ nextDate,
+ prevDate,
+ isTomorrowInFuture,
+ isYesterdayInFuture
+} from '../helpers/cycle-day'
+import { Colors, Containers, Spacing, Typography } from '../../styles/redesign'
+
+const SymptomPageTitle = ({
+ date,
+ reloadSymptomData,
+ setDate,
+ subtitle,
+ title
+}) => {
+ const rightArrowColor = isTomorrowInFuture(date) ? Colors.grey : Colors.orange
+ const leftArrowColor = isYesterdayInFuture(date) ? Colors.grey : Colors.orange
+ const navigate = (isForward) => {
+ const nextDay = isForward ? nextDate(date) : prevDate(date)
+ reloadSymptomData(nextDay)
+ setDate(nextDay)
+ }
+
+ return (
+
+ navigate(false)}>
+
+
+
+ {title}
+ {subtitle && {subtitle}}
+
+ navigate(true)}>
+
+
+
+ )
+}
+
+SymptomPageTitle.propTypes = {
+ date: PropTypes.string.isRequired,
+ reloadSymptomData: PropTypes.func.isRequired,
+ setDate: PropTypes.func.isRequired,
+ subtitle: PropTypes.string,
+ title: PropTypes.string
+}
+
+const styles = StyleSheet.create({
+ container: {
+ height: (Spacing.base * 4),
+ marginHorizontal: Spacing.base,
+ marginTop: Spacing.base,
+ ...Containers.rowContainer
+ },
+ textContainer: {
+ alignItems: 'center'
+ },
+ title: {
+ ...Typography.titleWithoutMargin
+ }
+})
+
+const mapStateToProps = (state) => {
+ return({
+ date: getDate(state),
+ })
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return({
+ setDate: (date) => dispatch(setDate(date)),
+ })
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(SymptomPageTitle)
\ No newline at end of file
diff --git a/components/cycle-day/symptoms/bleeding.js b/components/cycle-day/symptoms/bleeding.js
deleted file mode 100644
index f0579c1..0000000
--- a/components/cycle-day/symptoms/bleeding.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import React, { Component } from 'react'
-import { Switch } from 'react-native'
-import PropTypes from 'prop-types'
-
-import { bleeding } from '../../../i18n/en/cycle-day'
-import SelectTabGroup from '../select-tab-group'
-import SymptomSection from './symptom-section'
-import SymptomView from './symptom-view'
-
-import { getLabelsList } from '../../helpers/labels'
-import { saveSymptom } from '../../../db'
-
-class Bleeding extends Component {
-
- static propTypes = {
- cycleDay: PropTypes.object,
- date: PropTypes.string.isRequired,
- }
-
- constructor(props) {
- super(props)
- const symptom = 'bleeding'
- const { cycleDay } = props
-
- const defaultSymptomData = {
- value: null,
- exclude: false
- }
-
- const symptomData =
- cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
-
- this.state = { ...symptomData }
-
- this.bleedingRadioProps = getLabelsList(bleeding.labels)
-
- this.symptom = symptom
- }
-
- autoSave = () => {
- const { date } = this.props
- const valuesToSave = { ...this.state }
- const hasValueToSave = typeof this.state.value === 'number'
- saveSymptom(this.symptom, date, hasValueToSave ? valuesToSave : null)
- }
-
- componentDidUpdate() {
- this.autoSave()
- }
-
- render() {
- return (
-
-
- this.setState({ value: val })}
- />
-
-
- {
- this.setState({ exclude: val })
- }}
- value={this.state.exclude}
- />
-
-
- )
- }
-}
-
-export default Bleeding
diff --git a/components/cycle-day/symptoms/cervix.js b/components/cycle-day/symptoms/cervix.js
deleted file mode 100644
index 9966d71..0000000
--- a/components/cycle-day/symptoms/cervix.js
+++ /dev/null
@@ -1,113 +0,0 @@
-import React, { Component } from 'react'
-import { Switch } from 'react-native'
-import PropTypes from 'prop-types'
-
-import { cervix as labels } from '../../../i18n/en/cycle-day'
-import SelectTabGroup from '../select-tab-group'
-import SymptomSection from './symptom-section'
-import SymptomView from './symptom-view'
-
-import { getLabelsList } from '../../helpers/labels'
-import { saveSymptom } from '../../../db'
-
-class Cervix extends Component {
-
- static propTypes = {
- cycleDay: PropTypes.object,
- date: PropTypes.string.isRequired,
- }
-
- constructor(props) {
- super(props)
- const symptom = 'cervix'
- const { cycleDay } = props
-
- const defaultSymptomData = {}
-
- const symptomData =
- cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
-
- this.state = { ...symptomData }
-
- this.cervixOpeningRadioProps = getLabelsList(labels.opening.categories)
- this.cervixFirmnessRadioProps = getLabelsList(labels.firmness.categories)
- this.cervixPositionRadioProps = getLabelsList(labels.position.categories)
-
- this.symptom = symptom
- }
-
- autoSave = () => {
- const { date } = this.props
- const { opening, firmness, position, exclude } = this.state
- const valuesToSave = {
- opening,
- firmness,
- position,
- exclude: Boolean(exclude)
- }
- const nothingEntered = ['opening', 'firmness', 'position'].every(
- val => typeof this.state[val] !== 'number')
- saveSymptom(this.symptom, date, nothingEntered ? null : valuesToSave)
- }
-
- componentDidUpdate() {
- this.autoSave()
- }
-
- render() {
- // TODO saving this info for notice when leaving incomplete data
- // const mandatoryNotCompleted = typeof this.state.opening != 'number' || typeof this.state.firmness != 'number'
- return (
-
-
- this.setState({ opening: val })}
- />
-
-
- this.setState({ firmness: val })}
- />
-
-
- this.setState({ position: val })}
- />
-
-
- {
- this.setState({ exclude: val })
- }}
- value={this.state.exclude}
- />
-
-
- )
- }
-}
-
-export default Cervix
diff --git a/components/cycle-day/symptoms/desire.js b/components/cycle-day/symptoms/desire.js
deleted file mode 100644
index de05f52..0000000
--- a/components/cycle-day/symptoms/desire.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-
-import { intensity, desire } from '../../../i18n/en/cycle-day'
-import SelectTabGroup from '../select-tab-group'
-import SymptomSection from './symptom-section'
-import SymptomView from './symptom-view'
-
-import { getLabelsList } from '../../helpers/labels'
-import { saveSymptom } from '../../../db'
-
-class Desire extends Component {
-
- static propTypes = {
- cycleDay: PropTypes.object,
- date: PropTypes.string.isRequired,
- }
-
- constructor(props) {
- super(props)
- const symptom = 'desire'
- const { cycleDay } = props
-
- const defaultSymptomData = { value: null }
-
- const symptomData =
- cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
-
- this.state = { ...symptomData }
-
- this.symptom = symptom
-
- this.desireRadioProps = getLabelsList(intensity)
- }
-
- autoSave = () => {
- const { date } = this.props
- const valuesToSave = { ...this.state }
- const hasValueToSave = typeof this.state.value === 'number'
- saveSymptom(this.symptom, date, hasValueToSave ? valuesToSave : null)
- }
-
- componentDidUpdate() {
- this.autoSave()
- }
-
- render() {
- return (
-
-
- this.setState({ value: val })}
- />
-
-
- )
- }
-}
-
-export default Desire
diff --git a/components/cycle-day/symptoms/index.js b/components/cycle-day/symptoms/index.js
deleted file mode 100644
index aa84321..0000000
--- a/components/cycle-day/symptoms/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import BleedingEditView from './bleeding'
-import TemperatureEditView from './temperature'
-import MucusEditView from './mucus'
-import CervixEditView from './cervix'
-import NoteEditView from './note'
-import DesireEditView from './desire'
-import SexEditView from './sex'
-import PainEditView from './pain'
-import MoodEditView from './mood'
-
-export default {
- BleedingEditView,
- TemperatureEditView,
- MucusEditView,
- CervixEditView,
- NoteEditView,
- DesireEditView,
- SexEditView,
- PainEditView,
- MoodEditView
-}
diff --git a/components/cycle-day/symptoms/info-symptom.js b/components/cycle-day/symptoms/info-symptom.js
deleted file mode 100644
index d45984a..0000000
--- a/components/cycle-day/symptoms/info-symptom.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { ScrollView, View, TouchableOpacity } from 'react-native'
-import Icon from 'react-native-vector-icons/SimpleLineIcons'
-import AppText from '../../common/app-text'
-import labels from '../../../i18n/en/symptom-info.js'
-import styles, {iconStyles} from '../../../styles/index'
-
-export default function InfoSymptom({ close, symptom }) {
- return (
-
-
-
-
-
-
-
- {labels[symptom].text}
-
-
-
- )
-}
-
-InfoSymptom.propTypes = {
- close: PropTypes.func.isRequired,
- symptom: PropTypes.string.isRequired
-}
diff --git a/components/cycle-day/symptoms/mood.js b/components/cycle-day/symptoms/mood.js
deleted file mode 100644
index 228d2ce..0000000
--- a/components/cycle-day/symptoms/mood.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import React, { Component } from 'react'
-import { TextInput } from 'react-native'
-import PropTypes from 'prop-types'
-
-import { mood as labels } from '../../../i18n/en/cycle-day'
-import SelectBoxGroup from '../select-box-group'
-import SymptomSection from './symptom-section'
-import SymptomView from './symptom-view'
-
-import { saveSymptom } from '../../../db'
-
-class Mood extends Component {
-
- static propTypes = {
- cycleDay: PropTypes.object,
- date: PropTypes.string.isRequired,
- }
-
- constructor(props) {
- super(props)
- const symptom = 'mood'
- const { cycleDay } = props
-
- const defaultSymptomData = {}
-
- const symptomData =
- cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
-
- this.state = { ...symptomData }
-
- // We make sure other is always true when there is a note,
- // e.g. when import is messed up.
- if (this.state.note) this.state.other = true
-
- this.symptom = symptom
- }
-
- autoSave = () => {
- const { date } = this.props
- const valuesToSave = Object.assign({}, this.state)
- if (!valuesToSave.other) {
- valuesToSave.note = null
- }
- const nothingEntered = Object.values(this.state).every(val => !val)
-
- saveSymptom(this.symptom, date, nothingEntered ? null : valuesToSave)
- }
-
- componentDidUpdate() {
- this.autoSave()
- }
-
- toggleState = (key) => {
- const curr = this.state[key]
- this.setState({[key]: !curr})
- if (key === 'other' && !curr) {
- this.setState({focusTextArea: true})
- }
- }
-
- render() {
- return (
-
-
-
- { this.state.other &&
- {
- this.setState({note: val})
- }}
- />
- }
-
-
- )
- }
-}
-
-export default Mood
diff --git a/components/cycle-day/symptoms/mucus.js b/components/cycle-day/symptoms/mucus.js
deleted file mode 100644
index 2a57362..0000000
--- a/components/cycle-day/symptoms/mucus.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import React, { Component } from 'react'
-import { Switch } from 'react-native'
-import PropTypes from 'prop-types'
-
-import { mucus as labels } from '../../../i18n/en/cycle-day'
-import computeNfpValue from '../../../lib/nfp-mucus'
-import SelectTabGroup from '../select-tab-group'
-import SymptomSection from './symptom-section'
-import SymptomView from './symptom-view'
-
-import { getLabelsList } from '../../helpers/labels'
-import { saveSymptom } from '../../../db'
-
-class Mucus extends Component {
-
- static propTypes = {
- cycleDay: PropTypes.object,
- date: PropTypes.string.isRequired,
- }
-
- constructor(props) {
- super(props)
- const symptom = 'mucus'
- const { cycleDay } = props
-
- const defaultSymptomData = {}
-
- const symptomData =
- cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
-
- this.state = { ...symptomData }
-
- this.mucusFeeling = getLabelsList(labels.feeling.categories)
- this.mucusTexture = getLabelsList(labels.texture.categories)
-
- this.symptom = symptom
- }
-
- shouldAutoSave = () => {
- const { date } = this.props
- const nothingEntered = ['feeling', 'texture'].every(
- val => typeof this.state[val] !== 'number'
- )
- const { feeling, texture, exclude} = this.state
- const valuesToSave = {
- feeling,
- texture,
- value: computeNfpValue(feeling, texture),
- exclude: Boolean(exclude)
- }
- saveSymptom(this.symptom, date, nothingEntered ? null : valuesToSave)
- }
-
- componentDidUpdate() {
- this.shouldAutoSave()
- }
-
- render() {
- // TODO leaving this info for notice when leaving incomplete data
- // const mandatoryNotCompletedYet = typeof this.state.feeling != 'number' || typeof this.state.texture != 'number'
- return (
-
-
- this.setState({ feeling: val })}
- active={this.state.feeling}
- />
-
-
- this.setState({ texture: val })}
- active={this.state.texture}
- />
-
-
- {
- this.setState({ exclude: val })
- }}
- value={this.state.exclude}
- />
-
-
- )
- }
-}
-
-export default Mucus
diff --git a/components/cycle-day/symptoms/note.js b/components/cycle-day/symptoms/note.js
deleted file mode 100644
index dc70254..0000000
--- a/components/cycle-day/symptoms/note.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import { TextInput } from 'react-native'
-
-import SymptomSection from './symptom-section'
-import { noteExplainer } from '../../../i18n/en/cycle-day'
-import { shared as sharedLabels } from '../../../i18n/en/labels'
-import SymptomView from './symptom-view'
-
-import { saveSymptom } from '../../../db'
-
-class Note extends Component {
- static propTypes = {
- cycleDay: PropTypes.object,
- date: PropTypes.string.isRequired
- }
-
- constructor(props) {
- super(props)
- const symptom = 'note'
- const { cycleDay } = props
-
- const defaultSymptomData = { value: '' }
-
- const symptomData =
- cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
-
- this.state = { ...symptomData }
-
- this.symptom = symptom
- }
-
- autoSave = () => {
- const { date } = this.props
- const valuesToSave = { ...this.state }
- saveSymptom(this.symptom, date, this.state.value ? valuesToSave : null)
- }
-
- componentDidUpdate() {
- this.autoSave()
- }
-
- render() {
- return (
-
-
- { this.setState({ value: val })}}
- value={this.state.value}
- testID='noteInput'
- />
-
-
- )
- }
-}
-
-export default Note
diff --git a/components/cycle-day/symptoms/pain.js b/components/cycle-day/symptoms/pain.js
deleted file mode 100644
index 218eb38..0000000
--- a/components/cycle-day/symptoms/pain.js
+++ /dev/null
@@ -1,94 +0,0 @@
-import React, { Component } from 'react'
-import { TextInput } from 'react-native'
-import PropTypes from 'prop-types'
-
-import { pain as labels } from '../../../i18n/en/cycle-day'
-import { shared as sharedLabels } from '../../../i18n/en/labels'
-import SelectBoxGroup from '../select-box-group'
-import SymptomSection from './symptom-section'
-import SymptomView from './symptom-view'
-
-import { saveSymptom } from '../../../db'
-
-class Pain extends Component {
-
- static propTypes = {
- cycleDay: PropTypes.object,
- date: PropTypes.string.isRequired,
- }
-
- constructor(props) {
- super(props)
- const symptom = 'pain'
- const { cycleDay } = props
-
- const defaultSymptomData = {}
-
- const symptomData =
- cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
-
- this.state = { ...symptomData }
-
- // We make sure other is always true when there is a note,
- // e.g. when import is messed up.
- if (this.state.note) this.state.other = true
-
- this.symptom = symptom
- }
-
- autoSave = () => {
- const { date } = this.props
- const valuesToSave = Object.assign({}, this.state)
- if (!valuesToSave.other) {
- valuesToSave.note = null
- }
- const nothingEntered = Object.values(this.state).every(val => !val)
-
- saveSymptom(this.symptom, date, nothingEntered ? null : valuesToSave)
- }
-
- componentDidUpdate() {
- this.autoSave()
- }
-
- toggleState = (key) => {
- const curr = this.state[key]
- this.setState({[key]: !curr})
- if (key === 'other' && !curr) {
- this.setState({focusTextArea: true})
- }
- }
-
- render() {
- return (
-
-
-
- { this.state.other &&
- {
- this.setState({note: val})
- }}
- />
- }
-
-
- )
- }
-}
-
-export default Pain
diff --git a/components/cycle-day/symptoms/sex.js b/components/cycle-day/symptoms/sex.js
deleted file mode 100644
index 6b4bd96..0000000
--- a/components/cycle-day/symptoms/sex.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import React, { Component } from 'react'
-import { TextInput } from 'react-native'
-import PropTypes from 'prop-types'
-
-import { sex as sexLabels, contraceptives as contraceptivesLabels } from '../../../i18n/en/cycle-day'
-import { shared as sharedLabels } from '../../../i18n/en/labels'
-import SelectBoxGroup from '../select-box-group'
-import SymptomSection from './symptom-section'
-import SymptomView from './symptom-view'
-
-import { saveSymptom } from '../../../db'
-
-class Sex extends Component {
-
- static propTypes = {
- cycleDay: PropTypes.object,
- date: PropTypes.string.isRequired,
- }
-
- constructor(props) {
- super(props)
- const symptom = 'sex'
- const { cycleDay } = props
-
- const defaultSymptomData = {}
-
- const symptomData =
- cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
-
- this.state = { ...symptomData }
-
- // We make sure other is always true when there is a note,
- // e.g. when import is messed up.
- if (this.state.note) this.state.other = true
-
- this.symptom = symptom
- }
-
- autoSave = () => {
- const { date } = this.props
- const valuesToSave = Object.assign({}, this.state)
- if (!valuesToSave.other) {
- valuesToSave.note = null
- }
- const nothingEntered = Object.values(this.state).every(val => !val)
-
- saveSymptom(this.symptom, date, nothingEntered ? null : valuesToSave)
- }
-
- componentDidUpdate() {
- this.autoSave()
- }
-
- toggleState = (key) => {
- const curr = this.state[key]
- this.setState({[key]: !curr})
- if (key === 'other'){
- if (curr){
- this.setState({note: ""})
- } else {
- this.setState({focusTextArea: true})
- }
- }
- }
-
- render() {
- return (
-
-
-
-
-
-
-
-
- {this.state.other &&
- {
- this.setState({ note: val })
- }}
- />
- }
-
- )
- }
-}
-
-export default Sex
diff --git a/components/cycle-day/symptoms/symptom-info.js b/components/cycle-day/symptoms/symptom-info.js
deleted file mode 100644
index 6ccfab3..0000000
--- a/components/cycle-day/symptoms/symptom-info.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import React, { Component } from 'react'
-import { TouchableOpacity } from 'react-native'
-import PropTypes from 'prop-types'
-import Icon from 'react-native-vector-icons/Entypo'
-
-import InfoPopUp from './info-symptom'
-
-import styles, { iconStyles } from '../../../styles'
-
-export default class SymptomInfo extends Component {
-
- static propTypes = {
- symptom: PropTypes.string
- }
-
- constructor() {
- super()
- this.state = { showInfo: false }
- }
-
- showInfo = () => this.setState({ showInfo: true })
-
- hideInfo = () => this.setState({ showInfo: false })
-
- render() {
- return (
-
-
-
-
- { this.state.showInfo &&
-
- }
-
- )
- }
-}
\ No newline at end of file
diff --git a/components/cycle-day/symptoms/symptom-section.js b/components/cycle-day/symptoms/symptom-section.js
deleted file mode 100644
index 8965190..0000000
--- a/components/cycle-day/symptoms/symptom-section.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import React, { Component } from 'react'
-import { View } from 'react-native'
-import PropTypes from 'prop-types'
-
-import AppText from '../../common/app-text'
-import styles from '../../../styles'
-
-export default class SymptomSection extends Component {
- render() {
- const p = this.props
- let placeHeadingInline
- if (!p.explainer && p.inline) {
- placeHeadingInline = {
- flexDirection: 'row',
- alignItems: "center"
- }
- }
- return (
-
- { p.header &&
- {p.header}
- }
-
- { p.explainer && (
-
- {p.explainer}
-
- )}
- {p.children}
-
-
- )
- }
-}
-
-SymptomSection.propTypes = {
- children: PropTypes.node,
- explainer: PropTypes.string,
- header: PropTypes.string,
- inline: PropTypes.bool
-}
\ No newline at end of file
diff --git a/components/cycle-day/symptoms/symptom-view.js b/components/cycle-day/symptoms/symptom-view.js
deleted file mode 100644
index a02ca83..0000000
--- a/components/cycle-day/symptoms/symptom-view.js
+++ /dev/null
@@ -1,117 +0,0 @@
-import React, { Component } from 'react'
-import { ScrollView, View, Alert } from 'react-native'
-import PropTypes from 'prop-types'
-
-import { connect } from 'react-redux'
-import { getDate } from '../../../slices/date'
-import { goBack } from '../../../slices/navigation'
-
-import { saveSymptom } from '../../../db'
-import formatDate from '../../helpers/format-date'
-
-import Header from '../../header'
-import SymptomInfo from './symptom-info'
-
-import { headerTitles } from '../../../i18n/en/labels'
-import { sharedDialogs } from '../../../i18n/en/cycle-day'
-
-import styles from '../../../styles'
-
-const checkIfHasValues = data => {
- const isMeaningfulValue = value => value || value === 0
- return Object.values(data).some(isMeaningfulValue)
-}
-
-class SymptomView extends Component {
-
- static propTypes = {
- symptom: PropTypes.string.isRequired,
- values: PropTypes.object,
- date: PropTypes.string,
- children: PropTypes.node,
- goBack: PropTypes.func,
- }
-
- constructor(props) {
- super()
- this.state = {
- shouldShowDelete: checkIfHasValues(props.values)
- }
- }
-
- componentDidUpdate() {
- const shouldShowDelete = checkIfHasValues(this.props.values)
- if (shouldShowDelete !== this.state.shouldShowDelete) {
- this.setState({ shouldShowDelete })
- }
- }
-
- deleteSymptomEntry() {
- const { symptom, date } = this.props
- saveSymptom(symptom, date, null)
- }
-
- onDeleteConfirmation = () => {
- this.deleteSymptomEntry()
- this.props.goBack()
- }
-
- showConfirmationAlert = () => {
-
- const cancelButton = {
- text: sharedDialogs.cancel,
- style: 'cancel'
- }
-
- const confirmationButton = {
- text: sharedDialogs.reallyDeleteData,
- onPress: this.onDeleteConfirmation
- }
-
- return Alert.alert(
- sharedDialogs.areYouSureTitle,
- sharedDialogs.areYouSureToDelete,
- [cancelButton, confirmationButton]
- )
- }
-
- render() {
- const { symptom, date, goBack } = this.props
- const { shouldShowDelete } = this.state
- const handleDelete = shouldShowDelete ? this.showConfirmationAlert : null
-
- return (
-
-
-
-
- {this.props.children}
-
-
-
-
- )
- }
-}
-
-const mapStateToProps = (state) => {
- return({
- date: getDate(state),
- })
-}
-
-const mapDispatchToProps = (dispatch) => {
- return({
- goBack: () => dispatch(goBack()),
- })
-}
-
-export default connect(
- mapStateToProps,
- mapDispatchToProps,
-)(SymptomView)
diff --git a/components/cycle-day/symptoms/temperature-input.js b/components/cycle-day/symptoms/temperature-input.js
deleted file mode 100644
index fad6da7..0000000
--- a/components/cycle-day/symptoms/temperature-input.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-
-import { View } from 'react-native'
-import AppText from '../../common/app-text'
-import AppTextInput from '../../common/app-text-input'
-
-import { temperature as labels } from '../../../i18n/en/cycle-day'
-
-import styles from '../../../styles'
-
-import { getPreviousTemperature } from '../../../db'
-import { scaleObservable } from '../../../local-storage'
-import { TEMP_MAX, TEMP_MIN } from '../../../config'
-
-export default class TemperatureInput extends Component {
-
- static propTypes = {
- temperature: PropTypes.string,
- handleTemperatureChange: PropTypes.func,
- date: PropTypes.string,
- }
-
- constructor(props) {
- super(props)
-
- let shouldShowSuggestion = false
- let suggestedTemperature = null
-
- if (!props.temperature) {
- const prevTemp = getPreviousTemperature(props.date)
- if (prevTemp) {
- shouldShowSuggestion = true
- suggestedTemperature = prevTemp.toString()
- }
- }
-
- this.state = {
- temperature: props.temperature,
- shouldShowSuggestion,
- suggestedTemperature
- }
- }
-
- setTemperature = (temperature) => {
- this.setState({
- shouldShowSuggestion: false,
- temperature
- })
- this.props.handleTemperatureChange(Number(temperature))
- }
-
- render () {
- const {
- shouldShowSuggestion,
- suggestedTemperature,
- temperature
- } = this.state
- const inputStyle = [
- styles.temperatureTextInput,
- shouldShowSuggestion ? styles.temperatureTextInputSuggestion : null
- ]
- return (
-
-
-
- °C
-
-
-
- )
- }
-}
-
-const OutOfRangeWarning = ({ temperature }) => {
- if (temperature === '') {
- return false
- }
-
- const value = Number(temperature)
- const range = { min: TEMP_MIN, max: TEMP_MAX }
- const scale = scaleObservable.value
-
- let warningMsg
-
- if (value < range.min || value > range.max) {
- warningMsg = labels.outOfAbsoluteRangeWarning
- } else if (value < scale.min || value > scale.max) {
- warningMsg = labels.outOfRangeWarning
- } else {
- warningMsg = null
- }
-
- return {warningMsg}
-}
-
-OutOfRangeWarning.propTypes = {
- temperature: PropTypes.string.isRequired
-}
diff --git a/components/cycle-day/symptoms/temperature.js b/components/cycle-day/symptoms/temperature.js
deleted file mode 100644
index 1acd975..0000000
--- a/components/cycle-day/symptoms/temperature.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import React, { Component } from 'react'
-import { Switch } from 'react-native'
-import PropTypes from 'prop-types'
-
-import { LocalTime, ChronoUnit } from 'js-joda'
-import { temperature as labels } from '../../../i18n/en/cycle-day'
-import { shared as sharedLabels } from '../../../i18n/en/labels'
-
-import AppTextInput from '../../common/app-text-input'
-import SymptomSection from './symptom-section'
-import SymptomView from './symptom-view'
-import TimeInput from './time-input'
-import TemperatureInput from './temperature-input'
-
-import { saveSymptom } from '../../../db'
-
-const minutes = ChronoUnit.MINUTES
-
-class Temperature extends Component {
-
- static propTypes = {
- cycleDay: PropTypes.object,
- date: PropTypes.string.isRequired,
- }
-
- constructor(props) {
- super(props)
- const symptom = 'temperature'
- const { cycleDay } = props
-
- const defaultSymptomData = {
- time: LocalTime.now().truncatedTo(minutes).toString(),
- temperature: null,
- note: '',
- exclude: false
- }
-
- const symptomData =
- cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
-
- const { value, ...restSymptomData } = symptomData
- this.state = { temperature: value, ...restSymptomData }
-
- this.symptom = symptom
- }
-
- isDeleteIconActive() {
- return ['temperature', 'note', 'exclude'].some(key => {
- // the time is always and the suggested temp sometimes prefilled, so they're not relevant for setting
- // the delete button active.
- return this.state[key] || this.state[key] === 0
- })
- }
-
- autoSave = () => {
- const { date } = this.props
- const { temperature, exclude, time, note } = this.state
-
- const valuesToSave = {
- value: temperature,
- exclude,
- time,
- note
- }
-
- saveSymptom(this.symptom, date, temperature ? valuesToSave : null)
- }
-
- setTime = (time) => {
- this.setState({ time })
- }
-
- setTemperature = (temperature) => {
- this.setState({ temperature })
- }
-
- setNote = (note) => {
- this.setState({ note })
- }
-
- componentDidUpdate() {
- this.autoSave()
- }
-
- render() {
- const { temperature } = this.state
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- {
- this.setState({ exclude: val })
- }}
- value={this.state.exclude}
- />
-
-
- )
- }
-}
-
-export default Temperature
diff --git a/components/cycle-day/symptoms/time-input.js b/components/cycle-day/symptoms/time-input.js
deleted file mode 100644
index 2c48923..0000000
--- a/components/cycle-day/symptoms/time-input.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import { Keyboard } from 'react-native'
-
-import AppTextInput from '../../common/app-text-input'
-import styles from '../../../styles'
-
-import DateTimePicker from 'react-native-modal-datetime-picker-nevo'
-import moment from 'moment'
-
-export default class TimeInput extends Component {
-
- static propTypes = {
- time: PropTypes.string,
- handleTimeChange: PropTypes.func,
- }
-
- constructor(props) {
- super(props)
-
- this.state = {
- isTimePickerVisible: false,
- }
- }
-
- showTimePicker = () => {
- Keyboard.dismiss()
- this.setState({ isTimePickerVisible: true })
- }
-
- hideTimePicker = () => {
- this.setState({ isTimePickerVisible: false })
- }
-
- handleConfirm = (jsDate) => {
- // DateTimePicker also when in mode="time" returns full date (with time)
- const time = moment(jsDate).format('HH:mm')
- this.props.handleTimeChange(time)
- this.setState({
- isTimePickerVisible: false
- })
- }
-
- render () {
- return (
-
-
-
-
- )
- }
-}
diff --git a/components/cycle-day/temperature.js b/components/cycle-day/temperature.js
new file mode 100644
index 0000000..7a8b7d1
--- /dev/null
+++ b/components/cycle-day/temperature.js
@@ -0,0 +1,158 @@
+import React, { Component } from 'react'
+import { StyleSheet, View } from 'react-native'
+import PropTypes from 'prop-types'
+import { Keyboard } from 'react-native'
+import DateTimePicker from 'react-native-modal-datetime-picker-nevo'
+import moment from 'moment'
+
+import AppText from '../common/app-text'
+import AppTextInput from '../common/app-text-input'
+import Segment from '../common/segment'
+
+import { connect } from 'react-redux'
+import { getDate } from '../../slices/date'
+import { isTemperatureOutOfRange, isPreviousTemperature } from '../helpers/cycle-day'
+
+import { temperature as labels } from '../../i18n/en/cycle-day'
+
+import { Colors, Containers, Sizes, Spacing } from '../../styles/redesign'
+
+const formatTemperature = value => value === null
+ ? value
+ : Number.parseFloat(value).toFixed(2)
+
+class Temperature extends Component {
+
+ static propTypes = {
+ data: PropTypes.object,
+ date: PropTypes.string.isRequired,
+ save: PropTypes.func
+ }
+
+ constructor(props) {
+ super(props)
+
+ const { data, date } = this.props
+ const { value } = data
+ const { shouldShowSuggestion, suggestedTemperature } =
+ isPreviousTemperature(date)
+
+ this.state = {
+ isTimePickerVisible: false,
+ shouldShowSuggestion,
+ suggestedTemperature: formatTemperature(suggestedTemperature),
+ value: formatTemperature(value)
+ }
+ }
+
+ onCancelTimePicker = () => {
+ this.setState({ isTimePickerVisible: false })
+ }
+
+ onChangeTemperature = (value) => {
+ this.setState({ value, shouldShowSuggestion: false })
+ }
+
+ onShowTimePicker = () => {
+ Keyboard.dismiss()
+ this.setState({ isTimePickerVisible: true })
+ }
+
+ setTemperature = () => {
+ const { value } = this.state
+ this.props.save(value, 'value')
+ }
+
+ setTime = (jsDate) => {
+ const time = moment(jsDate).format('HH:mm')
+ const isTimePickerVisible = false
+
+ this.props.save(time, 'time')
+ this.setState({ isTimePickerVisible })
+ }
+
+ render() {
+ const { shouldShowSuggestion, suggestedTemperature, value } = this.state
+ const { time } = this.props.data
+
+ const inputStyle = (shouldShowSuggestion && value === null)
+ ? { color: Colors.grey }
+ : {color: Colors.greyDark}
+ const outOfRangeWarning = isTemperatureOutOfRange(value)
+ let temperatureToShow = null
+
+ if (value) {
+ temperatureToShow = value
+ } else if (shouldShowSuggestion) {
+ temperatureToShow = suggestedTemperature
+ }
+
+ return (
+
+
+ {labels.temperature.explainer}
+
+
+ °C
+
+ { outOfRangeWarning !== null &&
+
+ {outOfRangeWarning}
+
+ }
+
+
+ {labels.time}
+
+
+
+
+ )
+ }
+}
+
+const styles = StyleSheet.create({
+ container: {
+ ...Containers.rowContainer
+ },
+ hint: {
+ fontStyle: 'italic',
+ fontSize: Sizes.small
+ },
+ hintContainer: {
+ marginVertical: Spacing.tiny
+ },
+ title: {
+ fontSize: Sizes.subtitle
+ }
+})
+
+
+const mapStateToProps = (state) => {
+ return({
+ date: getDate(state),
+ })
+}
+
+export default connect(
+ mapStateToProps,
+ null,
+)(Temperature)
diff --git a/components/helpers/cycle-day.js b/components/helpers/cycle-day.js
new file mode 100644
index 0000000..653765a
--- /dev/null
+++ b/components/helpers/cycle-day.js
@@ -0,0 +1,437 @@
+import { ChronoUnit, LocalDate, LocalTime } from 'js-joda'
+
+import { getPreviousTemperature, saveSymptom } from '../../db'
+import { scaleObservable } from '../../local-storage'
+
+import * as labels from '../../i18n/en/cycle-day'
+import { getLabelsList } from './labels'
+import { TEMP_MAX, TEMP_MIN } from '../../config'
+
+const bleedingLabels = labels.bleeding.labels
+const cervixLabels = labels.cervix
+const contraceptiveLabels = labels.contraceptives.categories
+const intensityLabels = labels.intensity
+const moodLabels = labels.mood.categories
+const mucusLabels = labels.mucus
+const noteDescription = labels.noteExplainer
+const painLabels = labels.pain.categories
+const sexLabels = labels.sex.categories
+const temperatureLabels = labels.temperature
+
+const minutes = ChronoUnit.MINUTES
+
+const isNumber = (value) => typeof value === 'number'
+export const shouldShow = (value) => value !== null ? true : false
+
+export const isPreviousTemperature = (temperature) => {
+ const previousTemperature = getPreviousTemperature(temperature)
+ const shouldShowSuggestion = previousTemperature ? true : false
+ const suggestedTemperature = previousTemperature ?
+ previousTemperature.toString() : null
+
+ return { shouldShowSuggestion, suggestedTemperature }
+}
+
+export const isTemperatureOutOfRange = (temperature) => {
+ if (temperature === '') return null
+
+ const value = Number(temperature)
+ const range = { min: TEMP_MIN, max: TEMP_MAX }
+ const scale = scaleObservable.value
+
+ let warningMsg = null
+
+ if (value < range.min || value > range.max) {
+ warningMsg = labels.temperature.outOfAbsoluteRangeWarning
+ } else if (value < scale.min || value > scale.max) {
+ warningMsg = labels.temperature.outOfRangeWarning
+ }
+
+ return warningMsg
+}
+
+export const blank = {
+ bleeding: {
+ exclude: false,
+ value: null
+ },
+ cervix: {
+ exclude: false,
+ firmness: null,
+ opening: null,
+ position: null,
+ },
+ desire: {
+ value: null
+ },
+ mood:{
+ happy: null,
+ sad: null,
+ stressed: null,
+ balanced: null,
+ fine: null,
+ anxious: null,
+ energetic: null,
+ fatigue: null,
+ angry: null,
+ other: null,
+ note: null
+ },
+ mucus: {
+ exclude: false,
+ feeling: null,
+ texture: null,
+ value: null
+ },
+ note: {
+ value: null
+ },
+ pain: {
+ cramps: null,
+ ovulationPain: null,
+ headache: null,
+ backache: null,
+ nausea: null,
+ tenderBreasts: null,
+ migraine: null,
+ other: null,
+ note: null
+ },
+ sex: {
+ solo: null,
+ partner: null,
+ condom: null,
+ pill: null,
+ iud: null,
+ patch: null,
+ ring: null,
+ implant: null,
+ diaphragm: null,
+ none: null,
+ other: null,
+ note: null
+ },
+ temperature: {
+ exclude: false,
+ note: null,
+ time: LocalTime.now().truncatedTo(minutes).toString(),
+ value: null
+ }
+}
+
+export const symtomPage = {
+ bleeding: {
+ excludeText: labels.bleeding.exclude.explainer,
+ note: null,
+ selectBoxGroups: null,
+ selectTabGroups: [{
+ key: 'value',
+ options: getLabelsList(bleedingLabels),
+ title: labels.bleeding.heaviness.explainer,
+ }]
+ },
+ cervix: {
+ excludeText: cervixLabels.excludeExplainer,
+ note: null,
+ selectBoxGroups: null,
+ selectTabGroups: [
+ {
+ key: 'opening',
+ options: getLabelsList(cervixLabels.opening.categories),
+ title: cervixLabels.opening.explainer,
+ },
+ {
+ key: 'firmness',
+ options: getLabelsList(cervixLabels.firmness.categories),
+ title: cervixLabels.firmness.explainer,
+ },
+ {
+ key: 'position',
+ options: getLabelsList(cervixLabels.position.categories),
+ title: cervixLabels.position.explainer,
+ }
+ ]
+ },
+ desire: {
+ excludeText: null,
+ note: null,
+ selectBoxGroups: null,
+ selectTabGroups: [{
+ key: 'value',
+ options: getLabelsList(intensityLabels),
+ title: labels.desire.explainer
+ }]
+ },
+ mucus: {
+ excludeText: mucusLabels.excludeExplainer,
+ note: null,
+ selectBoxGroups: null,
+ selectTabGroups: [
+ {
+ key: 'feeling',
+ options: getLabelsList(mucusLabels.feeling.categories),
+ title: mucusLabels.feeling.explainer,
+ },
+ {
+ key: 'texture',
+ options: getLabelsList(mucusLabels.texture.categories),
+ title: mucusLabels.texture.explainer,
+ }
+ ]
+ },
+ mood: {
+ excludeText: null,
+ note: null,
+ selectBoxGroups: [{
+ key: 'mood',
+ options: moodLabels,
+ title: labels.mood.explainer
+ }],
+ selectTabGroups: null
+ },
+ note: {
+ excludeText: null,
+ note: noteDescription,
+ selectBoxGroups: null,
+ selectTabGroups: null
+ },
+ pain: {
+ excludeText: null,
+ note: null,
+ selectBoxGroups: [{
+ key: 'pain',
+ options: painLabels,
+ title: labels.pain.explainer
+ }],
+ selectTabGroups: null
+ },
+ sex: {
+ excludeText: null,
+ note: null,
+ selectBoxGroups: [
+ {
+ key: 'sex',
+ options: sexLabels,
+ title: labels.sex.explainer,
+ },
+ {
+ key: 'contraceptives',
+ options: contraceptiveLabels,
+ title: labels.contraceptives.explainer,
+ }
+ ],
+ selectTabGroups: null
+ },
+ temperature: {
+ excludeText: temperatureLabels.exclude.explainer,
+ note: temperatureLabels.note.explainer,
+ selectBoxGroups: null,
+ selectTabGroups: null
+ }
+}
+
+export const save = {
+ bleeding: (data, date, shouldDeleteData) => {
+ const { exclude, value } = data
+ const isDataEntered = isNumber(value)
+ const valuesToSave = shouldDeleteData || !isDataEntered
+ ? null : { value, exclude }
+
+ saveSymptom('bleeding', date, valuesToSave)
+ },
+ cervix: (data, date, shouldDeleteData) => {
+ const { opening, firmness, position, exclude } = data
+ const isDataEntered = ['opening', 'firmness', 'position'].some(
+ value => isNumber(data[value]))
+ const valuesToSave = shouldDeleteData || !isDataEntered
+ ? null : { opening, firmness, position, exclude }
+
+ saveSymptom('cervix', date, valuesToSave)
+ },
+ desire: (data, date, shouldDeleteData) => {
+ const { value } = data
+ const valuesToSave = shouldDeleteData || !isNumber(value)
+ ? null : { value }
+
+ saveSymptom('desire', date, valuesToSave)
+ },
+ mood: (data, date, shouldDeleteData) => {
+ saveBoxSymptom(data, date, shouldDeleteData, 'mood')
+ },
+ mucus: (data, date, shouldDeleteData) => {
+ const { feeling, texture, exclude } = data
+ const isDataEntered = ['feeling', 'texture'].some(
+ value => isNumber(data[value]))
+ const valuesToSave = shouldDeleteData || !isDataEntered
+ ? null : { feeling, texture, exclude }
+
+ saveSymptom('mucus', date, valuesToSave)
+ },
+ note: (data, date, shouldDeleteData) => {
+ const { value } = data
+ const isValidData = value !== null && value !== ''
+ const valuesToSave = shouldDeleteData || !isValidData ? null : { value }
+
+ saveSymptom('note', date, valuesToSave)
+ },
+ pain: (data, date, shouldDeleteData) => {
+ saveBoxSymptom(data, date, shouldDeleteData, 'pain')
+ },
+ sex: (data, date, shouldDeleteData) => {
+ saveBoxSymptom(data, date, shouldDeleteData, 'sex')
+ },
+ temperature: (data, date, shouldDeleteData) => {
+ const { exclude, note, time, value } = data
+ const valuesToSave = {
+ exclude,
+ note,
+ time,
+ value: Number(value)
+ }
+
+ saveSymptom(
+ 'temperature',
+ date,
+ (shouldDeleteData || value === null) ? null : valuesToSave
+ )
+ }
+}
+
+const saveBoxSymptom = (data, date, shouldDeleteData, symptom) => {
+ const isDataEntered = Object.keys(data).some(key => data[key] !== null)
+ const valuesToSave = shouldDeleteData || !isDataEntered
+ ? null : data
+
+ saveSymptom(symptom, date, valuesToSave)
+}
+
+const label = {
+ bleeding: ({ value, exclude }) => {
+ if (isNumber(value)) {
+ const bleedingLabel = bleedingLabels[value]
+ return exclude ? `(${bleedingLabel})` : bleedingLabel
+ }
+ },
+ temperature: ({ value, time, exclude }) => {
+ if (isNumber(value)) {
+ let temperatureLabel = `${value} °C`
+ if (time) {
+ temperatureLabel += ` - ${time}`
+ }
+ if (exclude) {
+ temperatureLabel = `(${temperatureLabel})`
+ }
+ return temperatureLabel
+ }
+ },
+ mucus: mucus => {
+ const filledCategories = ['feeling', 'texture'].filter(c => isNumber(mucus[c]))
+ let label = filledCategories.map(category => {
+ return labels.mucus.subcategories[category] + ': ' + labels.mucus[category].categories[mucus[category]]
+ }).join(', ')
+
+ if (isNumber(mucus.value)) label += `\n => ${labels.mucusNFP[mucus.value]}`
+ if (mucus.exclude) label = `(${label})`
+
+ return label
+ },
+ cervix: cervix => {
+ const filledCategories = ['opening', 'firmness', 'position'].filter(c => isNumber(cervix[c]))
+ let label = filledCategories.map(category => {
+ return labels.cervix.subcategories[category] + ': ' + labels.cervix[category].categories[cervix[category]]
+ }).join(', ')
+
+ if (cervix.exclude) label = `(${label})`
+
+ return label
+ },
+ note: note => note.value,
+ desire: ({ value }) => {
+ if (isNumber(value)) {
+ return intensityLabels[value]
+ }
+ },
+ sex: sex => {
+ const sexLabel = []
+ if (sex && Object.values({...sex}).some(val => val)){
+ Object.keys(sex).forEach(key => {
+ if(sex[key] && key !== 'other' && key !== 'note') {
+ sexLabel.push(
+ sexLabels[key] ||
+ contraceptiveLabels[key]
+ )
+ }
+ if(key === 'other' && sex.other) {
+ let label = contraceptiveLabels[key]
+ if(sex.note) {
+ label = `${label} (${sex.note})`
+ }
+ sexLabel.push(label)
+ }
+ })
+ return sexLabel.join(', ')
+ }
+ },
+ pain: pain => {
+ const painLabel = []
+ if (pain && Object.values({...pain}).some(val => val)){
+ Object.keys(pain).forEach(key => {
+ if(pain[key] && key !== 'other' && key !== 'note') {
+ painLabel.push(painLabels[key])
+ }
+ if(key === 'other' && pain.other) {
+ let label = painLabels[key]
+ if(pain.note) {
+ label = `${label} (${pain.note})`
+ }
+ painLabel.push(label)
+ }
+ })
+ return painLabel.join(', ')
+ }
+ },
+ mood: mood => {
+ const moodLabel = []
+ if (mood && Object.values({...mood}).some(val => val)){
+ Object.keys(mood).forEach(key => {
+ if(mood[key] && key !== 'other' && key !== 'note') {
+ moodLabel.push(moodLabels[key])
+ }
+ if(key === 'other' && mood.other) {
+ let label = moodLabels[key]
+ if(mood.note) {
+ label = `${label} (${mood.note})`
+ }
+ moodLabel.push(label)
+ }
+ })
+ return moodLabel.join(', ')
+ }
+ }
+}
+
+export const getData = (symptom, symptomData) => {
+ return symptomData && label[symptom](symptomData)
+}
+
+export const prevDate = (dateString) => {
+ return LocalDate.parse(dateString).minusDays(1).toString()
+}
+
+export const nextDate = (dateString) => {
+ return LocalDate.parse(dateString).plusDays(1).toString()
+}
+
+export const isDateInFuture = (dateString) => {
+ return LocalDate.now().isBefore(LocalDate.parse(dateString))
+}
+
+export const isTomorrowInFuture = (dateString) => {
+ const tomorrow = nextDate(dateString)
+ return LocalDate.now().isBefore(LocalDate.parse(tomorrow))
+}
+
+export const isYesterdayInFuture = (dateString) => {
+ const yesterday = prevDate(dateString)
+ return LocalDate.now().isBefore(LocalDate.parse(yesterday))
+}
\ No newline at end of file
diff --git a/components/helpers/format-date.js b/components/helpers/format-date.js
index d353dc6..88e4ec4 100644
--- a/components/helpers/format-date.js
+++ b/components/helpers/format-date.js
@@ -1,14 +1,24 @@
import { LocalDate } from 'js-joda'
import moment from 'moment'
+import { general as labels } from '../../i18n/en/cycle-day'
+
export default function (date) {
const today = LocalDate.now()
const dateToDisplay = LocalDate.parse(date)
return today.equals(dateToDisplay) ?
- 'today' :
+ labels.today :
moment(date).format('MMMM Do YYYY')
}
export function formatDateForShortText (date) {
return moment(date.toString()).format('dddd, MMMM Do')
+}
+
+export function dateToTitle(dateString) {
+ const today = LocalDate.now()
+ const dateToDisplay = LocalDate.parse(dateString)
+ return today.equals(dateToDisplay) ?
+ labels.today :
+ moment(dateString).format('MMMM Do')
}
\ No newline at end of file
diff --git a/components/pages.js b/components/pages.js
index a4e6bc6..e7e6d98 100644
--- a/components/pages.js
+++ b/components/pages.js
@@ -1,17 +1,8 @@
-import symptomViews from './cycle-day/symptoms'
import settingsViews from './settings'
import settingsLabels from '../i18n/en/settings'
const labels = settingsLabels.menuItems
-const symptomsPages = Object.keys(symptomViews).map(symptomView => ({
- component: symptomView,
- parent: 'CycleDay',
-}))
-
-export const isSymptomView =
- (page) => Object.keys(symptomViews).includes(page)
-
export const isSettingsView =
(page) => Object.keys(settingsViews).includes(page)
@@ -82,6 +73,5 @@ export const pages = [
{
component: 'CycleDay',
parent: 'Home',
- },
- ...symptomsPages
+ }
]
\ No newline at end of file
diff --git a/components/views.js b/components/views.js
index 1cb8545..e32d29a 100644
--- a/components/views.js
+++ b/components/views.js
@@ -1,7 +1,6 @@
import Home from './home'
import Calendar from './calendar'
import CycleDay from './cycle-day/cycle-day-overview'
-import symptomViews from './cycle-day/symptoms'
import Chart from './chart/chart'
import SettingsMenu from './settings/settings-menu'
import settingsViews from './settings'
@@ -14,6 +13,5 @@ export const viewsList = {
Chart,
SettingsMenu,
...settingsViews,
- Stats,
- ...symptomViews
+ Stats
}
diff --git a/config.js b/config.js
index 4f11d22..343d760 100644
--- a/config.js
+++ b/config.js
@@ -4,6 +4,7 @@ export const ACTION_IMPORT = 'import'
export const SYMPTOMS = [
'bleeding',
+ 'temperature',
'mucus',
'cervix',
'sex',
diff --git a/i18n/en/cycle-day.js b/i18n/en/cycle-day.js
index 4399ca5..981b648 100644
--- a/i18n/en/cycle-day.js
+++ b/i18n/en/cycle-day.js
@@ -31,6 +31,7 @@ export const cervix = {
categories: ['low', 'medium', 'high'],
explainer: 'How high up in the vagina is the cervix?'
},
+ excludeExplainer: "You can exclude this value if you don't want to use it for fertility detection.",
actionHint: 'Choose values for at least "Opening" and "Firmness" to save.'
}
@@ -132,6 +133,11 @@ export const temperature = {
export const noteExplainer = "Anything you want to add for the day?"
+export const general = {
+ cycleDayNumber: "Cycle day ",
+ today: "Today"
+}
+
export const sharedDialogs = {
cancel: 'Cancel',
areYouSureTitle: 'Are you sure?',
diff --git a/styles/containers.js b/styles/containers.js
index 257c7b4..6526d3a 100644
--- a/styles/containers.js
+++ b/styles/containers.js
@@ -1,4 +1,19 @@
+import Colors from './colors'
+import Spacing from './spacing'
+
export default {
+ box: {
+ borderColor: Colors.orange,
+ borderRadius: 5,
+ borderWidth: 1,
+ marginTop: Spacing.small,
+ marginRight: Spacing.small,
+ paddingHorizontal: Spacing.small,
+ paddingVertical: Spacing.tiny
+ },
+ boxActive: {
+ backgroundColor: Colors.orange,
+ },
centerItems: {
alignItems: 'center',
flex: 1,
@@ -8,5 +23,11 @@ export default {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between'
+ },
+ selectGroupContainer: {
+ alignItems: 'center',
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ marginVertical: Spacing.small
}
}
\ No newline at end of file
diff --git a/styles/spacing.js b/styles/spacing.js
index 32c5797..b665347 100644
--- a/styles/spacing.js
+++ b/styles/spacing.js
@@ -2,5 +2,7 @@ export default {
tiny: 4,
small: 10,
base: 16,
- large: 20
+ large: 20,
+ symptomTileWidth: '48%',
+ textWidth: '70%'
}
\ No newline at end of file
diff --git a/styles/typography.js b/styles/typography.js
index 153f1fa..f0f6bb5 100644
--- a/styles/typography.js
+++ b/styles/typography.js
@@ -96,5 +96,12 @@ export default {
fontSize: sizes.title,
marginHorizontal: Spacing.base,
...title
+ },
+ titleWithoutMargin: {
+ alignSelf: 'center',
+ color: Colors.purple,
+ fontFamily: fonts.bold,
+ fontWeight: '700',
+ fontSize: sizes.title,
}
}