Compare commits

...

8 Commits

Author SHA1 Message Date
Sofiya Tepikin 9f3d115aca Use the date from context 2022-09-24 12:44:28 +02:00
Sofiya Tepikin 2f187b1007 Add date context and provide it to the app 2022-09-24 12:44:16 +02:00
Maria Zadnepryanets d78fbf74e3 Merge branch 'chore/add-translations-to-stats' into 'main'
Chore: add translations to stats

See merge request bloodyhealth/drip!545
2022-09-20 17:50:05 +00:00
Maria Zadnepryanets c9430439c5 Chore: add translations to stats 2022-09-20 17:50:05 +00:00
Maria Zadnepryanets 08712f460e Merge branch 'chore/add-footnote-component' into 'main'
Chore: add footnote component

See merge request bloodyhealth/drip!544
2022-09-20 16:43:21 +00:00
Maria Zadnepryanets 3447a0ea1e Chore: add footnote component 2022-09-20 16:43:20 +00:00
Sofiya Tepikin b4d92d0d7b Merge branch 'fix/chart-warning' into 'main'
Fix useEffect callback cannot be async

See merge request bloodyhealth/drip!540
2022-09-20 15:32:47 +00:00
Sofiya Tepikin d5f0e3532a Fix useEffect callback cannot be async 2022-09-20 15:32:47 +00:00
16 changed files with 246 additions and 71 deletions
+6 -30
View File
@@ -4,7 +4,9 @@ import PropTypes from 'prop-types'
import moment from 'moment'
import AppText from './common/app-text'
import Asterisk from './common/Asterisk'
import Button from './common/button'
import Footnote from './common/Footnote'
import cycleModule from '../lib/cycle'
import { getFertilityStatusForDay } from '../lib/sympto-adapter'
@@ -16,9 +18,11 @@ import {
import { Colors, Fonts, Sizes, Spacing } from '../styles'
import { LocalDate } from '@js-joda/core'
import { useTranslation } from 'react-i18next'
import { useDate } from '../hooks/useDate'
const Home = ({ navigate, setDate }) => {
const Home = ({ navigate }) => {
const { t } = useTranslation()
const { setDate } = useDate()
function navigateToCycleDayView() {
setDate(todayDateString)
@@ -69,26 +73,12 @@ const Home = ({ navigate, setDate }) => {
<Button isCTA isSmall={false} onPress={navigateToCycleDayView}>
{t('labels.home.addDataForToday')}
</Button>
{phase && (
<View style={styles.asteriskLine}>
<Asterisk />
<AppText linkStyle={styles.whiteText} style={styles.greyText}>
{statusText}
</AppText>
</View>
)}
{phase && <Footnote>{statusText}</Footnote>}
</ScrollView>
)
}
const Asterisk = () => {
return <AppText style={styles.asterisk}>*</AppText>
}
const styles = StyleSheet.create({
asterisk: {
color: Colors.orange,
},
container: {
backgroundColor: Colors.purple,
flex: 1,
@@ -104,12 +94,6 @@ const styles = StyleSheet.create({
marginBottom: Spacing.tiny,
marginTop: Spacing.small,
},
asteriskLine: {
flexDirection: 'row',
alignContent: 'flex-start',
marginBottom: Spacing.tiny,
marginTop: Spacing.small,
},
title: {
color: Colors.purpleLight,
fontFamily: Fonts.bold,
@@ -124,18 +108,10 @@ const styles = StyleSheet.create({
color: 'white',
fontSize: Sizes.subtitle,
},
whiteText: {
color: 'white',
},
greyText: {
color: Colors.greyLight,
paddingLeft: Spacing.base,
},
})
Home.propTypes = {
navigate: PropTypes.func,
setDate: PropTypes.func,
}
export default Home
+5 -1
View File
@@ -10,6 +10,8 @@ import AppStatusBar from './common/app-status-bar'
import AcceptLicense from './AcceptLicense'
import PasswordPrompt from './password-prompt'
import { DateProvider } from '../hooks/useDate'
export default function AppWrapper() {
const [isLoading, setIsLoading] = useState(true)
const [isLicenseAccepted, setIsLicenseAccepted] = useState(false)
@@ -47,7 +49,9 @@ export default function AppWrapper() {
{isDbEncrypted ? (
<PasswordPrompt enableShowApp={() => setIsDbEncrypted(false)} />
) : (
<App restartApp={() => checkIsDbEncrypted()} />
<DateProvider>
<App restartApp={() => checkIsDbEncrypted()} />
</DateProvider>
)}
</>
)
+3 -4
View File
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'
import { BackHandler, StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
import { LocalDate } from '@js-joda/core'
import { useDate } from '../hooks/useDate'
import Header from './header'
import Menu from './menu'
@@ -13,7 +13,8 @@ import setupNotifications from '../lib/notifications'
import { closeDb } from '../db'
const App = ({ restartApp }) => {
const [date, setDate] = useState(LocalDate.now().toString())
const { setDate } = useDate()
const [currentPage, setCurrentPage] = useState('Home')
const goBack = () => {
if (currentPage === 'Home') {
@@ -43,8 +44,6 @@ const App = ({ restartApp }) => {
const isTemperatureEditView = currentPage === 'TemperatureEditView'
const headerProps = { navigate: setCurrentPage }
const pageProps = {
date,
setDate,
isTemperatureEditView,
navigate: setCurrentPage,
}
+4 -2
View File
@@ -3,6 +3,8 @@ import PropTypes from 'prop-types'
import { StyleSheet, View } from 'react-native'
import { CalendarList } from 'react-native-calendars'
import { useDate } from '../hooks/useDate'
import { getBleedingDaysSortedByDate } from '../db'
import cycleModule from '../lib/cycle'
import {
@@ -12,7 +14,8 @@ import {
todayToCalFormat,
} from './helpers/calendar'
const CalendarView = ({ setDate, navigate }) => {
const CalendarView = ({ navigate }) => {
const { setDate } = useDate()
const bleedingDays = getBleedingDaysSortedByDate()
const predictedMenses = cycleModule().getPredictedMenses()
@@ -49,7 +52,6 @@ const styles = StyleSheet.create({
})
CalendarView.propTypes = {
setDate: PropTypes.func.isRequired,
navigate: PropTypes.func.isRequired,
}
+16 -6
View File
@@ -28,12 +28,24 @@ import { Spacing } from '../../styles'
const getSymptomsFromCycleDays = (cycleDays) =>
SYMPTOMS.filter((symptom) => cycleDays.some((cycleDay) => cycleDay[symptom]))
const CycleChart = ({ navigate, setDate }) => {
const CycleChart = ({ navigate }) => {
const [shouldShowHint, setShouldShowHint] = useState(true)
useEffect(async () => {
const flag = await getChartFlag()
setShouldShowHint(flag === 'true')
useEffect(() => {
let isMounted = true
async function checkShouldShowHint() {
const flag = await getChartFlag()
if (isMounted) {
setShouldShowHint(flag === 'true')
}
}
checkShouldShowHint()
return () => {
isMounted = false
}
}, [])
const hideHint = () => {
@@ -72,7 +84,6 @@ const CycleChart = ({ navigate, setDate }) => {
const renderColumn = ({ item }) => {
return (
<DayColumn
setDate={setDate}
dateString={item}
navigate={navigate}
symptomHeight={symptomHeight}
@@ -124,7 +135,6 @@ const CycleChart = ({ navigate, setDate }) => {
CycleChart.propTypes = {
navigate: PropTypes.func,
setDate: PropTypes.func,
}
const styles = StyleSheet.create({
+3 -2
View File
@@ -8,6 +8,8 @@ import SymptomCell from './symptom-cell'
import TemperatureColumn from './temperature-column'
import CycleDayLabel from './cycle-day-label'
import { useDate } from '../../hooks/useDate'
import {
symptomColorMethods,
getTemperatureProps,
@@ -19,13 +21,13 @@ const DayColumn = ({
dateString,
chartSymptoms,
columnHeight,
setDate,
navigate,
shouldShowTemperatureColumn,
symptomHeight,
symptomRowSymptoms,
xAxisHeight,
}) => {
const { setDate } = useDate()
const cycleDayData = getCycleDay(dateString)
let data = {}
@@ -105,7 +107,6 @@ DayColumn.propTypes = {
chartSymptoms: PropTypes.array,
columnHeight: PropTypes.number.isRequired,
navigate: PropTypes.func.isRequired,
setDate: PropTypes.func.isRequired,
shouldShowTemperatureColumn: PropTypes.bool,
symptomHeight: PropTypes.number.isRequired,
symptomRowSymptoms: PropTypes.array,
+16
View File
@@ -0,0 +1,16 @@
import React from 'react'
import { StyleSheet } from 'react-native'
import AppText from './app-text'
import { Colors } from '../../styles'
const Asterisk = () => <AppText style={styles.asterisk}>*</AppText>
const styles = StyleSheet.create({
asterisk: {
color: Colors.orange,
},
})
export default Asterisk
+43
View File
@@ -0,0 +1,43 @@
import React from 'react'
import PropTypes from 'prop-types'
import { StyleSheet, View } from 'react-native'
import AppText from '../common/app-text'
import Asterisk from '../common/Asterisk'
import { Colors, Spacing } from '../../styles'
const Footnote = ({ children }) => {
if (!children) return false
return (
<View style={styles.container}>
<Asterisk />
<AppText linkStyle={styles.link} style={styles.text}>
{children}
</AppText>
</View>
)
}
Footnote.propTypes = {
children: PropTypes.node,
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignContent: 'flex-start',
marginBottom: Spacing.tiny,
marginTop: Spacing.small,
},
link: {
color: 'white',
},
text: {
color: Colors.greyLight,
paddingLeft: Spacing.base,
},
})
export default Footnote
+4 -3
View File
@@ -9,10 +9,13 @@ import SymptomPageTitle from './symptom-page-title'
import { getCycleDay } from '../../db'
import { getData, nextDate, prevDate } from '../helpers/cycle-day'
import { useDate } from '../../hooks/useDate'
import { Spacing } from '../../styles'
import { SYMPTOMS } from '../../config'
const CycleDayOverView = ({ date, setDate, isTemperatureEditView }) => {
const CycleDayOverView = ({ isTemperatureEditView }) => {
const { date, setDate } = useDate()
const cycleDay = getCycleDay(date)
const [editedSymptom, setEditedSymptom] = useState(
@@ -58,8 +61,6 @@ const CycleDayOverView = ({ date, setDate, isTemperatureEditView }) => {
CycleDayOverView.propTypes = {
cycleDay: PropTypes.object,
date: PropTypes.string,
setDate: PropTypes.func,
isTemperatureEditView: PropTypes.bool,
}
@@ -2,7 +2,7 @@ import React from 'react'
import { StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
import AppText from './app-text'
import AppText from '../common/app-text'
import { Sizes, Spacing, Typography } from '../../styles'
@@ -3,7 +3,7 @@ import { FlatList, StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
import AppText from './app-text'
import AppText from '../common/app-text'
import cycleModule from '../../lib/cycle'
import { Spacing, Typography, Colors } from '../../styles'
@@ -1,39 +1,45 @@
import React from 'react'
import { ImageBackground, View } from 'react-native'
import { ScaledSheet } from 'react-native-size-matters'
import { useTranslation } from 'react-i18next'
import AppText from './common/app-text'
import StatsOverview from './common/StatsOverview'
import StatsTable from './common/StatsTable'
import AppText from '../common/app-text'
import StatsOverview from './StatsOverview'
import StatsTable from './StatsTable'
import cycleModule from '../lib/cycle'
import { getCycleLengthStats as getCycleInfo } from '../lib/cycle-length'
import { stats as labels } from '../i18n/en/labels'
import cycleModule from '../../lib/cycle'
import { getCycleLengthStats as getCycleInfo } from '../../lib/cycle-length'
import { Containers, Sizes, Spacing, Typography } from '../styles'
import { Containers, Sizes, Spacing, Typography } from '../../styles'
const image = require('../assets/cycle-icon.png')
const image = require('../../assets/cycle-icon.png')
const Stats = () => {
const { t } = useTranslation(null, { keyPrefix: 'stats' })
const cycleLengths = cycleModule().getAllCycleLengths()
const numberOfCycles = cycleLengths.length
const hasAtLeastOneCycle = numberOfCycles >= 1
const cycleData = hasAtLeastOneCycle
? getCycleInfo(cycleLengths)
: { minimum: '—', maximum: '—', stdDeviation: '—' }
const cycleData =
numberOfCycles > 0
? getCycleInfo(cycleLengths)
: { minimum: '—', maximum: '—', stdDeviation: '—' }
const standardDeviation = cycleData.stdDeviation
? cycleData.stdDeviation
: '—'
const statsData = [
[cycleData.minimum, labels.minLabel],
[cycleData.maximum, labels.maxLabel],
[cycleData.stdDeviation ? cycleData.stdDeviation : '—', labels.stdLabel],
[numberOfCycles, labels.basisOfStatsEnd],
[cycleData.minimum, t('overview.min')],
[cycleData.maximum, t('overview.max')],
[standardDeviation, t('overview.standardDeviation')],
[numberOfCycles, t('overview.completedCycles')],
]
return (
<View style={styles.pageContainer}>
<View style={styles.overviewContainer}>
<AppText>{labels.cycleLengthExplainer}</AppText>
{!hasAtLeastOneCycle && <AppText>{labels.emptyStats}</AppText>}
{hasAtLeastOneCycle && (
<AppText>{t('intro')}</AppText>
{numberOfCycles === 0 ? (
<AppText>{t('noData')}</AppText>
) : (
<View style={styles.container}>
<View style={styles.columnLeft}>
<ImageBackground
@@ -49,11 +55,11 @@ const Stats = () => {
{cycleData.mean}
</AppText>
<AppText style={styles.accentPurpleHuge}>
{labels.daysLabel}
{t('overview.days')}
</AppText>
</ImageBackground>
<AppText style={styles.accentOrange}>
{labels.averageLabel}
{t('overview.average')}
</AppText>
</View>
<View style={styles.columnRight}>
+24
View File
@@ -0,0 +1,24 @@
import React, { createContext, useContext, useState } from 'react'
import PropTypes from 'prop-types'
import { LocalDate } from '@js-joda/core'
const DateContext = createContext()
export const DateProvider = ({ children }) => {
const [date, setDate] = useState(LocalDate.now().toString())
return (
<DateContext.Provider value={{ date, setDate }}>
{children}
</DateContext.Provider>
)
}
DateProvider.propTypes = {
children: PropTypes.node,
}
export const useDate = () => {
const { date, setDate } = useContext(DateContext)
return { date, setDate }
}
+19
View File
@@ -61,6 +61,25 @@
}
}
},
"stats": {
"noData": "At least one completed cycle is needed to display stats.",
"intro": "Basic statistics about the length of your cycles.",
"overview": {
"average": "Average cycle",
"days": "days",
"min": "Shortest",
"max": "Longest",
"standardDeviation": "Standard\ndeviation",
"completedCycles": "completed\ncycles"
},
"showStats": "Show period details",
"details": {
"cycleStart": "Cycle start",
"cycleLength": "Cycle length",
"bleedingDays": "Bleeding"
},
"footnote": "Based on the standard deviation of all your tracked periods drip. calculates a range for the starting day of the upcoming 3 periods. The range will be 3 days if your standard deviation is smaller than 1.5 and 5 days if the value is bigger.\n\nThe standard deviation tells you how much the length of your periods vary, 0 means all your periods are exactly the same length and the bigger the value the more the period length varies."
},
"plurals": {
"day": "{{count}} day",
"day_plural": "{{count}} days"
+19
View File
@@ -0,0 +1,19 @@
import React from 'react'
import { render } from '@testing-library/react-native'
import Footnote from '../../../components/common/Footnote'
describe('Footnote component', () => {
test('when children are present, renders them', () => {
const text = 'Some footnote text'
const { toJSON } = render(<Footnote>{text}</Footnote>)
expect(toJSON()).toMatchSnapshot()
})
test('when no children, renders nothing', () => {
const { toJSON } = render(<Footnote></Footnote>)
expect(toJSON()).toMatchSnapshot()
})
})
@@ -0,0 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Footnote component when children are present, renders them 1`] = `
<View
style={
Object {
"alignContent": "flex-start",
"flexDirection": "row",
"marginBottom": 8.571428571428571,
"marginTop": 21.428571428571427,
}
}
>
<Text
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#F38337",
},
]
}
>
*
</Text>
<Text
linkStyle={
Object {
"color": "white",
}
}
style={
Array [
Object {
"color": "#555",
"fontFamily": "Jost-Book",
"fontSize": 34.285714285714285,
},
Object {
"color": "#CCC",
"paddingLeft": 34.285714285714285,
},
]
}
>
Some footnote text
</Text>
</View>
`;
exports[`Footnote component when no children, renders nothing 1`] = `null`;