diff --git a/components/common/table.js b/components/common/StatsOverview.js
similarity index 77%
rename from components/common/table.js
rename to components/common/StatsOverview.js
index 5066c64..1e07953 100644
--- a/components/common/table.js
+++ b/components/common/StatsOverview.js
@@ -6,14 +6,12 @@ import AppText from './app-text'
import { Sizes, Spacing, Typography } from '../../styles'
-const Table = ({ tableContent }) => {
- return tableContent.map((rowContent, i) => (
-
- ))
+const StatsOverview = ({ data }) => {
+ return data.map((rowContent, i) =>
)
}
-Table.propTypes = {
- tableContent: PropTypes.array.isRequired,
+StatsOverview.propTypes = {
+ data: PropTypes.array.isRequired,
}
const Row = ({ rowContent }) => {
@@ -65,18 +63,11 @@ const styles = StyleSheet.create({
},
cellLeft: {
alignItems: 'flex-end',
- flex: 5,
+ flex: 3,
justifyContent: 'center',
},
- cellRight: {
- flex: 5,
- justifyContent: 'center',
- },
- row: {
- flexDirection: 'row',
- marginBottom: Spacing.tiny,
- marginLeft: Spacing.tiny,
- },
+ cellRight: { flex: 5 },
+ row: { flexDirection: 'row' },
})
-export default Table
+export default StatsOverview
diff --git a/components/common/StatsTable.js b/components/common/StatsTable.js
new file mode 100644
index 0000000..c628bcc
--- /dev/null
+++ b/components/common/StatsTable.js
@@ -0,0 +1,107 @@
+import React from 'react'
+import { FlatList, StyleSheet, View } from 'react-native'
+import PropTypes from 'prop-types'
+import { useTranslation } from 'react-i18next'
+
+import AppText from './app-text'
+
+import cycleModule from '../../lib/cycle'
+import { Spacing, Typography, Colors } from '../../styles'
+import { humanizeDate } from '../helpers/format-date'
+
+const Item = ({ data }) => {
+ const { t } = useTranslation(null, { keyPrefix: 'plurals' })
+
+ if (!data) return false
+
+ const { date, cycleLength, bleedingLength } = data
+
+ return (
+
+
+ {humanizeDate(date)}
+
+
+ {t('day', { count: cycleLength })}
+
+
+ {t('day', { count: bleedingLength })}
+
+
+ )
+}
+
+Item.propTypes = {
+ data: PropTypes.object.isRequired,
+}
+
+const StatsTable = () => {
+ const renderItem = ({ item }) =>
+ const data = cycleModule().getStats()
+
+ if (!data || data.length === 0) return false
+
+ return (
+ item.date}
+ ItemSeparatorComponent={ItemDivider}
+ ListHeaderComponent={FlatListHeader}
+ ListHeaderComponentStyle={styles.headerDivider}
+ stickyHeaderIndices={[0]}
+ contentContainerStyle={styles.container}
+ />
+ )
+}
+
+const ItemDivider = () =>
+
+const FlatListHeader = () => (
+
+
+ {'Cycle Start'}
+
+
+ {'Cycle Length'}
+
+
+ {'Bleeding'}
+
+
+)
+
+const styles = StyleSheet.create({
+ divider: {
+ height: 1,
+ width: '100%',
+ backgroundColor: Colors.grey,
+ },
+ header: {
+ ...Typography.accentOrange,
+ paddingVertical: Spacing.small,
+ },
+ headerDivider: {
+ borderBottomColor: Colors.purple,
+ borderBottomWidth: 2,
+ },
+ row: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ paddingVertical: Spacing.tiny,
+ backgroundColor: Colors.turquoiseLight,
+ },
+ cell: {
+ flex: 2,
+ justifyContent: 'center',
+ },
+ accentCell: {
+ flex: 3,
+ justifyContent: 'center',
+ },
+ container: {
+ paddingHorizontal: Spacing.base,
+ },
+})
+
+export default StatsTable
diff --git a/components/common/app-page.js b/components/common/app-page.js
index 74ced3a..784fd66 100644
--- a/components/common/app-page.js
+++ b/components/common/app-page.js
@@ -4,7 +4,7 @@ import { ScrollView, StyleSheet, View } from 'react-native'
import AppText from '../common/app-text'
-import { Colors, Typography } from '../../styles'
+import { Colors, Containers, Typography } from '../../styles'
const AppPage = ({
children,
@@ -35,10 +35,7 @@ AppPage.propTypes = {
}
const styles = StyleSheet.create({
- container: {
- backgroundColor: Colors.turquoiseLight,
- flex: 1,
- },
+ container: { ...Containers.pageContainer },
scrollView: {
backgroundColor: Colors.turquoiseLight,
flexGrow: 1,
diff --git a/components/common/segment.js b/components/common/segment.js
index d17f1b7..0069666 100644
--- a/components/common/segment.js
+++ b/components/common/segment.js
@@ -4,7 +4,7 @@ import { StyleSheet, View } from 'react-native'
import AppText from './app-text'
-import { Colors, Spacing, Typography } from '../../styles'
+import { Colors, Containers, Spacing, Typography } from '../../styles'
const Segment = ({ children, last, title }) => {
const containerStyle = last ? styles.containerLast : styles.container
@@ -25,21 +25,16 @@ Segment.propTypes = {
title: PropTypes.string,
}
-const segmentContainer = {
- marginHorizontal: Spacing.base,
- marginBottom: Spacing.base,
-}
-
const styles = StyleSheet.create({
container: {
borderStyle: 'solid',
borderBottomWidth: 1,
borderBottomColor: Colors.greyLight,
paddingBottom: Spacing.base,
- ...segmentContainer,
+ ...Containers.segmentContainer,
},
containerLast: {
- ...segmentContainer,
+ ...Containers.segmentContainer,
},
title: {
...Typography.subtitle,
diff --git a/components/helpers/format-date.js b/components/helpers/format-date.js
index ef04668..7f91321 100644
--- a/components/helpers/format-date.js
+++ b/components/helpers/format-date.js
@@ -22,3 +22,18 @@ export function dateToTitle(dateString) {
? labels.today
: moment(dateString).format('ddd DD. MMM YY')
}
+
+export function humanizeDate(dateString) {
+ if (!dateString) return ''
+
+ const today = LocalDate.now()
+
+ try {
+ const dateToDisplay = LocalDate.parse(dateString)
+ return today.equals(dateToDisplay)
+ ? labels.today
+ : moment(dateString).format('DD. MMM YY')
+ } catch (e) {
+ return ''
+ }
+}
diff --git a/components/stats.js b/components/stats.js
index b3809b2..51af03f 100644
--- a/components/stats.js
+++ b/components/stats.js
@@ -2,16 +2,15 @@ import React from 'react'
import { ImageBackground, View } from 'react-native'
import { ScaledSheet } from 'react-native-size-matters'
-import AppPage from './common/app-page'
import AppText from './common/app-text'
-import Segment from './common/segment'
-import Table from './common/table'
+import StatsOverview from './common/StatsOverview'
+import StatsTable from './common/StatsTable'
import cycleModule from '../lib/cycle'
-import {getCycleLengthStats as getCycleInfo} from '../lib/cycle-length'
-import {stats as labels} from '../i18n/en/labels'
+import { getCycleLengthStats as getCycleInfo } from '../lib/cycle-length'
+import { stats as labels } from '../i18n/en/labels'
-import { Sizes, Spacing, Typography } from '../styles'
+import { Containers, Sizes, Spacing, Typography } from '../styles'
const image = require('../assets/cycle-icon.png')
@@ -19,21 +18,22 @@ const Stats = () => {
const cycleLengths = cycleModule().getAllCycleLengths()
const numberOfCycles = cycleLengths.length
const hasAtLeastOneCycle = numberOfCycles >= 1
- const cycleData = hasAtLeastOneCycle ? getCycleInfo(cycleLengths)
+ const cycleData = hasAtLeastOneCycle
+ ? getCycleInfo(cycleLengths)
: { minimum: '—', maximum: '—', stdDeviation: '—' }
const statsData = [
[cycleData.minimum, labels.minLabel],
[cycleData.maximum, labels.maxLabel],
[cycleData.stdDeviation ? cycleData.stdDeviation : '—', labels.stdLabel],
- [numberOfCycles, labels.basisOfStatsEnd]
+ [numberOfCycles, labels.basisOfStatsEnd],
]
return (
-
-
+
+
{labels.cycleLengthExplainer}
{!hasAtLeastOneCycle && {labels.emptyStats}}
- {hasAtLeastOneCycle &&
+ {hasAtLeastOneCycle && (
{
-
+
- }
-
-
+ )}
+
+
+
)
}
@@ -77,25 +78,24 @@ const styles = ScaledSheet.create({
},
accentPurpleGiant: {
...Typography.accentPurpleGiant,
- marginTop: Spacing.base * (-2),
+ marginTop: Spacing.base * -2,
},
accentPurpleHuge: {
...Typography.accentPurpleHuge,
- marginTop: Spacing.base * (-1),
+ marginTop: Spacing.base * -1,
},
container: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
- paddingTop: Spacing.base
},
columnLeft: {
...column,
- flex: 2,
+ flex: 3,
},
columnRight: {
...column,
- flex: 3,
+ flex: 5,
paddingTop: Spacing.small,
},
image: {
@@ -105,9 +105,13 @@ const styles = ScaledSheet.create({
paddingTop: Spacing.large * 2.5,
marginBottom: Spacing.large,
},
+ overviewContainer: {
+ paddingHorizontal: Spacing.base,
+ paddingTop: Spacing.base,
+ },
pageContainer: {
- marginTop: Spacing.base * 2,
- }
+ ...Containers.pageContainer,
+ },
})
export default Stats
diff --git a/i18n/en.json b/i18n/en.json
index 74fbbfa..08c7576 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -60,5 +60,9 @@
"text": "You can read through the source code of drip. to ensure the given information is correct. The source code is like a recipe: It tells you how much and what kind of ingredients you need and how you prepare them to cook a tasty meal or program a funky app.\n\nBuon appetito!"
}
}
+ },
+ "plurals": {
+ "day": "{{count}} day",
+ "day_plural": "{{count}} days"
}
}
diff --git a/lib/cycle.js b/lib/cycle.js
index 3a8ca62..90c4a30 100644
--- a/lib/cycle.js
+++ b/lib/cycle.js
@@ -3,6 +3,8 @@ import { getCycleLengthStats } from './cycle-length'
const LocalDate = joda.LocalDate
const DAYS = joda.ChronoUnit.DAYS
+const toJSON = (realmObj) => JSON.parse(JSON.stringify(realmObj))
+
export default function config(opts) {
let bleedingDaysSortedByDate
let cycleStartsSortedByDate
@@ -14,9 +16,13 @@ export default function config(opts) {
if (!opts) {
// we only want to require (and run) the db module
// when not running the tests
- bleedingDaysSortedByDate = require('../db').getBleedingDaysSortedByDate()
- cycleStartsSortedByDate = require('../db').getCycleStartsSortedByDate()
- cycleDaysSortedByDate = require('../db').getCycleDaysSortedByDate()
+ bleedingDaysSortedByDate = toJSON(
+ require('../db').getBleedingDaysSortedByDate()
+ )
+ cycleStartsSortedByDate = toJSON(
+ require('../db').getCycleStartsSortedByDate()
+ )
+ cycleDaysSortedByDate = toJSON(require('../db').getCycleDaysSortedByDate())
maxBreakInBleeding = 1
maxCycleLength = 99
minCyclesForPrediction = 3
@@ -222,6 +228,19 @@ export default function config(opts) {
return predictedMenses
}
+ const getStats = () =>
+ cycleStartsSortedByDate.map((day, i) => {
+ const today = getTodayDate()
+ const cycleLength =
+ i === 0 ? getCycleDayNumber(today) : getAllCycleLengths()[i - 1]
+
+ return {
+ date: day.date,
+ cycleLength,
+ bleedingLength: ++getMensesDaysRightAfter(day).length,
+ }
+ })
+
return {
getCycleDayNumber,
getCycleForDay,
@@ -232,5 +251,6 @@ export default function config(opts) {
isMensesStart,
getMensesDaysRightAfter,
getCycleByStartDay,
+ getStats,
}
}
diff --git a/styles/containers.js b/styles/containers.js
index 6526d3a..8560d10 100644
--- a/styles/containers.js
+++ b/styles/containers.js
@@ -9,7 +9,7 @@ export default {
marginTop: Spacing.small,
marginRight: Spacing.small,
paddingHorizontal: Spacing.small,
- paddingVertical: Spacing.tiny
+ paddingVertical: Spacing.tiny,
},
boxActive: {
backgroundColor: Colors.orange,
@@ -17,17 +17,25 @@ export default {
centerItems: {
alignItems: 'center',
flex: 1,
- justifyContent: 'center'
+ justifyContent: 'center',
+ },
+ pageContainer: {
+ backgroundColor: Colors.turquoiseLight,
+ flex: 1,
},
rowContainer: {
alignItems: 'center',
flexDirection: 'row',
- justifyContent: 'space-between'
+ justifyContent: 'space-between',
},
selectGroupContainer: {
alignItems: 'center',
flexDirection: 'row',
flexWrap: 'wrap',
- marginVertical: Spacing.small
- }
-}
\ No newline at end of file
+ marginVertical: Spacing.small,
+ },
+ segmentContainer: {
+ marginHorizontal: Spacing.base,
+ marginBottom: Spacing.base,
+ },
+}
diff --git a/test/helpers/format-date.spec.js b/test/helpers/format-date.spec.js
new file mode 100644
index 0000000..9aa3a57
--- /dev/null
+++ b/test/helpers/format-date.spec.js
@@ -0,0 +1,27 @@
+import { humanizeDate } from '../../components/helpers/format-date'
+
+describe('humanizeDate', () => {
+ test('if receives null, returns empty string', () => {
+ const result = humanizeDate(null)
+
+ expect(result).toEqual('')
+ })
+
+ test('if receives undefined, returns empty string', () => {
+ const result = humanizeDate(undefined)
+
+ expect(result).toEqual('')
+ })
+
+ test('if receives incorrectly formatted date, returns empty string', () => {
+ const result = humanizeDate('abc')
+
+ expect(result).toEqual('')
+ })
+
+ test('if receives correct date string, returns date in humanized format', () => {
+ const result = humanizeDate('2022-01-07')
+
+ expect(result).toEqual('07. Jan 22')
+ })
+})