Merge branch '671-feature-customisation-not-display-menstrual-bleeding-prediction' into 'main'
Feature: Customizable period prediction Closes #671 See merge request bloodyhealth/drip!647
This commit is contained in:
@@ -9,7 +9,6 @@ cache:
|
|||||||
test_async:
|
test_async:
|
||||||
script:
|
script:
|
||||||
- npm install npm@7.0.1 -g
|
- npm install npm@7.0.1 -g
|
||||||
- npm install
|
|
||||||
- npm test
|
- npm test
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
|
|||||||
+7
-3
@@ -14,6 +14,7 @@ import {
|
|||||||
determinePredictionText,
|
determinePredictionText,
|
||||||
formatWithOrdinalSuffix,
|
formatWithOrdinalSuffix,
|
||||||
} from './helpers/home'
|
} from './helpers/home'
|
||||||
|
import { periodPredictionObservable } from '../local-storage'
|
||||||
|
|
||||||
import { Colors, Fonts, Sizes, Spacing } from '../styles'
|
import { Colors, Fonts, Sizes, Spacing } from '../styles'
|
||||||
import { LocalDate } from '@js-joda/core'
|
import { LocalDate } from '@js-joda/core'
|
||||||
@@ -32,6 +33,7 @@ const Home = ({ navigate, setDate }) => {
|
|||||||
const cycleDayNumber = getCycleDayNumber(todayDateString)
|
const cycleDayNumber = getCycleDayNumber(todayDateString)
|
||||||
const { status, phase, statusText } =
|
const { status, phase, statusText } =
|
||||||
getFertilityStatusForDay(todayDateString)
|
getFertilityStatusForDay(todayDateString)
|
||||||
|
const isPeriodPredictionEnabled = periodPredictionObservable.value
|
||||||
const prediction = determinePredictionText(getPredictedMenses(), t)
|
const prediction = determinePredictionText(getPredictedMenses(), t)
|
||||||
|
|
||||||
const cycleDayText = cycleDayNumber
|
const cycleDayText = cycleDayNumber
|
||||||
@@ -65,9 +67,11 @@ const Home = ({ navigate, setDate }) => {
|
|||||||
<Asterisk />
|
<Asterisk />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<View style={styles.line}>
|
{isPeriodPredictionEnabled && (
|
||||||
<AppText style={styles.turquoiseText}>{prediction}</AppText>
|
<View style={styles.line}>
|
||||||
</View>
|
<AppText style={styles.turquoiseText}>{prediction}</AppText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
<Button isCTA isSmall={false} onPress={navigateToCycleDayView}>
|
<Button isCTA isSmall={false} onPress={navigateToCycleDayView}>
|
||||||
{t('labels.home.addDataForToday')}
|
{t('labels.home.addDataForToday')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import AppText from './app-text'
|
|||||||
|
|
||||||
import { Containers } from '../../styles'
|
import { Containers } from '../../styles'
|
||||||
|
|
||||||
const AppSwitch = ({ onToggle, text, value, trackColor }) => {
|
const AppSwitch = ({ onToggle, text, value, trackColor, disabled }) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.textContainer}>
|
<View style={styles.textContainer}>
|
||||||
@@ -17,6 +17,7 @@ const AppSwitch = ({ onToggle, text, value, trackColor }) => {
|
|||||||
style={styles.switch}
|
style={styles.switch}
|
||||||
value={value}
|
value={value}
|
||||||
trackColor={trackColor}
|
trackColor={trackColor}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
@@ -27,6 +28,7 @@ AppSwitch.propTypes = {
|
|||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
value: PropTypes.bool,
|
value: PropTypes.bool,
|
||||||
trackColor: PropTypes.string,
|
trackColor: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { LocalDate } from '@js-joda/core'
|
|||||||
import { verticalScale } from 'react-native-size-matters'
|
import { verticalScale } from 'react-native-size-matters'
|
||||||
|
|
||||||
import { Colors, Fonts, Sizes } from '../../styles'
|
import { Colors, Fonts, Sizes } from '../../styles'
|
||||||
|
import { periodPredictionObservable } from '../../local-storage'
|
||||||
|
|
||||||
const { shades } = Colors.iconColors.bleeding
|
const { shades } = Colors.iconColors.bleeding
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ export const toCalFormat = (bleedingDaysSortedByDate) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const predictionToCalFormat = (predictedDays) => {
|
export const predictionToCalFormat = (predictedDays) => {
|
||||||
|
if (!periodPredictionObservable.value) return {}
|
||||||
if (!predictedDays.length) return {}
|
if (!predictedDays.length) return {}
|
||||||
const todayDateString = LocalDate.now().toString()
|
const todayDateString = LocalDate.now().toString()
|
||||||
const middleIndex = (predictedDays[0].length - 1) / 2
|
const middleIndex = (predictedDays[0].length - 1) / 2
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ import AppText from '../../common/app-text'
|
|||||||
import TemperatureSlider from './temperature-slider'
|
import TemperatureSlider from './temperature-slider'
|
||||||
import Segment from '../../common/segment'
|
import Segment from '../../common/segment'
|
||||||
|
|
||||||
import { useCervixObservable, saveUseCervix } from '../../../local-storage'
|
import {
|
||||||
|
periodPredictionObservable,
|
||||||
|
savePeriodPrediction,
|
||||||
|
useCervixObservable,
|
||||||
|
saveUseCervix,
|
||||||
|
} from '../../../local-storage'
|
||||||
import { Colors } from '../../../styles'
|
import { Colors } from '../../../styles'
|
||||||
import labels from '../../../i18n/en/settings'
|
import labels from '../../../i18n/en/settings'
|
||||||
|
|
||||||
@@ -15,9 +20,22 @@ const Settings = () => {
|
|||||||
useCervixObservable.value
|
useCervixObservable.value
|
||||||
)
|
)
|
||||||
|
|
||||||
const [isEnabled, setIsEnabled] = useState(true)
|
const [isPeriodPredictionEnabled, setPeriodPrediction] = useState(
|
||||||
|
periodPredictionObservable.value
|
||||||
|
)
|
||||||
|
|
||||||
|
const [isEnabled, setIsEnabled] = useState(false)
|
||||||
const toggleSwitch = () => setIsEnabled((previousState) => !previousState)
|
const toggleSwitch = () => setIsEnabled((previousState) => !previousState)
|
||||||
|
|
||||||
|
const onPeriodPredictionToggle = (value) => {
|
||||||
|
setPeriodPrediction(value)
|
||||||
|
savePeriodPrediction(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const periodPredictionText = isPeriodPredictionEnabled
|
||||||
|
? labels.periodPrediction.on
|
||||||
|
: labels.periodPrediction.off
|
||||||
|
|
||||||
const onCervixToggle = (value) => {
|
const onCervixToggle = (value) => {
|
||||||
setShouldUseCervix(value)
|
setShouldUseCervix(value)
|
||||||
saveUseCervix(value)
|
saveUseCervix(value)
|
||||||
@@ -103,13 +121,11 @@ const Settings = () => {
|
|||||||
/>
|
/>
|
||||||
</Segment>
|
</Segment>
|
||||||
|
|
||||||
<Segment title={'Period prediction'}>
|
<Segment title={labels.periodPrediction.title} last>
|
||||||
<AppSwitch
|
<AppSwitch
|
||||||
onToggle={toggleSwitch}
|
onToggle={onPeriodPredictionToggle}
|
||||||
text={
|
text={periodPredictionText}
|
||||||
'If turned on drip will predict your next menstrual bleeding, after you have tracked 3 complete menstrual cycles.'
|
value={isPeriodPredictionEnabled}
|
||||||
}
|
|
||||||
value={isEnabled}
|
|
||||||
trackColor={{ true: Colors.turquoiseDark }}
|
trackColor={{ true: Colors.turquoiseDark }}
|
||||||
/>
|
/>
|
||||||
</Segment>
|
</Segment>
|
||||||
|
|||||||
@@ -8,11 +8,15 @@ import TemperatureReminder from './temperature-reminder'
|
|||||||
import {
|
import {
|
||||||
periodReminderObservable,
|
periodReminderObservable,
|
||||||
savePeriodReminder,
|
savePeriodReminder,
|
||||||
|
periodPredictionObservable,
|
||||||
} from '../../../local-storage'
|
} from '../../../local-storage'
|
||||||
|
|
||||||
import labels from '../../../i18n/en/settings'
|
import labels from '../../../i18n/en/settings'
|
||||||
|
import { Alert, Pressable } from 'react-native'
|
||||||
|
|
||||||
const Reminders = () => {
|
const Reminders = () => {
|
||||||
|
const isPeriodPredictionDisabled = !periodPredictionObservable.value
|
||||||
|
|
||||||
const [isPeriodReminderEnabled, setIsPeriodReminderEnabled] = useState(
|
const [isPeriodReminderEnabled, setIsPeriodReminderEnabled] = useState(
|
||||||
periodReminderObservable.value.enabled
|
periodReminderObservable.value.enabled
|
||||||
)
|
)
|
||||||
@@ -21,15 +25,27 @@ const Reminders = () => {
|
|||||||
savePeriodReminder({ enabled: isEnabled })
|
savePeriodReminder({ enabled: isEnabled })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reminderDisabledPrompt = () => {
|
||||||
|
if (!periodPredictionObservable.value) {
|
||||||
|
Alert.alert(
|
||||||
|
labels.periodReminder.alertNoPeriodReminder.title,
|
||||||
|
labels.periodReminder.alertNoPeriodReminder.message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppPage>
|
<AppPage>
|
||||||
<Segment title={labels.periodReminder.title}>
|
<Pressable onPress={reminderDisabledPrompt}>
|
||||||
<AppSwitch
|
<Segment title={labels.periodReminder.title}>
|
||||||
onToggle={periodReminderToggle}
|
<AppSwitch
|
||||||
text={labels.periodReminder.reminderText}
|
onToggle={periodReminderToggle}
|
||||||
value={isPeriodReminderEnabled}
|
text={labels.periodReminder.reminderText}
|
||||||
/>
|
value={isPeriodReminderEnabled}
|
||||||
</Segment>
|
disabled={isPeriodPredictionDisabled}
|
||||||
|
/>
|
||||||
|
</Segment>
|
||||||
|
</Pressable>
|
||||||
<Segment title={labels.tempReminder.title} last>
|
<Segment title={labels.tempReminder.title} last>
|
||||||
<TemperatureReminder />
|
<TemperatureReminder />
|
||||||
</Segment>
|
</Segment>
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ export default {
|
|||||||
'Get a notification 3 days before your next period is likely to start.',
|
'Get a notification 3 days before your next period is likely to start.',
|
||||||
notification: (daysToEndOfPrediction) =>
|
notification: (daysToEndOfPrediction) =>
|
||||||
`Your next period is likely to start in 3 to ${daysToEndOfPrediction} days.`,
|
`Your next period is likely to start in 3 to ${daysToEndOfPrediction} days.`,
|
||||||
|
alertNoPeriodReminder: {
|
||||||
|
title: 'Period predictions turned off',
|
||||||
|
message:
|
||||||
|
'To use the period reminder please first enable period predictions in the customization settings.',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
useCervix: {
|
useCervix: {
|
||||||
title: 'Secondary symptom',
|
title: 'Secondary symptom',
|
||||||
@@ -58,6 +63,11 @@ export default {
|
|||||||
cervixModeOff:
|
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',
|
'By default, cervical mucus values are being used for symptothermal fertility detection. You can switch here to use cervix values for symptothermal fertility detection',
|
||||||
},
|
},
|
||||||
|
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.',
|
||||||
|
off: 'There are no predictions for menstrual cycles displayed. If turned on the calendar and the home screen will display period predictions.',
|
||||||
|
},
|
||||||
passwordSettings: {
|
passwordSettings: {
|
||||||
title: 'App password',
|
title: 'App password',
|
||||||
explainerDisabled:
|
explainerDisabled:
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ After tracking at least 3 menstrual cycles, drip. will give you an overview of
|
|||||||
· whether the length of your cycles varied significantly (in "stats" and in bleeding predictions)
|
· whether the length of your cycles varied significantly (in "stats" and in bleeding predictions)
|
||||||
· and predict your next 3 cycles with a range of 3 or 5 days (on home screen and "calendar").
|
· and predict your next 3 cycles with a range of 3 or 5 days (on home screen and "calendar").
|
||||||
|
|
||||||
The app allows you to track different intensities of bleeding. On the chart and on the calendar, bleeding values are colored in different shades of red. The darker, the more intense your bleeding. Every bleeding value that is not excluded is taken into account for fertility calculation and prediction for the start of next cycles.
|
The app allows you to track different intensities of bleeding. On the chart and on the calendar, bleeding values are colored in different shades of red. The darker, the more intense your bleeding. Every bleeding value that is not excluded is taken into account for fertility calculation and period predictions.
|
||||||
|
|
||||||
Excluding bleeding values is for tracking bleeding when it's not marking the start of a new cycle or the continuation of menstrual bleeding the day(s) before, e.g. bleeding caused by ovulation or a miscarriage.
|
Excluding bleeding values is for tracking bleeding when it's not marking the start of a new cycle or the continuation of menstrual bleeding the day(s) before, e.g. bleeding caused by ovulation or a miscarriage.
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,21 @@ export async function savePeriodReminder(reminder) {
|
|||||||
periodReminderObservable.set(reminder)
|
periodReminderObservable.set(reminder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const periodPredictionObservable = Observable()
|
||||||
|
setObvWithInitValue('periodPrediction', periodPredictionObservable, true)
|
||||||
|
|
||||||
|
export async function savePeriodPrediction(bool) {
|
||||||
|
await AsyncStorage.setItem('periodPrediction', JSON.stringify(bool))
|
||||||
|
periodPredictionObservable.set(bool)
|
||||||
|
|
||||||
|
if (!periodPredictionObservable.value) {
|
||||||
|
const result = await AsyncStorage.getItem('periodReminder')
|
||||||
|
if (JSON.parse(result).enabled) {
|
||||||
|
periodReminderObservable.set(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const useCervixObservable = Observable()
|
export const useCervixObservable = Observable()
|
||||||
setObvWithInitValue('useCervix', useCervixObservable, false)
|
setObvWithInitValue('useCervix', useCervixObservable, false)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user