Merge branch 'main' into 'feature/prods'

# Conflicts:
#   components/cycle-day/symptom-edit-view.js
#   i18n/en/cycle-day.js
This commit is contained in:
bl00dymarie
2024-04-03 19:56:12 +00:00
116 changed files with 5090 additions and 4628 deletions
+22 -5
View File
@@ -14,6 +14,10 @@ import {
determinePredictionText,
formatWithOrdinalSuffix,
} from './helpers/home'
import {
fertilityTrackingObservable,
periodPredictionObservable,
} from '../local-storage'
import { Colors, Fonts, Sizes, Spacing } from '../styles'
import { LocalDate } from '@js-joda/core'
@@ -27,11 +31,13 @@ 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)
const cycleDayText = cycleDayNumber
@@ -45,6 +51,7 @@ const Home = ({ navigate, setDate }) => {
>
<AppText style={styles.title}>{moment().format('MMM Do YYYY')}</AppText>
{/* display if at least 1 bleeding day has been entered */}
{cycleDayNumber && (
<View style={styles.line}>
<AppText style={styles.whiteSubtitle}>{cycleDayText}</AppText>
@@ -53,7 +60,9 @@ const Home = ({ navigate, setDate }) => {
</AppText>
</View>
)}
{phase && (
{/* display if fertility tracking enabled and if phase 1, 2 or 3 has been identified */}
{isFertilityTrackingEnabled && phase && (
<View style={styles.line}>
<AppText style={styles.whiteSubtitle}>
{formatWithOrdinalSuffix(phase)}
@@ -65,9 +74,14 @@ const Home = ({ navigate, setDate }) => {
<Asterisk />
</View>
)}
<View style={styles.line}>
<AppText style={styles.turquoiseText}>{prediction}</AppText>
</View>
{isPeriodPredictionEnabled && (
<View style={styles.line}>
<AppText style={styles.turquoiseText}>{prediction}</AppText>
</View>
)}
{!isFertilityTrackingEnabled && <View style={styles.largePadding}></View>}
<Button isCTA isSmall={false} onPress={navigateToCycleDayView}>
{t('labels.home.addDataForToday')}
</Button>
@@ -106,6 +120,9 @@ const styles = StyleSheet.create({
color: 'white',
fontSize: Sizes.subtitle,
},
largePadding: {
padding: Spacing.large,
},
})
Home.propTypes = {
+12 -3
View File
@@ -4,15 +4,19 @@ import { StyleSheet, View } from 'react-native'
import AppText from '../common/app-text'
import { Typography } from '../../styles'
import { Sizes, Typography } from '../../styles'
import { CHART_YAXIS_WIDTH } from '../../config'
import { shared as labels } from '../../i18n/en/labels'
const ChartLegend = ({ height }) => {
return (
<View style={[styles.container, { height }]}>
<AppText style={styles.textBold}>#</AppText>
<AppText style={styles.text}>{labels.date}</AppText>
<View style={[styles.singleLabelContainer, { height: height / 2 }]}>
<AppText style={styles.textBold}>#</AppText>
</View>
<View style={[styles.singleLabelContainer, { height: height / 2 }]}>
<AppText style={styles.text}>{labels.date}</AppText>
</View>
</View>
)
}
@@ -27,8 +31,13 @@ const styles = StyleSheet.create({
justifyContent: 'flex-end',
width: CHART_YAXIS_WIDTH,
},
singleLabelContainer: {
justifyContent: 'space-around',
alignItems: 'center',
},
text: {
...Typography.label,
fontSize: Sizes.footnote,
},
textBold: {
...Typography.labelBold,
+44 -8
View File
@@ -13,7 +13,18 @@ import Tutorial from './Tutorial'
import YAxis from './y-axis'
import { getCycleDaysSortedByDate } from '../../db'
import { getChartFlag, setChartFlag } from '../../local-storage'
import {
getChartFlag,
setChartFlag,
desireTrackingCategoryObservable,
moodTrackingCategoryObservable,
noteTrackingCategoryObservable,
painTrackingCategoryObservable,
sexTrackingCategoryObservable,
temperatureTrackingCategoryObservable,
mucusTrackingCategoryObservable,
cervixTrackingCategoryObservable,
} from '../../local-storage'
import { makeColumnInfo } from '../helpers/chart'
import {
@@ -29,7 +40,7 @@ const getSymptomsFromCycleDays = (cycleDays) =>
SYMPTOMS.filter((symptom) => cycleDays.some((cycleDay) => cycleDay[symptom]))
const CycleChart = ({ navigate, setDate }) => {
const [shouldShowHint, setShouldShowHint] = useState(true)
const [shouldShowHint, setShouldShowHint] = useState(false)
useEffect(() => {
let isMounted = true
@@ -60,7 +71,31 @@ const CycleChart = ({ navigate, setDate }) => {
(symptom) => symptom !== 'temperature'
)
const shouldShowTemperatureColumn = chartSymptoms.indexOf('temperature') > -1
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') {
return painTrackingCategoryObservable.value ? symptom : null
} else if (symptom === 'mood') {
return moodTrackingCategoryObservable.value ? symptom : null
} else if (symptom === 'note') {
return noteTrackingCategoryObservable.value ? symptom : null
} else {
return symptom
}
})
const isTemperatureEnabled = temperatureTrackingCategoryObservable.value
const shouldShowTemperatureColumn =
isTemperatureEnabled && chartSymptoms.indexOf('temperature') > -1
const shouldShowNoDataWarning =
isTemperatureEnabled && chartSymptoms.indexOf('temperature') <= -1
const { width, height } = Dimensions.get('window')
const numberOfColumnsToRender = Math.round(width / CHART_COLUMN_WIDTH)
@@ -71,8 +106,9 @@ const CycleChart = ({ navigate, setDate }) => {
remainingHeight * CHART_SYMPTOM_HEIGHT_RATIO
)
const symptomRowHeight =
PixelRatio.roundToNearestPixel(symptomRowSymptoms.length * symptomHeight) +
CHART_GRID_LINE_HORIZONTAL_WIDTH
PixelRatio.roundToNearestPixel(
symptomRowEnabledSymptoms.length * symptomHeight
) + CHART_GRID_LINE_HORIZONTAL_WIDTH
const columnHeight = remainingHeight - symptomRowHeight
const chartHeight = shouldShowTemperatureColumn
@@ -89,7 +125,7 @@ const CycleChart = ({ navigate, setDate }) => {
navigate={navigate}
symptomHeight={symptomHeight}
columnHeight={columnHeight}
symptomRowSymptoms={symptomRowSymptoms}
symptomRowSymptoms={symptomRowEnabledSymptoms}
chartSymptoms={chartSymptoms}
shouldShowTemperatureColumn={shouldShowTemperatureColumn}
xAxisHeight={xAxisHeight}
@@ -110,11 +146,11 @@ const CycleChart = ({ navigate, setDate }) => {
>
<View style={styles.chartContainer}>
{shouldShowHint && <Tutorial onClose={hideHint} />}
{!shouldShowTemperatureColumn && <NoTemperature />}
{shouldShowNoDataWarning && <NoTemperature />}
<View style={styles.chartArea}>
<YAxis
height={columnHeight}
symptomsToDisplay={symptomRowSymptoms}
symptomsToDisplay={symptomRowEnabledSymptoms}
symptomsSectionHeight={symptomRowHeight}
shouldShowTemperatureColumn={shouldShowTemperatureColumn}
xAxisHeight={xAxisHeight}
+20 -7
View File
@@ -19,11 +19,20 @@ const CycleDayLabel = ({ height, date }) => {
return (
<View style={[styles.container, { height }]}>
<AppText style={styles.textBold}>{cycleDayLabel}</AppText>
<View style={styles.dateLabel}>
<AppText style={styles.text}>
{isFirstDayOfMonth ? momentDate.format('MMM') : dayOfMonth}
</AppText>
<View style={{ ...styles.labelRow, height: height / 2 }}>
<AppText style={styles.textBold}>{cycleDayLabel}</AppText>
</View>
<View style={{ ...styles.labelRow, height: height / 2 }}>
{isFirstDayOfMonth && (
<AppText style={styles.textFootnote}>
{momentDate.format('MMM')}
</AppText>
)}
{!isFirstDayOfMonth && (
<AppText style={styles.textSmall}>{dayOfMonth}</AppText>
)}
{!isFirstDayOfMonth && (
<AppText style={styles.textLight}>
{getOrdinalSuffix(dayOfMonth)}
@@ -45,17 +54,21 @@ const styles = StyleSheet.create({
justifyContent: 'flex-end',
left: 4,
},
text: {
textSmall: {
...Typography.label,
fontSize: Sizes.small,
},
textFootnote: {
...Typography.label,
fontSize: Sizes.footnote,
},
textBold: {
...Typography.labelBold,
},
textLight: {
...Typography.labelLight,
},
dateLabel: {
labelRow: {
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
+5
View File
@@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { TouchableOpacity } from 'react-native'
import moment from 'moment'
import { getCycleDay } from '../../db'
@@ -26,6 +27,8 @@ const DayColumn = ({
symptomRowSymptoms,
xAxisHeight,
}) => {
const momentDate = moment(dateString)
const isWeekend = momentDate.day() == 0 || momentDate.day() == 6
const cycleDayData = getCycleDay(dateString)
let data = {}
@@ -73,6 +76,7 @@ const DayColumn = ({
isVerticalLine={fhmAndLtl.drawFhmLine}
data={data && data.temperature}
columnHeight={columnHeight}
isWeekend={isWeekend}
/>
)}
@@ -92,6 +96,7 @@ const DayColumn = ({
isSymptomDataComplete={
hasSymptomData && isSymptomDataComplete(symptom, dateString)
}
isWeekend={isWeekend}
height={symptomHeight}
/>
)
+7 -6
View File
@@ -7,7 +7,8 @@ import { Colors } from '../../styles'
import {
CHART_COLUMN_WIDTH,
CHART_COLUMN_MIDDLE,
CHART_DOT_RADIUS,
CHART_DOT_RADIUS_SYMPTOM,
CHART_DOT_RADIUS_TEMPERATURE,
CHART_STROKE_WIDTH,
} from '../../config'
@@ -35,9 +36,9 @@ const DotAndLine = ({
}
const dot = new Path()
.moveTo(CHART_COLUMN_MIDDLE, y - CHART_DOT_RADIUS)
.arc(0, CHART_DOT_RADIUS * 2, CHART_DOT_RADIUS)
.arc(0, CHART_DOT_RADIUS * -2, CHART_DOT_RADIUS)
.moveTo(CHART_COLUMN_MIDDLE, y - CHART_DOT_RADIUS_TEMPERATURE)
.arc(0, CHART_DOT_RADIUS_TEMPERATURE * 2, CHART_DOT_RADIUS_TEMPERATURE)
.arc(0, CHART_DOT_RADIUS_TEMPERATURE * -2, CHART_DOT_RADIUS_TEMPERATURE)
const dotColor = exclude ? Colors.turquoise : Colors.turquoiseDark
const lineColorLeft = excludeLeftLine
? Colors.turquoise
@@ -58,13 +59,13 @@ const DotAndLine = ({
d={lineRight}
stroke={lineColorRight}
strokeWidth={CHART_STROKE_WIDTH}
key={y + CHART_DOT_RADIUS}
key={y + CHART_DOT_RADIUS_SYMPTOM}
/>
<Shape
d={dot}
stroke={dotColor}
strokeWidth={CHART_STROKE_WIDTH}
fill="white"
fill={dotColor}
key="dot"
/>
</React.Fragment>
+24 -7
View File
@@ -5,7 +5,7 @@ import { StyleSheet, View } from 'react-native'
import { Colors, Containers } from '../../styles'
import {
CHART_COLUMN_WIDTH,
CHART_DOT_RADIUS,
CHART_DOT_RADIUS_SYMPTOM,
CHART_GRID_LINE_HORIZONTAL_WIDTH,
} from '../../config'
@@ -15,14 +15,31 @@ const SymptomCell = ({
symptom,
symptomValue,
isSymptomDataComplete,
isWeekend,
}) => {
const shouldDrawDot = symptomValue !== false
// Determine the background color based on isWeekend prop
const backgroundColor = isWeekend ? Colors.greyVeryLight : 'white'
const styleCell =
index !== 0
? [styles.cell, { height, width: CHART_COLUMN_WIDTH }]
: [styles.cell, { height, width: CHART_COLUMN_WIDTH }, styles.topBorder]
? [
styles.cell,
{
height,
width: CHART_COLUMN_WIDTH,
backgroundColor: backgroundColor,
},
]
: [
styles.cell,
{
height,
width: CHART_COLUMN_WIDTH,
backgroundColor: backgroundColor,
},
styles.topBorder,
]
let styleDot
if (shouldDrawDot) {
const styleSymptom = Colors.iconColors[symptom]
const symptomColor = styleSymptom.shades[symptomValue]
@@ -47,11 +64,11 @@ SymptomCell.propTypes = {
symptom: PropTypes.string,
symptomValue: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
isSymptomDataComplete: PropTypes.bool,
isWeekend: PropTypes.bool,
}
const styles = StyleSheet.create({
cell: {
backgroundColor: 'white',
borderBottomColor: Colors.grey,
borderBottomWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
borderLeftColor: Colors.grey,
@@ -63,8 +80,8 @@ const styles = StyleSheet.create({
borderTopWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
},
dot: {
width: CHART_DOT_RADIUS * 2,
height: CHART_DOT_RADIUS * 2,
width: CHART_DOT_RADIUS_SYMPTOM * 2,
height: CHART_DOT_RADIUS_SYMPTOM * 2,
borderRadius: 50,
},
})
+5 -8
View File
@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { StyleSheet } from 'react-native'
import { Colors } from '../../styles'
import { Surface, Path } from '@react-native-community/art'
@@ -14,14 +14,16 @@ const TemperatureColumn = ({
isVerticalLine,
data,
columnHeight,
isWeekend,
}) => {
const x = CHART_STROKE_WIDTH / 2
const backgroundColor = isWeekend ? Colors.greyVeryLight : 'white'
return (
<Surface
width={CHART_COLUMN_WIDTH}
height={columnHeight}
style={styles.container}
style={{ backgroundColor: backgroundColor }}
>
<ChartLine path={new Path().lineTo(0, columnHeight)} />
@@ -63,12 +65,7 @@ TemperatureColumn.propTypes = {
isVerticalLine: PropTypes.bool,
data: PropTypes.object,
columnHeight: PropTypes.number,
isWeekend: PropTypes.bool,
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'white',
},
})
export default TemperatureColumn
+5 -2
View File
@@ -8,13 +8,15 @@ import { Sizes } from '../../styles'
import { CHART_TICK_WIDTH } from '../../config'
const Tick = ({ yPosition, height, isBold, shouldShowLabel, label }) => {
const top = yPosition - height / 2
const top = yPosition - height / 2 - 4
const containerStyle = [styles.container, { flexBasis: height, height, top }]
const textStyle = isBold ? styles.textBold : styles.textNormal
if (!shouldShowLabel) return null
return (
<View style={containerStyle}>
<AppText style={textStyle}>{shouldShowLabel && label}</AppText>
<AppText style={textStyle}>{label}</AppText>
</View>
)
}
@@ -36,6 +38,7 @@ const styles = StyleSheet.create({
position: 'absolute',
right: 0,
width: CHART_TICK_WIDTH,
minHeight: Sizes.base + 2,
},
textBold: {
fontSize: Sizes.base,
+17 -4
View File
@@ -1,18 +1,25 @@
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 { Containers } from '../../styles'
import { Colors, Containers, Spacing } from '../../styles'
const AppSwitch = ({ onToggle, text, value }) => {
const AppSwitch = ({ onToggle, text, value, disabled }) => {
const trackColor = { true: Colors.turquoiseDark }
return (
<View style={styles.container}>
<View style={styles.textContainer}>
<AppText>{text}</AppText>
</View>
<Switch onValueChange={onToggle} style={styles.switch} value={value} />
<Switch
onValueChange={onToggle}
style={styles.switch}
value={value}
trackColor={trackColor}
disabled={disabled}
/>
</View>
)
}
@@ -21,14 +28,20 @@ AppSwitch.propTypes = {
onToggle: PropTypes.func.isRequired,
text: PropTypes.string,
value: PropTypes.bool,
disabled: PropTypes.bool,
}
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,
+8 -1
View File
@@ -6,13 +6,14 @@ import AppText from './app-text'
import { Colors, Containers, Spacing, Typography } from '../../styles'
const Segment = ({ children, last, title }) => {
const Segment = ({ children, last, title, subheader }) => {
const containerStyle = last ? styles.containerLast : styles.container
const commonStyle = Object.assign({}, containerStyle)
return (
<View style={commonStyle}>
{title && <AppText style={styles.title}>{title}</AppText>}
{subheader && <AppText style={styles.subheader}>{subheader}</AppText>}
{children}
</View>
)
@@ -23,6 +24,7 @@ Segment.propTypes = {
last: PropTypes.bool,
style: PropTypes.object,
title: PropTypes.string,
subheader: PropTypes.string,
}
const styles = StyleSheet.create({
@@ -39,6 +41,11 @@ const styles = StyleSheet.create({
title: {
...Typography.subtitle,
},
subheader: {
...Typography.subtitle,
fontWeight: 'bold',
marginBottom: Spacing.zero,
},
})
export default Segment
@@ -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 (
<View style={styles.container}>
<View style={styles.iconContainer}>
<DripIcon
color={iconColor}
name={`drip-icon-${symptom}`}
size={Sizes.title}
/>
</View>
<View style={styles.textContainer}>
<AppText>{text}</AppText>
</View>
<Switch
onValueChange={onToggle}
style={styles.appSwitch}
value={value}
trackColor={trackColor}
/>
</View>
)
}
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
+34 -1
View File
@@ -9,6 +9,16 @@ import SymptomPageTitle from './symptom-page-title'
import { getCycleDay } from '../../db'
import { getData, nextDate, prevDate } from '../helpers/cycle-day'
import {
desireTrackingCategoryObservable,
moodTrackingCategoryObservable,
noteTrackingCategoryObservable,
painTrackingCategoryObservable,
sexTrackingCategoryObservable,
temperatureTrackingCategoryObservable,
mucusTrackingCategoryObservable,
cervixTrackingCategoryObservable,
} from '../../local-storage'
import { Spacing } from '../../styles'
import { SYMPTOMS } from '../../config'
@@ -27,6 +37,29 @@ const CycleDayOverView = ({ date, setDate, isTemperatureEditView }) => {
setDate(prevDate(date))
}
const allEnabledSymptoms = SYMPTOMS.map((symptom) => {
if (symptom === 'temperature') {
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') {
return painTrackingCategoryObservable.value ? symptom : null
} else if (symptom === 'mood') {
return moodTrackingCategoryObservable.value ? symptom : null
} else if (symptom === 'note') {
return noteTrackingCategoryObservable.value ? symptom : null
} else {
return symptom
}
})
const cleanSymptoms = allEnabledSymptoms.filter(Boolean)
return (
<AppPage>
<SymptomPageTitle
@@ -35,7 +68,7 @@ const CycleDayOverView = ({ date, setDate, isTemperatureEditView }) => {
onPrevCycleDay={showPrevCycleDay}
/>
<View style={styles.container}>
{SYMPTOMS.map((symptom) => {
{cleanSymptoms.map((symptom) => {
const symptomData =
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : null
+62 -6
View File
@@ -1,22 +1,61 @@
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 === labels.secondarySymptom.cervix ||
label === labels.secondarySymptom.mucus
) {
Alert.alert(
labels.secondarySymptom.disabled.title,
labels.secondarySymptom.disabled.message
)
}
}
export default function SelectTabGroup({ activeButton, buttons, onSelect }) {
return (
<View style={styles.container}>
{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 (
<TouchableOpacity
onPress={() => onSelect(value)}
onPress={() =>
!disabled ? onSelect(value) : showDisabledAlert(label)
}
key={i}
style={boxStyle}
>
@@ -32,6 +71,7 @@ SelectTabGroup.propTypes = {
activeButton: PropTypes.number,
buttons: PropTypes.array.isRequired,
onSelect: PropTypes.func.isRequired,
disabled: PropTypes.bool,
}
const styles = StyleSheet.create({
@@ -50,4 +90,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,
},
})
+1 -1
View File
@@ -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
+16 -5
View File
@@ -15,6 +15,7 @@ import Temperature from './temperature'
import { blank, save, shouldShow, symptomPage } from '../helpers/cycle-day'
import { showToast } from '../helpers/general'
import { fertilityTrackingObservable } from '../../local-storage'
import { shared as sharedLabels } from '../../i18n/en/labels'
import info from '../../i18n/en/symptom-info'
import { Colors, Containers, Sizes, Spacing } from '../../styles'
@@ -26,6 +27,8 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
const isBleeding = symptom === 'bleeding'
const getParsedData = () => JSON.parse(JSON.stringify(data))
const onPressLearnMore = () => setShouldShowInfo(!shouldShowInfo)
const isFertilityTrackingEnabled = fertilityTrackingObservable.value
const onEditNote = (note) => {
const parsedData = getParsedData()
@@ -102,8 +105,7 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
const onSelectTab = (group, value) => {
const parsedData = getParsedData()
Object.assign(parsedData, { [group.key]: value })
parsedData[group.key] = parsedData[group.key] !== value ? value : null
setData(parsedData)
}
const iconName = shouldShowInfo ? 'chevron-up' : 'chevron-down'
@@ -187,9 +189,18 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
</Segment>
)
})}
{!isBleeding && excludeToggle}
{/* show exclude AppSwitch for bleeding, mucus, cervix, temperature */}
{/* but if fertility is off only for bleeding */}
{shouldShow(symptomConfig.excludeText) &&
(symptom === 'bleeding' || isFertilityTrackingEnabled) && (
<Segment style={styles.segmentBorder}>
<AppSwitch
onToggle={onExcludeToggle}
text={symtomPage[symptom].excludeText}
value={data.exclude}
/>
</Segment>
)}
{shouldShow(symptomConfig.note) && (
<Segment style={styles.segmentBorder}>
<AppText>{symptomPage[symptom].note}</AppText>
+1
View File
@@ -108,6 +108,7 @@ const styles = StyleSheet.create({
hint: {
fontStyle: 'italic',
fontSize: Sizes.small,
color: Colors.orange,
},
hintContainer: {
marginVertical: Spacing.tiny,
+2
View File
@@ -2,6 +2,7 @@ import { LocalDate } from '@js-joda/core'
import { verticalScale } from 'react-native-size-matters'
import { Colors, Fonts, Sizes } from '../../styles'
import { periodPredictionObservable } from '../../local-storage'
const { shades } = Colors.iconColors.bleeding
@@ -26,6 +27,7 @@ export const toCalFormat = (bleedingDaysSortedByDate) => {
}
export const predictionToCalFormat = (predictedDays) => {
if (!periodPredictionObservable.value) return {}
if (!predictedDays.length) return {}
const todayDateString = LocalDate.now().toString()
const middleIndex = (predictedDays[0].length - 1) / 2
+7 -2
View File
@@ -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) {
+4
View File
@@ -35,6 +35,10 @@ export const formatTemperature = (temperature) =>
? temperature
: Number.parseFloat(temperature.toString()).toFixed(2)
//maximum of precision digits after decimal point, but no x.0
export const formatDecimal = (num, precision) =>
+parseFloat(Number.parseFloat(num).toFixed(precision))
export const getPreviousTemperature = (date) => {
const previousTemperature = getPreviousTemperatureForDate(date)
return formatTemperature(previousTemperature)
+5 -1
View File
@@ -37,13 +37,17 @@ export const pages = [
parent: 'SettingsMenu',
},
{
component: 'NfpSettings',
component: 'Customization',
parent: 'SettingsMenu',
},
{
component: 'DataManagement',
parent: 'SettingsMenu',
},
{
component: 'Info',
parent: 'SettingsMenu',
},
{
component: 'Password',
parent: 'SettingsMenu',
+50
View File
@@ -0,0 +1,50 @@
import React from 'react'
import { StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
import AppIcon from '../common/app-icon'
import AppPage from '../common/app-page'
import AppText from '../common/app-text'
import Segment from '../common/segment'
import { Colors, Spacing, Typography } from '../../styles'
import labels from '../../i18n/en/settings'
const Info = () => {
const { t } = useTranslation(null, { keyPrefix: 'hamburgerMenu.info' })
return (
<AppPage title={t('title')}>
<Segment last>
<View style={styles.line}>
<AppIcon
color={Colors.purple}
name="info-with-circle"
style={styles.icon}
/>
<AppText style={styles.title}>{labels.preOvu.title}</AppText>
</View>
<AppText>{labels.preOvu.note}</AppText>
</Segment>
</AppPage>
)
}
Info.propTypes = {
children: PropTypes.node,
}
export default Info
const styles = StyleSheet.create({
icon: {
marginRight: Spacing.base,
},
line: {
flexDirection: 'row',
alignItems: 'center',
},
title: {
...Typography.subtitle,
},
})
+9 -1
View File
@@ -1,18 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
import { StyleSheet } from 'react-native'
import AppPage from '../common/app-page'
import AppText from '../common/app-text'
import AppLink from '../common/AppLink'
import Segment from '../common/segment'
import { Spacing } from '../../styles'
const License = ({ children }) => {
const { t } = useTranslation(null, { keyPrefix: 'hamburgerMenu.license' })
const currentYear = new Date().getFullYear()
const link = 'https://www.gnu.org/licenses/gpl-3.0.html'
return (
<AppPage title={t('title')}>
<AppPage title={t('title')} contentContainerStyle={styles.contentContainer}>
<Segment last>
<AppText>{t('text', { currentYear })}</AppText>
<AppLink url={link}>{link}</AppLink>
@@ -26,4 +28,10 @@ License.propTypes = {
children: PropTypes.node,
}
const styles = StyleSheet.create({
contentContainer: {
marginTop: Spacing.large,
},
})
export default License
+351
View File
@@ -0,0 +1,351 @@
import React, { useEffect, useState } from 'react'
import { Alert, Pressable, StyleSheet, View } from 'react-native'
import { useTranslation } from 'react-i18next'
import AppIcon from '../../common/app-icon'
import AppPage from '../../common/app-page'
import AppSwitch from '../../common/app-switch'
import AppText from '../../common/app-text'
import { Colors, Spacing, Typography } from '../../../styles'
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,
saveUseCervixAsSecondarySymptom,
} from '../../../local-storage'
import labels from '../../../i18n/en/settings'
import { SYMPTOMS } from '../../../config'
const Settings = () => {
const { t } = useTranslation(null, { keyPrefix: 'symptoms' })
const [useCervixAsSecondarySymptom, setUseCervixAsSecondarySymptom] =
useState(useCervixAsSecondarySymptomObservable.value)
const [isPeriodPredictionEnabled, setPeriodPrediction] = useState(
periodPredictionObservable.value
)
const [isTemperatureTrackingCategoryEnabled, setTemperatureTrackingCategory] =
useState(temperatureTrackingCategoryObservable.value)
const [isMucusTrackingCategoryEnabled, setMucusTrackingCategory] = useState(
mucusTrackingCategoryObservable.value
)
const [isCervixTrackingCategoryEnabled, setCervixTrackingCategory] = useState(
cervixTrackingCategoryObservable.value
)
const [isSexTrackingCategoryEnabled, setSexTrackingCategory] = useState(
sexTrackingCategoryObservable.value
)
const [isDesireTrackingCategoryEnabled, setDesireTrackingCategory] = useState(
desireTrackingCategoryObservable.value
)
const [isPainTrackingCategoryEnabled, setPainTrackingCategory] = useState(
painTrackingCategoryObservable.value
)
const [isMoodTrackingCategoryEnabled, setMoodTrackingCategory] = useState(
moodTrackingCategoryObservable.value
)
const [isNoteTrackingCategoryEnabled, setNoteTrackingCategory] = useState(
noteTrackingCategoryObservable.value
)
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)
}
const painTrackingCategoryToggle = (value) => {
setPainTrackingCategory(value)
savePainTrackingCategory(value)
}
const moodTrackingCategoryToggle = (value) => {
setMoodTrackingCategory(value)
saveMoodTrackingCategory(value)
}
const noteTrackingCategoryToggle = (value) => {
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 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()
}
}
// 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.tempScale.disabled, labels.tempScale.disabledMessage)
}
}
const fertilityDisabledPrompt = () => {
if (!manageFertilityFeature) {
Alert.alert(
labels.fertilityTracking.disabledTitle,
labels.fertilityTracking.disabled
)
}
}
return (
<AppPage title={labels.customization.title}>
<Segment title={labels.customization.trackingCategories}>
<TrackingCategorySwitch
onToggle={temperatureTrackingCategoryToggle}
text={t(SYMPTOMS[1])}
value={isTemperatureTrackingCategoryEnabled}
symptom={SYMPTOMS[1]}
/>
<TrackingCategorySwitch
onToggle={(enabled) => {
mucusTrackingCategoryToggle(enabled)
}}
text={t(SYMPTOMS[2])}
value={isMucusTrackingCategoryEnabled}
symptom={SYMPTOMS[2]}
/>
<TrackingCategorySwitch
onToggle={(enabled) => {
cervixTrackingCategoryToggle(enabled)
}}
text={t(SYMPTOMS[3])}
value={isCervixTrackingCategoryEnabled}
symptom={SYMPTOMS[3]}
/>
<TrackingCategorySwitch
onToggle={sexTrackingCategoryToggle}
text={t(SYMPTOMS[4])}
value={isSexTrackingCategoryEnabled}
symptom={SYMPTOMS[4]}
/>
<TrackingCategorySwitch
onToggle={desireTrackingCategoryToggle}
text={t(SYMPTOMS[5])}
value={isDesireTrackingCategoryEnabled}
symptom={SYMPTOMS[5]}
/>
<TrackingCategorySwitch
onToggle={painTrackingCategoryToggle}
text={t(SYMPTOMS[6])}
value={isPainTrackingCategoryEnabled}
symptom={SYMPTOMS[6]}
/>
<TrackingCategorySwitch
onToggle={moodTrackingCategoryToggle}
text={t(SYMPTOMS[7])}
value={isMoodTrackingCategoryEnabled}
symptom={SYMPTOMS[7]}
/>
<TrackingCategorySwitch
onToggle={noteTrackingCategoryToggle}
text={t(SYMPTOMS[8])}
value={isNoteTrackingCategoryEnabled}
symptom={SYMPTOMS[8]}
/>
</Segment>
<Pressable onPress={fertilityDisabledPrompt}>
<Segment title={labels.fertilityTracking.title}>
<AppText>{labels.fertilityTracking.message}</AppText>
<AppSwitch
onToggle={fertilityTrackingToggle}
text={fertilityTrackingText}
value={isFertilityTrackingEnabled}
disabled={!manageFertilityFeature}
/>
</Segment>
</Pressable>
<Segment title={labels.periodPrediction.title}>
<AppSwitch
onToggle={onPeriodPredictionToggle}
text={periodPredictionText}
value={isPeriodPredictionEnabled}
/>
</Segment>
<Segment
subheader={labels.customization.subheaderSymptoThermalMethod}
last
></Segment>
<Pressable onPress={sliderDisabledPrompt}>
<Segment title={labels.tempScale.segmentTitle}>
<AppText>{labels.tempScale.segmentExplainer}</AppText>
<TemperatureSlider disabled={!isTemperatureTrackingCategoryEnabled} />
</Segment>
</Pressable>
<Pressable onPress={secondarySymptomDisabledPrompt}>
<Segment title={labels.secondarySymptom.title}>
<AppText>{cervixText}</AppText>
<SelectTabGroup
activeButton={useCervixAsSecondarySymptom}
buttons={secondarySymptomButtons}
onSelect={(value) => onSelectTab(value)}
disabled={!isFertilityTrackingEnabled}
/>
</Segment>
</Pressable>
<Segment last>
<View style={styles.line}>
<AppIcon
color={Colors.purple}
name="info-with-circle"
style={styles.icon}
/>
<AppText style={styles.title}>{labels.preOvu.title}</AppText>
</View>
<AppText>{labels.preOvu.note}</AppText>
</Segment>
</AppPage>
)
}
export default Settings
const styles = StyleSheet.create({
icon: {
marginRight: Spacing.base,
},
line: {
flexDirection: 'row',
alignItems: 'center',
},
title: {
...Typography.subtitle,
},
})
@@ -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 (
<View style={styles.container}>
<Slider
@@ -35,11 +44,13 @@ const TemperatureSlider = () => {
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}
/>
</View>
)
@@ -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,
},
})
@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Alert } from 'react-native'
import { Alert, Platform } from 'react-native'
import DocumentPicker from 'react-native-document-picker'
import rnfs from 'react-native-fs'
import importCsv from '../../../lib/import-export/import-from-csv'
@@ -59,7 +59,8 @@ export default function ImportData({ resetIsDeletingData, setIsLoading }) {
function openImportDialog() {
resetIsDeletingData()
Alert.alert(t('dialog.title'), t('dialog.message'), [
let buttons = [
{
text: t('dialog.cancel'),
style: 'cancel',
@@ -73,7 +74,13 @@ export default function ImportData({ resetIsDeletingData, setIsLoading }) {
text: t('dialog.delete'),
onPress: () => startImport(true),
},
])
]
if (Platform.OS === 'android') {
buttons = [buttons[0], buttons[2], buttons[1]]
}
Alert.alert(t('dialog.title'), t('dialog.message'), buttons)
}
function showImportErrorAlert(message) {
+4 -2
View File
@@ -1,6 +1,7 @@
import Reminders from './reminders/reminders'
import NfpSettings from './nfp-settings'
import Customization from './customization'
import DataManagement from './data-management/DataManagement'
import Info from './Info'
import Password from './password'
import About from './About'
import License from './License'
@@ -8,8 +9,9 @@ import PrivacyPolicy from './privacy-policy'
export default {
Reminders,
NfpSettings,
Customization,
DataManagement,
Info,
Password,
About,
License,
+13 -2
View File
@@ -20,11 +20,15 @@ const MenuItem = ({ item, last, navigate }) => {
key={item.label}
onPress={() => navigate(item.componentName)}
>
<View>
<View style={styles.textContainer}>
<AppText style={styles.title}>{t(`${item.label}.name`)}</AppText>
{!!item.label && <AppText>{t(`${item.label}.text`)}</AppText>}
</View>
<AppIcon name="chevron-right" color={Colors.orange} />
<AppIcon
style={styles.chevronContainer}
name="chevron-right"
color={Colors.orange}
/>
</TouchableOpacity>
</Segment>
)
@@ -44,6 +48,13 @@ const styles = StyleSheet.create({
color: Colors.purple,
fontSize: Sizes.subtitle,
},
textContainer: {
flex: 5,
},
chevronContainer: {
textAlign: 'right',
flex: 1,
},
})
export default MenuItem
-73
View File
@@ -1,73 +0,0 @@
import React, { useState } from 'react'
import { Platform, StyleSheet, View } from 'react-native'
import AppIcon from '../../common/app-icon'
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 { useCervixObservable, saveUseCervix } from '../../../local-storage'
import { Colors, Spacing, Typography } from '../../../styles'
import labels from '../../../i18n/en/settings'
const Settings = () => {
const [shouldUseCervix, setShouldUseCervix] = useState(
useCervixObservable.value
)
const onCervixToggle = (value) => {
setShouldUseCervix(value)
saveUseCervix(value)
}
const cervixText = shouldUseCervix
? labels.useCervix.cervixModeOn
: labels.useCervix.cervixModeOff
return (
<AppPage>
<Segment title={labels.useCervix.title}>
<AppSwitch
onToggle={onCervixToggle}
text={cervixText}
value={shouldUseCervix}
/>
</Segment>
{/* for iOS disabled temporarily, TODO https://gitlab.com/bloodyhealth/drip/-/issues/545 */}
{Platform.OS !== 'ios' && (
<Segment title={labels.tempScale.segmentTitle}>
<AppText>{labels.tempScale.segmentExplainer}</AppText>
<TemperatureSlider />
</Segment>
)}
<Segment last>
<View style={styles.line}>
<AppIcon
color={Colors.purple}
name="info-with-circle"
style={styles.icon}
/>
<AppText style={styles.title}>{labels.preOvu.title}</AppText>
</View>
<AppText>{labels.preOvu.note}</AppText>
</Segment>
</AppPage>
)
}
export default Settings
const styles = StyleSheet.create({
icon: {
marginRight: Spacing.base,
},
line: {
flexDirection: 'row',
alignItems: 'center',
},
title: {
...Typography.subtitle,
},
})
+37 -10
View File
@@ -8,11 +8,16 @@ import TemperatureReminder from './temperature-reminder'
import {
periodReminderObservable,
savePeriodReminder,
periodPredictionObservable,
temperatureTrackingCategoryObservable,
} from '../../../local-storage'
import labels from '../../../i18n/en/settings'
import { Alert, Pressable } from 'react-native'
const Reminders = () => {
const isPeriodPredictionDisabled = !periodPredictionObservable.value
const [isPeriodReminderEnabled, setIsPeriodReminderEnabled] = useState(
periodReminderObservable.value.enabled
)
@@ -21,18 +26,40 @@ const Reminders = () => {
savePeriodReminder({ enabled: isEnabled })
}
const reminderDisabledPrompt = () => {
if (!periodPredictionObservable.value) {
Alert.alert(
labels.periodReminder.alertNoPeriodReminder.title,
labels.periodReminder.alertNoPeriodReminder.message
)
}
}
const tempReminderDisabledPrompt = () => {
if (!temperatureTrackingCategoryObservable.value) {
Alert.alert(
labels.tempReminder.alertNoTempReminder.title,
labels.tempReminder.alertNoTempReminder.message
)
}
}
return (
<AppPage>
<Segment title={labels.tempReminder.title}>
<TemperatureReminder />
</Segment>
<Segment title={labels.periodReminder.title} last>
<AppSwitch
onToggle={periodReminderToggle}
text={labels.periodReminder.reminderText}
value={isPeriodReminderEnabled}
/>
</Segment>
<Pressable onPress={reminderDisabledPrompt}>
<Segment title={labels.periodReminder.title}>
<AppSwitch
onToggle={periodReminderToggle}
text={labels.periodReminder.reminderText}
value={isPeriodReminderEnabled}
disabled={isPeriodPredictionDisabled}
/>
</Segment>
</Pressable>
<Pressable onPress={tempReminderDisabledPrompt}>
<Segment title={labels.tempReminder.title} last>
<TemperatureReminder />
</Segment>
</Pressable>
</AppPage>
)
}
@@ -7,6 +7,7 @@ import AppSwitch from '../../common/app-switch'
import {
saveTempReminder,
tempReminderObservable,
temperatureTrackingCategoryObservable,
} from '../../../local-storage'
import padWithZeros from '../../helpers/pad-time-with-zeros'
@@ -51,6 +52,7 @@ const TemperatureReminder = () => {
onToggle={temperatureReminderToggle}
text={tempReminderText}
value={isEnabled}
disabled={!temperatureTrackingCategoryObservable.value}
/>
<DateTimePicker
isVisible={isTimePickerVisible}
+1 -1
View File
@@ -7,8 +7,8 @@ import MenuItem from './menu-item'
import { useTranslation } from 'react-i18next'
const menuItems = [
{ label: 'customization', componentName: 'Customization' },
{ label: 'reminders', componentName: 'Reminders' },
{ label: 'nfpSettings', componentName: 'NfpSettings' },
{ label: 'dataManagement', componentName: 'DataManagement' },
{ label: 'password', componentName: 'Password' },
]
+4 -2
View File
@@ -3,7 +3,7 @@ import { StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
import AppText from '../common/app-text'
import { formatDecimal } from '../helpers/cycle-day'
import { Sizes, Spacing, Typography } from '../../styles'
const StatsOverview = ({ data }) => {
@@ -16,7 +16,9 @@ StatsOverview.propTypes = {
const Row = ({ rowContent }) => {
const isStandardDeviation = rowContent[1].includes('deviation')
if (isStandardDeviation && rowContent[0] !== '—') {
rowContent[0] = formatDecimal(rowContent[0], 1)
}
return (
<View style={styles.row}>
<Cell content={rowContent[0]} isLeft />
+6
View File
@@ -11,6 +11,7 @@ import PeriodDetailsModal from './PeriodDetailsModal'
import cycleModule from '../../lib/cycle'
import { getCycleLengthStats as getCycleInfo } from '../../lib/cycle-length'
import { formatDecimal } from '../helpers/cycle-day'
import { Containers, Sizes, Spacing, Typography } from '../../styles'
@@ -27,6 +28,7 @@ const Stats = () => {
numberOfCycles > 0
? getCycleInfo(cycleLengths)
: { minimum: '—', maximum: '—', stdDeviation: '—' }
const standardDeviation = cycleData.stdDeviation
? cycleData.stdDeviation
: '—'
@@ -37,6 +39,10 @@ const Stats = () => {
[numberOfCycles, t('overview.completedCycles')],
]
if (cycleData.mean) {
cycleData.mean = formatDecimal(cycleData.mean, 1)
}
return (
<SafeAreaView style={styles.pageContainer}>
<ScrollView contentContainerStyle={styles.overviewContainer}>