diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b03cccc..cc1e829 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,9 +33,10 @@ We are an open source project and we highly appreciate contributions. At the sam - 🔮 open source - 🩸 feminist and gender inclusive - 🔒 secure: data entered stays with that person/on their device - - 🔬 science based: we implemented the symptothermal method - + - 🔬 science-based: we implemented the sympto-thermal method + This means that we will never implement anything that contradicts these core values. Some examples: We will never build a cloud integration, we will never make an ovulation prediction. + - If you would like to make a sustainable contribution to the project, we would be happy to join the game. ### Reporting Bugs or Making Suggestions @@ -48,7 +49,6 @@ If you found a bug or have suggestions, please :one: first review the [list of e - If you want to open a merge request, yeah :tada: exciting! We are using a template for merge requests to make sure we explain what we have done and why. - Keep in mind that people who will review your merge request are more motivated to do so when the merge request is well explained and ideally not too big. - ### Thank you ![](https://media.giphy.com/media/kPA88elN9kYco/giphy.gif) diff --git a/components/Home.js b/components/Home.js index 783d8c4..af02576 100644 --- a/components/Home.js +++ b/components/Home.js @@ -14,7 +14,10 @@ import { determinePredictionText, formatWithOrdinalSuffix, } from './helpers/home' -import { periodPredictionObservable } from '../local-storage' +import { + fertilityTrackingObservable, + periodPredictionObservable, +} from '../local-storage' import { Colors, Fonts, Sizes, Spacing } from '../styles' import { LocalDate } from '@js-joda/core' @@ -28,11 +31,12 @@ const Home = ({ navigate, setDate }) => { navigate('CycleDay') } + const isFertilityTrackingEnabled = fertilityTrackingObservable.value const todayDateString = LocalDate.now().toString() const { getCycleDayNumber, getPredictedMenses } = cycleModule() const cycleDayNumber = getCycleDayNumber(todayDateString) const { status, phase, statusText } = - getFertilityStatusForDay(todayDateString) + isFertilityTrackingEnabled && getFertilityStatusForDay(todayDateString) const isPeriodPredictionEnabled = periodPredictionObservable.value const prediction = determinePredictionText(getPredictedMenses(), t) @@ -55,7 +59,7 @@ const Home = ({ navigate, setDate }) => { )} - {phase && ( + {isFertilityTrackingEnabled && phase && ( {formatWithOrdinalSuffix(phase)} diff --git a/components/chart/chart.js b/components/chart/chart.js index b394129..5cc54b3 100644 --- a/components/chart/chart.js +++ b/components/chart/chart.js @@ -22,6 +22,8 @@ import { painTrackingCategoryObservable, sexTrackingCategoryObservable, temperatureTrackingCategoryObservable, + mucusTrackingCategoryObservable, + cervixTrackingCategoryObservable, } from '../../local-storage' import { makeColumnInfo } from '../helpers/chart' @@ -72,6 +74,10 @@ const CycleChart = ({ navigate, setDate }) => { const symptomRowEnabledSymptoms = symptomRowSymptoms.filter((symptom) => { if (symptom === 'sex') { return sexTrackingCategoryObservable.value ? symptom : null + } else if (symptom === 'mucus') { + return mucusTrackingCategoryObservable.value ? symptom : null + } else if (symptom === 'cervix') { + return cervixTrackingCategoryObservable.value ? symptom : null } else if (symptom === 'desire') { return desireTrackingCategoryObservable.value ? symptom : null } else if (symptom === 'pain') { diff --git a/components/common/app-switch.js b/components/common/app-switch.js index d45758f..dfa8dfe 100644 --- a/components/common/app-switch.js +++ b/components/common/app-switch.js @@ -1,10 +1,10 @@ import React from 'react' -import { StyleSheet, Switch, View } from 'react-native' +import { Platform, StyleSheet, Switch, View } from 'react-native' import PropTypes from 'prop-types' import AppText from './app-text' -import { Colors, Containers } from '../../styles' +import { Colors, Containers, Spacing } from '../../styles' const AppSwitch = ({ onToggle, text, value, disabled }) => { const trackColor = { true: Colors.turquoiseDark } @@ -34,9 +34,14 @@ AppSwitch.propTypes = { const styles = StyleSheet.create({ container: { ...Containers.rowContainer, + marginTop: Spacing.tiny, }, switch: { flex: 1, + transform: + Platform.OS === 'ios' + ? [{ scaleX: 0.8 }, { scaleY: 0.8 }] + : [{ scaleX: 1 }, { scaleY: 1 }], }, textContainer: { flex: 4, diff --git a/components/common/tracking-category-switch.js b/components/common/tracking-category-switch.js new file mode 100644 index 0000000..aef5824 --- /dev/null +++ b/components/common/tracking-category-switch.js @@ -0,0 +1,63 @@ +import React from 'react' +import { Platform, StyleSheet, Switch, View } from 'react-native' +import PropTypes from 'prop-types' + +import AppText from './app-text' + +import DripIcon from '../../assets/drip-icons' +import { Colors, Containers, Sizes, Spacing } from '../../styles' + +const TrackingCategorySwitch = ({ onToggle, symptom, text, value }) => { + const trackColor = { true: Colors.turquoiseDark } + const iconColor = value ? Colors.iconColors[symptom].color : Colors.grey + + return ( + + + + + + {text} + + + + ) +} + +TrackingCategorySwitch.propTypes = { + onToggle: PropTypes.func.isRequired, + symptom: PropTypes.string, + text: PropTypes.string, + value: PropTypes.bool, +} + +const styles = StyleSheet.create({ + container: { + ...Containers.rowContainer, + marginVertical: Spacing.tiny, + }, + iconContainer: { + marginRight: Spacing.tiny, + flex: 1, + }, + textContainer: { + flex: 5, + }, + appSwitch: { + flex: 2, + transform: + Platform.OS === 'ios' + ? [{ scaleX: 0.8 }, { scaleY: 0.8 }] + : [{ scaleX: 1 }, { scaleY: 1 }], + }, +}) +export default TrackingCategorySwitch diff --git a/components/cycle-day/cycle-day-overview.js b/components/cycle-day/cycle-day-overview.js index a033135..7f999f9 100644 --- a/components/cycle-day/cycle-day-overview.js +++ b/components/cycle-day/cycle-day-overview.js @@ -16,6 +16,8 @@ import { painTrackingCategoryObservable, sexTrackingCategoryObservable, temperatureTrackingCategoryObservable, + mucusTrackingCategoryObservable, + cervixTrackingCategoryObservable, } from '../../local-storage' import { Spacing } from '../../styles' import { SYMPTOMS } from '../../config' @@ -40,6 +42,10 @@ const CycleDayOverView = ({ date, setDate, isTemperatureEditView }) => { return temperatureTrackingCategoryObservable.value ? symptom : null } else if (symptom === 'sex') { return sexTrackingCategoryObservable.value ? symptom : null + } else if (symptom === 'mucus') { + return mucusTrackingCategoryObservable.value ? symptom : null + } else if (symptom === 'cervix') { + return cervixTrackingCategoryObservable.value ? symptom : null } else if (symptom === 'desire') { return desireTrackingCategoryObservable.value ? symptom : null } else if (symptom === 'pain') { diff --git a/components/cycle-day/select-tab-group.js b/components/cycle-day/select-tab-group.js index b298cbd..25db72a 100644 --- a/components/cycle-day/select-tab-group.js +++ b/components/cycle-day/select-tab-group.js @@ -1,22 +1,58 @@ import React from 'react' import PropTypes from 'prop-types' -import { StyleSheet, TouchableOpacity, View } from 'react-native' +import { Alert, StyleSheet, TouchableOpacity, View } from 'react-native' import AppText from '../common/app-text' import { Colors, Containers } from '../../styles' +import labels from '../../i18n/en/settings' + +export default function SelectTabGroup({ + activeButton, + buttons, + onSelect, + disabled, +}) { + // TODO https://gitlab.com/bloodyhealth/drip/-/issues/707 + const oneTimeTransformIntoNumber = + typeof activeButton === 'boolean' && Number(activeButton) + const isSecondarySymptomSwitch = + buttons[0]['label'] === labels.secondarySymptom.mucus + + // Disable is only used for secondarySymptom in customization, if more come up maybe consider more tidy solution + const showDisabledAlert = (label) => { + if (label === 'cervix' || label === 'mucus') { + Alert.alert( + labels.secondarySymptom.disabled.title, + labels.secondarySymptom.disabled.message + ) + } + } -export default function SelectTabGroup({ activeButton, buttons, onSelect }) { return ( {buttons.map(({ label, value }, i) => { - const isActive = value === activeButton - const boxStyle = [styles.box, isActive && styles.boxActive] - const textStyle = [styles.text, isActive && styles.textActive] + const isActive = + value === activeButton || value === oneTimeTransformIntoNumber + const boxStyle = [ + styles.box, + isActive && styles.boxActive, + isSecondarySymptomSwitch && styles.purpleBox, + isSecondarySymptomSwitch && isActive && styles.activePurpleBox, + disabled && styles.disabledBox, + ] + const textStyle = [ + styles.text, + isSecondarySymptomSwitch && styles.purpleText, + isActive && styles.textActive, + disabled && styles.greyText, + ] return ( onSelect(value)} + onPress={() => + !disabled ? onSelect(value) : showDisabledAlert(label) + } key={i} style={boxStyle} > @@ -32,6 +68,7 @@ SelectTabGroup.propTypes = { activeButton: PropTypes.number, buttons: PropTypes.array.isRequired, onSelect: PropTypes.func.isRequired, + disabled: PropTypes.bool, } const styles = StyleSheet.create({ @@ -50,4 +87,20 @@ const styles = StyleSheet.create({ textActive: { color: 'white', }, + purpleBox: { + borderColor: Colors.purple, + }, + activePurpleBox: { + backgroundColor: Colors.purple, + }, + purpleText: { + color: Colors.purple, + }, + greyText: { + color: Colors.grey, + }, + disabledBox: { + borderColor: Colors.grey, + backgroundColor: Colors.turquoiseLight, + }, }) diff --git a/components/cycle-day/symptom-box.js b/components/cycle-day/symptom-box.js index f9fcd0c..1d78f02 100644 --- a/components/cycle-day/symptom-box.js +++ b/components/cycle-day/symptom-box.js @@ -20,7 +20,7 @@ const SymptomBox = ({ editedSymptom, setEditedSymptom, }) => { - const { t } = useTranslation(null, { keyPrefix: 'cycleDay.symptomBox' }) + const { t } = useTranslation(null, { keyPrefix: 'symptoms' }) const isSymptomEdited = editedSymptom === symptom const isSymptomDisabled = isDateInFuture(date) && symptom !== 'note' const isExcluded = symptomData !== null ? symptomData.exclude : false diff --git a/components/helpers/chart.js b/components/helpers/chart.js index ba25223..245123f 100644 --- a/components/helpers/chart.js +++ b/components/helpers/chart.js @@ -1,6 +1,10 @@ import { LocalDate } from '@js-joda/core' -import { scaleObservable, unitObservable } from '../../local-storage' +import { + fertilityTrackingObservable, + scaleObservable, + unitObservable, +} from '../../local-storage' import { getCycleStatusForDay } from '../../lib/sympto-adapter' import { getCycleDay, getAmountOfCycleDays } from '../../db' @@ -270,7 +274,8 @@ export function nfpLines() { if (dateString < cycle.startDate) updateCurrentCycle(dateString) if (cycle.noMoreCycles) return ret - const tempShift = cycle.status.temperatureShift + const tempShift = + fertilityTrackingObservable.value && cycle.status.temperatureShift if (tempShift) { if (tempShift.firstHighMeasurementDay.date === dateString) { diff --git a/components/settings/customization/index.js b/components/settings/customization/index.js index 136e8f1..d1381aa 100644 --- a/components/settings/customization/index.js +++ b/components/settings/customization/index.js @@ -1,37 +1,47 @@ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { Alert, Pressable } from 'react-native' +import { useTranslation } from 'react-i18next' import AppPage from '../../common/app-page' import AppSwitch from '../../common/app-switch' import AppText from '../../common/app-text' import TemperatureSlider from './temperature-slider' import Segment from '../../common/segment' +import TrackingCategorySwitch from '../../common/tracking-category-switch' +import SelectTabGroup from '../../cycle-day/select-tab-group' import { desireTrackingCategoryObservable, + fertilityTrackingObservable, moodTrackingCategoryObservable, noteTrackingCategoryObservable, painTrackingCategoryObservable, sexTrackingCategoryObservable, temperatureTrackingCategoryObservable, + mucusTrackingCategoryObservable, + cervixTrackingCategoryObservable, + periodPredictionObservable, + useCervixAsSecondarySymptomObservable, saveDesireTrackingCategory, + saveFertilityTrackingEnabled, saveMoodTrackingCategory, saveNoteTrackingCategory, savePainTrackingCategory, + saveMucusTrackingCategory, + saveCervixTrackingCategory, savePeriodPrediction, saveSexTrackingCategory, saveTemperatureTrackingCategory, - saveUseCervix, - periodPredictionObservable, - useCervixObservable, + saveUseCervixAsSecondarySymptom, } from '../../../local-storage' import labels from '../../../i18n/en/settings' import { SYMPTOMS } from '../../../config' const Settings = () => { - const [shouldUseCervix, setShouldUseCervix] = useState( - useCervixObservable.value - ) + const { t } = useTranslation(null, { keyPrefix: 'symptoms' }) + + const [useCervixAsSecondarySymptom, setUseCervixAsSecondarySymptom] = + useState(useCervixAsSecondarySymptomObservable.value) const [isPeriodPredictionEnabled, setPeriodPrediction] = useState( periodPredictionObservable.value @@ -40,6 +50,14 @@ const Settings = () => { const [isTemperatureTrackingCategoryEnabled, setTemperatureTrackingCategory] = useState(temperatureTrackingCategoryObservable.value) + const [isMucusTrackingCategoryEnabled, setMucusTrackingCategory] = useState( + mucusTrackingCategoryObservable.value + ) + + const [isCervixTrackingCategoryEnabled, setCervixTrackingCategory] = useState( + cervixTrackingCategoryObservable.value + ) + const [isSexTrackingCategoryEnabled, setSexTrackingCategory] = useState( sexTrackingCategoryObservable.value ) @@ -60,19 +78,33 @@ const Settings = () => { noteTrackingCategoryObservable.value ) - const [isEnabled, setIsEnabled] = useState(false) - const toggleSwitch = () => setIsEnabled((previousState) => !previousState) + const [isFertilityTrackingEnabled, setFertilityTrackingEnabled] = useState( + fertilityTrackingObservable.value + ) + + const fertilityTrackingToggle = (value) => { + setFertilityTrackingEnabled(value) + saveFertilityTrackingEnabled(value) + } const temperatureTrackingCategoryToggle = (value) => { setTemperatureTrackingCategory(value) saveTemperatureTrackingCategory(value) + if (!value) { + setFertilityTrackingEnabled(false) + saveFertilityTrackingEnabled(false) + } + } + const mucusTrackingCategoryToggle = (value) => { + manageSecondarySymptom(cervixTrackingCategoryObservable.value, value) + } + const cervixTrackingCategoryToggle = (value) => { + manageSecondarySymptom(value, mucusTrackingCategoryObservable.value) } - const sexTrackingCategoryToggle = (value) => { setSexTrackingCategory(value) saveSexTrackingCategory(value) } - const desireTrackingCategoryToggle = (value) => { setDesireTrackingCategory(value) saveDesireTrackingCategory(value) @@ -89,99 +121,187 @@ const Settings = () => { setNoteTrackingCategory(value) saveNoteTrackingCategory(value) } - const onPeriodPredictionToggle = (value) => { setPeriodPrediction(value) savePeriodPrediction(value) } + const fertilityTrackingText = isFertilityTrackingEnabled + ? labels.fertilityTracking.on + : labels.fertilityTracking.off + const periodPredictionText = isPeriodPredictionEnabled ? labels.periodPrediction.on : labels.periodPrediction.off - const onCervixToggle = (value) => { - setShouldUseCervix(value) - saveUseCervix(value) + const secondarySymptomButtons = [ + { + label: labels.secondarySymptom.mucus, + value: 0, + }, + { + label: labels.secondarySymptom.cervix, + value: 1, + }, + ] + + const onSelectTab = (value) => { + if (isMucusTrackingCategoryEnabled && isCervixTrackingCategoryEnabled) { + setUseCervixAsSecondarySymptom(value) + saveUseCervixAsSecondarySymptom(value) + } else { + secondarySymptomDisabledPrompt() + } } - const cervixText = shouldUseCervix - ? labels.useCervix.cervixModeOn - : labels.useCervix.cervixModeOff + // is needed so secondary symptom is set correct on load + useEffect(() => { + manageSecondarySymptom( + cervixTrackingCategoryObservable.value, + mucusTrackingCategoryObservable.value + ) + }, []) + + const manageSecondarySymptom = (cervix, mucus) => { + if (!cervix && mucus) { + setUseCervixAsSecondarySymptom(0) + saveUseCervixAsSecondarySymptom(0) + } else if (cervix && !mucus) { + setUseCervixAsSecondarySymptom(1) + saveUseCervixAsSecondarySymptom(1) + } else if (!cervix && !mucus) { + setFertilityTrackingEnabled(false) + saveFertilityTrackingEnabled(false) + } + setMucusTrackingCategory(mucus) + saveMucusTrackingCategory(mucus) + setCervixTrackingCategory(cervix) + saveCervixTrackingCategory(cervix) + } + + const secondarySymptomDisabledPrompt = () => { + if (!isFertilityTrackingEnabled) { + Alert.alert( + labels.secondarySymptom.disabled.title, + labels.secondarySymptom.disabled.message + ) + } else if ( + !isMucusTrackingCategoryEnabled == isCervixTrackingCategoryEnabled + ) { + Alert.alert( + labels.secondarySymptom.disabled.title, + labels.secondarySymptom.disabled.noSecondaryEnabled + ) + } + } + + const manageFertilityFeature = + isTemperatureTrackingCategoryEnabled && + (isMucusTrackingCategoryEnabled || isCervixTrackingCategoryEnabled) + + const cervixText = useCervixAsSecondarySymptom + ? labels.secondarySymptom.cervixModeOn + : labels.secondarySymptom.cervixModeOff const sliderDisabledPrompt = () => { if (!isTemperatureTrackingCategoryEnabled) { - Alert.alert(labels.disabled.title, labels.disabled.message) + Alert.alert(labels.tempScale.disabled, labels.tempScale.disabledMessage) } } + + const fertilityDisabledPrompt = () => { + if (!manageFertilityFeature) { + Alert.alert( + labels.fertilityTracking.disabledTitle, + labels.fertilityTracking.disabled + ) + } + } + return ( - - - + + - { + mucusTrackingCategoryToggle(enabled) + }} + text={t(SYMPTOMS[2])} + value={isMucusTrackingCategoryEnabled} + symptom={SYMPTOMS[2]} + /> + { + cervixTrackingCategoryToggle(enabled) + }} + text={t(SYMPTOMS[3])} + value={isCervixTrackingCategoryEnabled} + symptom={SYMPTOMS[3]} + /> + - - - - - - - - - - - - {isTemperatureTrackingCategoryEnabled && ( - <> - {labels.tempScale.segmentExplainer} - - - )} - {!isTemperatureTrackingCategoryEnabled && ( - {labels.disabled.message} - )} + + + {labels.fertilityTracking.message} + - - {isTemperatureTrackingCategoryEnabled && ( - - )} - {!isTemperatureTrackingCategoryEnabled && ( - {labels.disabled.message} - )} + + {labels.tempScale.segmentExplainer} + + + + + + + {cervixText} + onSelectTab(value)} + disabled={!isFertilityTrackingEnabled} + /> diff --git a/components/settings/customization/temperature-slider.js b/components/settings/customization/temperature-slider.js index 2019d55..7d91f7a 100644 --- a/components/settings/customization/temperature-slider.js +++ b/components/settings/customization/temperature-slider.js @@ -1,5 +1,6 @@ import React, { useState } from 'react' import { StyleSheet, View } from 'react-native' +import PropTypes from 'prop-types' import Slider from '@ptomasroos/react-native-multi-slider' import alertError from '../common/alert-error' @@ -10,7 +11,7 @@ import { Colors, Sizes } from '../../../styles' import labels from '../../../i18n/en/settings' import { TEMP_MIN, TEMP_MAX, TEMP_SLIDER_STEP } from '../../../config' -const TemperatureSlider = () => { +const TemperatureSlider = ({ disabled }) => { const savedValue = scaleObservable.value const [minTemperature, setMinTemperature] = useState(savedValue.min) const [maxTemperature, setMaxTemperature] = useState(savedValue.max) @@ -25,6 +26,14 @@ const TemperatureSlider = () => { } } + const sliderAccentBackground = disabled + ? styles.disabledSliderAccentBackground + : styles.sliderAccentBackground + + const sliderBackground = disabled + ? styles.disabledSliderBackground + : styles.sliderBackground + return ( { max={TEMP_MAX} min={TEMP_MIN} onValuesChange={onTemperatureSliderChange} - selectedStyle={styles.sliderAccentBackground} step={TEMP_SLIDER_STEP} trackStyle={styles.slider} - unselectedStyle={styles.sliderBackground} values={[minTemperature, maxTemperature]} + enabledOne={!disabled} + enabledTwo={!disabled} + selectedStyle={sliderAccentBackground} + unselectedStyle={sliderBackground} /> ) @@ -47,6 +58,10 @@ const TemperatureSlider = () => { export default TemperatureSlider +TemperatureSlider.propTypes = { + disabled: PropTypes.bool, +} + const styles = StyleSheet.create({ container: { alignItems: 'center', @@ -54,6 +69,7 @@ const styles = StyleSheet.create({ }, marker: { backgroundColor: Colors.turquoiseDark, + borderRadius: 50, elevation: 4, height: Sizes.subtitle, @@ -66,7 +82,13 @@ const styles = StyleSheet.create({ sliderAccentBackground: { backgroundColor: Colors.turquoiseDark, }, + disabledSliderAccentBackground: { + backgroundColor: Colors.grey, + }, sliderBackground: { backgroundColor: Colors.turquoise, }, + disabledSliderBackground: { + backgroundColor: Colors.greyLight, + }, }) diff --git a/i18n/en.json b/i18n/en.json index 436ca79..6467b64 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -7,18 +7,16 @@ "chart": { "tutorial": "You can swipe the chart to view more dates." }, - "cycleDay": { - "symptomBox": { - "bleeding": "bleeding", - "temperature": "temperature", - "mucus": "cervical mucus", - "cervix": "cervix", - "note": "note", - "desire": "desire", - "sex": "sex", - "pain": "pain", - "mood": "mood" - } + "symptoms": { + "bleeding": "bleeding", + "temperature": "temperature", + "mucus": "cervical mucus", + "cervix": "cervix", + "note": "note", + "desire": "desire", + "sex": "sex", + "pain": "pain", + "mood": "mood" }, "labels": { "bleedingPrediction": { diff --git a/i18n/en/settings.js b/i18n/en/settings.js index 68b9ffc..5748d02 100644 --- a/i18n/en/settings.js +++ b/i18n/en/settings.js @@ -1,6 +1,10 @@ import links from './links' export default { + customization: { + title: 'Customization', + trackingCategories: 'Tracking categories', + }, export: { errors: { noData: 'There is no data to export', @@ -32,17 +36,16 @@ export default { tempScale: { segmentTitle: 'Temperature scale', segmentExplainer: - 'Change the minimum and maximum value for the temperature chart', + 'Change the minimum and maximum value for the temperature chart.', min: 'Min', max: 'Max', loadError: 'Could not load saved temperature scale settings', saveError: 'Could not save temperature scale settings', + disabled: 'Disabled', + disabledMessage: + 'To use the temperature scale please first enable temperature tracking above.', }, - disabled: { - title: 'This feature is turned off', - message: - 'Please first enable the temperature tracking category in the customization settings.', - }, + tempReminder: { title: 'Temperature reminder', noTimeSet: 'Set a time for a daily reminder to take your temperature', @@ -66,16 +69,35 @@ export default { 'To use the period reminder please first enable period predictions in the customization settings.', }, }, - useCervix: { + fertilityTracking: { + title: 'Fertility phases calculation', + disabledTitle: 'Disabled', + disabled: + 'To use fertility phases calculation please enable temperature tracking and cervical mucus or cervix tracking above.', + message: + 'If you enter menstrual bleeding, temperature and cervical mucus or cervix data according to the sympto-thermal method, drip will calculate cycle phases with the provided data.', + on: 'If you switch this off, drip will not show fertility related information.', + off: 'If you switch this on, drip will show fertility related information.', + }, + secondarySymptom: { title: 'Secondary symptom', cervixModeOn: - 'Cervix values are being used for symptothermal fertility detection. You can switch here to use cervical mucus values for symptothermal fertility detection', + 'Cervix values are being used for fertility detection according to the sympto-thermal method.', cervixModeOff: - 'By default, cervical mucus values are being used for symptothermal fertility detection. You can switch here to use cervix values for symptothermal fertility detection', + 'Cervical mucus values are being used for fertility detection according to the sympto-thermal method.', + disabled: { + title: 'Disabled', + message: + 'To set a secondary symptom please first enable the cervical mucus or cervix tracking category as well as temperature and fertility phases calculation above.', + noSecondaryEnabled: + 'To switch the secondary symptom both cervical mucus and cervix need to be enabled above.', + }, + mucus: 'cervical mucus', + cervix: 'cervix', }, periodPrediction: { title: 'Period predictions', - on: 'drip predicts your 3 next menstrual bleedings based on the statistics of your previously tracked cycles, min 3 complete cycles.', + on: 'drip predicts your 3 next menstrual bleedings based on statistics if you previously tracked at least 3 complete cycles.', off: 'There are no predictions for menstrual cycles displayed. If turned on the calendar and the home screen will display period predictions.', }, passwordSettings: { @@ -119,6 +141,6 @@ Making any changes to your password setting will keep your data as it was before }, preOvu: { title: 'Infertile days at cycle start', - note: `drip. applies the sympto-thermal method for calculating infertile days at the start of the cycle (see ${links.wiki.url} for more info). However, drip. does not currently apply the so called 20-day-rule, which determines infertile days at the cycle start from past cycle lengths in case no past symptothermal info is available.`, + note: `drip. applies the sympto-thermal method for calculating infertile days at the start of the cycle (see ${links.wiki.url} for more info). However, drip. does not currently apply the so called 20-day-rule, which determines infertile days at the cycle start from past cycle lengths in case no past sympto-thermal info is available.`, }, } diff --git a/lib/sympto-adapter.js b/lib/sympto-adapter.js index aac2807..44e3d18 100644 --- a/lib/sympto-adapter.js +++ b/lib/sympto-adapter.js @@ -1,9 +1,15 @@ import getFertilityStatus from 'sympto' import cycleModule from './cycle' -import { useCervixObservable } from '../local-storage' +import { fertilityTrackingObservable, useCervixAsSecondarySymptomObservable } from '../local-storage' import { fertilityStatus as labels } from '../i18n/en/labels' +const isFertilityTrackingEnabled = fertilityTrackingObservable.value + export function getFertilityStatusForDay(dateString) { + if (!isFertilityTrackingEnabled) { + return + } + const status = getCycleStatusForDay(dateString) if (!status) return { status: labels.fertile, @@ -34,6 +40,10 @@ export function getFertilityStatusForDay(dateString) { } export function getCycleStatusForDay(dateString, opts = {}) { + if (!isFertilityTrackingEnabled) { + return + } + const { getCycleForDay, getCyclesBefore, @@ -57,7 +67,7 @@ export function getCycleStatusForDay(dateString, opts = {}) { } } - cycleInfo.secondarySymptom = useCervixObservable.value ? 'cervix' : 'mucus' + cycleInfo.secondarySymptom = useCervixAsSecondarySymptomObservable.value ? 'cervix' : 'mucus' return getFertilityStatus(cycleInfo) } diff --git a/local-storage.js b/local-storage.js index ece4355..66ce305 100644 --- a/local-storage.js +++ b/local-storage.js @@ -59,12 +59,19 @@ export async function savePeriodPrediction(bool) { } } -export const useCervixObservable = Observable() -setObvWithInitValue('useCervix', useCervixObservable, false) +export const useCervixAsSecondarySymptomObservable = Observable() +setObvWithInitValue( + 'useCervixAsSecondarySymptom', + useCervixAsSecondarySymptomObservable, + 0 +) -export async function saveUseCervix(bool) { - await AsyncStorage.setItem('useCervix', JSON.stringify(bool)) - useCervixObservable.set(bool) +export async function saveUseCervixAsSecondarySymptom(value) { + await AsyncStorage.setItem( + 'useCervixAsSecondarySymptom', + JSON.stringify(value) + ) + useCervixAsSecondarySymptomObservable.set(value) } export const hasEncryptionObservable = Observable() @@ -100,13 +107,30 @@ export async function saveTemperatureTrackingCategory(bool) { temperatureTrackingCategoryObservable.set(bool) if (!temperatureTrackingCategoryObservable.value) { - const result = await AsyncStorage.getItem('tempReminder') - if (JSON.parse(result).enabled) { + // if temperature tracking is turned off, the temperature reminder gets disabled + const tempReminderResult = await AsyncStorage.getItem('tempReminder') + if (tempReminderResult && JSON.parse(tempReminderResult).enabled) { tempReminderObservable.set(false) } } } +export const mucusTrackingCategoryObservable = Observable() +setObvWithInitValue('mucus', mucusTrackingCategoryObservable, true) + +export async function saveMucusTrackingCategory(bool) { + await AsyncStorage.setItem('mucus', JSON.stringify(bool)) + mucusTrackingCategoryObservable.set(bool) +} + +export const cervixTrackingCategoryObservable = Observable() +setObvWithInitValue('cervix', cervixTrackingCategoryObservable, true) + +export async function saveCervixTrackingCategory(bool) { + await AsyncStorage.setItem('cervix', JSON.stringify(bool)) + cervixTrackingCategoryObservable.set(bool) +} + export const sexTrackingCategoryObservable = Observable() setObvWithInitValue('sex', sexTrackingCategoryObservable, true) @@ -147,6 +171,14 @@ export async function saveNoteTrackingCategory(bool) { noteTrackingCategoryObservable.set(bool) } +export const fertilityTrackingObservable = Observable() +setObvWithInitValue('fertilityTracking', fertilityTrackingObservable, true) + +export async function saveFertilityTrackingEnabled(bool) { + await AsyncStorage.setItem('fertilityTracking', JSON.stringify(bool)) + fertilityTrackingObservable.set(bool) +} + async function setObvWithInitValue(key, obv, defaultValue) { const result = await AsyncStorage.getItem(key) let value diff --git a/styles/colors.js b/styles/colors.js index f091d02..7baf8cd 100644 --- a/styles/colors.js +++ b/styles/colors.js @@ -18,6 +18,7 @@ const shadesOfPink = ['#c485a6', '#b15c89', pinkColor] // light to dark const lightGreenColor = '#bccd67' const orangeColor = '#bc6642' const mintColor = '#6ca299' +const turquoiseDark = '#69CBC1' export default { greyDark: '#555', @@ -27,7 +28,7 @@ export default { orange: '#F38337', purple: '#3A2671', purpleLight: '#938EB2', - turquoiseDark: '#69CBC1', + turquoiseDark: turquoiseDark, turquoise: '#CFECEA', turquoiseLight: '#E9F2ED', iconColors: { @@ -35,6 +36,9 @@ export default { color: redColor, shades: shadesOfRed, }, + temperature: { + color: turquoiseDark, + }, mucus: { color: violetColor, shades: shadesOfViolet,