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, } }