Merge branch 'main' into 'feature/prods'
# Conflicts: # components/cycle-day/symptom-edit-view.js # i18n/en/cycle-day.js
This commit is contained in:
+22
-5
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -108,6 +108,7 @@ const styles = StyleSheet.create({
|
||||
hint: {
|
||||
fontStyle: 'italic',
|
||||
fontSize: Sizes.small,
|
||||
color: Colors.orange,
|
||||
},
|
||||
hintContainer: {
|
||||
marginVertical: Spacing.tiny,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
+25
-3
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
@@ -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}
|
||||
|
||||
@@ -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' },
|
||||
]
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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}>
|
||||
|
||||
Reference in New Issue
Block a user