diff --git a/components/chart/chart.js b/components/chart/chart.js index 9d16920..6b07629 100644 --- a/components/chart/chart.js +++ b/components/chart/chart.js @@ -1,32 +1,20 @@ -import React, { Component } from 'react' +import React, { useEffect, useState } from 'react' import PropTypes from 'prop-types' -import { - ActivityIndicator, - Dimensions, - FlatList, - PixelRatio, - StyleSheet, - View, -} from 'react-native' +import { Dimensions, PixelRatio, StyleSheet, View } from 'react-native' -import AppLoadingView from '../common/app-loading' import AppPage from '../common/app-page' -import AppText from '../common/app-text' import DayColumn from './day-column' import HorizontalGrid from './horizontal-grid' +import MainGrid from './main-grid' import NoData from './no-data' +import NoTemperature from './no-temperature' import Tutorial from './tutorial' import YAxis from './y-axis' import { getCycleDaysSortedByDate } from '../../db' -import nothingChanged from '../../db/db-unchanged' -import { - getChartFlag, - scaleObservable, - setChartFlag, -} from '../../local-storage' -import { makeColumnInfo, nfpLines } from '../helpers/chart' +import { getChartFlag, setChartFlag } from '../../local-storage' +import { makeColumnInfo } from '../helpers/chart' import { CHART_COLUMN_WIDTH, @@ -35,204 +23,108 @@ import { CHART_XAXIS_HEIGHT_RATIO, SYMPTOMS, } from '../../config' -import { shared } from '../../i18n/en/labels' -import { Colors, Spacing } from '../../styles' +import { Spacing } from '../../styles' -class CycleChart extends Component { - static propTypes = { - navigate: PropTypes.func, - end: PropTypes.bool, - setDate: PropTypes.func, - } +const getSymptomsFromCycleDays = (cycleDays) => + SYMPTOMS.filter((symptom) => cycleDays.some((cycleDay) => cycleDay[symptom])) - constructor(props) { - super(props) +const CycleChart = ({ navigate, setDate }) => { + const [shouldShowHint, setShouldShowHint] = useState(true) - this.state = {} - this.cycleDaysSortedByDate = getCycleDaysSortedByDate() - this.getFhmAndLtlInfo = nfpLines() - this.shouldShowTemperatureColumn = false - - this.checkShouldShowHint() - this.prepareSymptomData() - } - - componentWillUnmount() { - this.cycleDaysSortedByDate.removeListener(this.handleDbChange) - this.removeObvListener() - } - - checkShouldShowHint = async () => { + useEffect(async () => { const flag = await getChartFlag() - const shouldShowHint = flag === 'true' ? true : false - this.setState({ shouldShowHint }) + setShouldShowHint(flag === 'true') + }, []) + + const hideHint = () => { + setShouldShowHint(false) + setChartFlag() } - setShouldShowHint = async () => { - await setChartFlag() - this.setState({ shouldShowHint: false }) - } + const cycleDaysSortedByDate = getCycleDaysSortedByDate() - onLayout = () => { - if (this.state.chartHeight) return false + const chartSymptoms = getSymptomsFromCycleDays(cycleDaysSortedByDate) + const symptomRowSymptoms = chartSymptoms.filter( + (symptom) => symptom !== 'temperature' + ) - this.reCalculateChartInfo() - this.updateListeners(this.reCalculateChartInfo) - } + const shouldShowTemperatureColumn = chartSymptoms.indexOf('temperature') > -1 - prepareSymptomData = () => { - this.symptomRowSymptoms = SYMPTOMS.filter((symptomName) => { - return this.cycleDaysSortedByDate.some((cycleDay) => { - return symptomName !== 'temperature' && cycleDay[symptomName] - }) - }) - this.chartSymptoms = [...this.symptomRowSymptoms] - if (this.cycleDaysSortedByDate.some((day) => day.temperature)) { - this.chartSymptoms.push('temperature') - this.shouldShowTemperatureColumn = true - } - } + const { width, height } = Dimensions.get('window') + const numberOfColumnsToRender = Math.round(width / CHART_COLUMN_WIDTH) - renderColumn = ({ item, index }) => { + const xAxisHeight = height * 0.7 * CHART_XAXIS_HEIGHT_RATIO + const remainingHeight = height * 0.7 - xAxisHeight + const symptomHeight = PixelRatio.roundToNearestPixel( + remainingHeight * CHART_SYMPTOM_HEIGHT_RATIO + ) + const symptomRowHeight = + PixelRatio.roundToNearestPixel(symptomRowSymptoms.length * symptomHeight) + + CHART_GRID_LINE_HORIZONTAL_WIDTH + const columnHeight = remainingHeight - symptomRowHeight + + const chartHeight = shouldShowTemperatureColumn + ? height * 0.7 + : symptomRowHeight + xAxisHeight + + const columns = makeColumnInfo() + + const renderColumn = ({ item }) => { return ( ) } - reCalculateChartInfo = () => { - const { width, height } = Dimensions.get('window') + const hasDataToDisplay = chartSymptoms.length > 0 - this.xAxisHeight = height * 0.7 * CHART_XAXIS_HEIGHT_RATIO - const remainingHeight = height * 0.7 - this.xAxisHeight - this.symptomHeight = PixelRatio.roundToNearestPixel( - remainingHeight * CHART_SYMPTOM_HEIGHT_RATIO - ) - this.symptomRowHeight = - PixelRatio.roundToNearestPixel( - this.symptomRowSymptoms.length * this.symptomHeight - ) + CHART_GRID_LINE_HORIZONTAL_WIDTH - this.columnHeight = remainingHeight - this.symptomRowHeight - const chartHeight = this.shouldShowTemperatureColumn - ? height * 0.7 - : this.symptomRowHeight + this.xAxisHeight - const numberOfColumnsToRender = Math.round(width / CHART_COLUMN_WIDTH) - const columns = makeColumnInfo() - - this.setState({ columns, chartHeight, numberOfColumnsToRender }) + if (!hasDataToDisplay) { + return } - updateListeners(dataUpdateHandler) { - // remove existing listeners - if (this.handleDbChange) { - this.cycleDaysSortedByDate.removeListener(this.handleDbChange) - } - if (this.removeObvListener) this.removeObvListener() - - this.handleDbChange = (_, changes) => { - if (nothingChanged(changes)) return - dataUpdateHandler() - } - - this.cycleDaysSortedByDate.addListener(this.handleDbChange) - this.removeObvListener = scaleObservable(dataUpdateHandler, false) - } - - render() { - const { - chartHeight, - chartLoaded, - shouldShowHint, - numberOfColumnsToRender, - } = this.state - const hasDataToDisplay = this.chartSymptoms.length > 0 - - return ( - - {!hasDataToDisplay && } - {hasDataToDisplay && !chartHeight && !chartLoaded && } - - {shouldShowHint && chartLoaded && ( - - )} - {hasDataToDisplay && - chartLoaded && - !this.shouldShowTemperatureColumn && ( - - - {shared.noTemperatureWarning} - - - )} - {hasDataToDisplay && ( - - {chartHeight && chartLoaded && ( - - )} - - {chartHeight && ( - item} - initialNumToRender={numberOfColumnsToRender} - windowSize={30} - onLayout={() => this.setState({ chartLoaded: true })} - onEndReached={() => this.setState({ end: true })} - ListFooterComponent={} - updateCellsBatchingPeriod={800} - contentContainerStyle={{ height: chartHeight }} - /> - )} - {chartHeight && chartLoaded && ( - - {this.shouldShowTemperatureColumn && ( - - )} - - )} - + return ( + + + {shouldShowHint && } + {!shouldShowTemperatureColumn && } + + + + {shouldShowTemperatureColumn && ( + )} - - ) - } -} - -function LoadingMoreView({ end }) { - return ( - - {!end && } - + + ) } -LoadingMoreView.propTypes = { - end: PropTypes.bool, +CycleChart.propTypes = { + navigate: PropTypes.func, + setDate: PropTypes.func, } const styles = StyleSheet.create({ @@ -242,20 +134,12 @@ const styles = StyleSheet.create({ chartContainer: { flexDirection: 'column', }, - loadingContainer: { - height: '100%', - backgroundColor: Colors.turquoiseLight, - justifyContent: 'center', - }, page: { marginVertical: Spacing.small, }, pageContainer: { paddingHorizontal: Spacing.base, }, - warning: { - padding: Spacing.large, - }, }) export default CycleChart diff --git a/components/chart/day-column.js b/components/chart/day-column.js index ac4ac55..7d9e0a0 100644 --- a/components/chart/day-column.js +++ b/components/chart/day-column.js @@ -12,13 +12,13 @@ import { symptomColorMethods, getTemperatureProps, isSymptomDataComplete, + nfpLines, } from '../helpers/chart' const DayColumn = ({ dateString, chartSymptoms, columnHeight, - getFhmAndLtlInfo, setDate, navigate, shouldShowTemperatureColumn, @@ -54,7 +54,7 @@ const DayColumn = ({ }, data) } - const fhmAndLtl = getFhmAndLtlInfo( + const fhmAndLtl = nfpLines()( dateString, data.temperature ? data.temperature.value : null, columnHeight @@ -104,7 +104,6 @@ DayColumn.propTypes = { dateString: PropTypes.string.isRequired, chartSymptoms: PropTypes.array, columnHeight: PropTypes.number.isRequired, - getFhmAndLtlInfo: PropTypes.func.isRequired, navigate: PropTypes.func.isRequired, setDate: PropTypes.func.isRequired, shouldShowTemperatureColumn: PropTypes.bool, diff --git a/components/chart/loading-more.js b/components/chart/loading-more.js new file mode 100644 index 0000000..97df4db --- /dev/null +++ b/components/chart/loading-more.js @@ -0,0 +1,27 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { ActivityIndicator, StyleSheet, View } from 'react-native' + +import { Colors } from '../../styles' + +function LoadingMoreView({ end }) { + return ( + + {!end && } + + ) +} + +LoadingMoreView.propTypes = { + end: PropTypes.bool, +} + +const styles = StyleSheet.create({ + loadingContainer: { + height: '100%', + backgroundColor: Colors.turquoiseLight, + justifyContent: 'center', + }, +}) + +export default LoadingMoreView diff --git a/components/chart/main-grid.js b/components/chart/main-grid.js new file mode 100644 index 0000000..bd65b1f --- /dev/null +++ b/components/chart/main-grid.js @@ -0,0 +1,32 @@ +import React, { useState } from 'react' +import PropTypes from 'prop-types' +import { FlatList } from 'react-native' + +import LoadingMoreView from './loading-more' + +const MainGrid = (props) => { + const [endReached, setEndReached] = useState(false) + return ( + item} + windowSize={30} + onEndReached={() => setEndReached(true)} + ListFooterComponent={} + updateCellsBatchingPeriod={800} + {...props} + /> + ) +} + +MainGrid.propTypes = { + height: PropTypes.number, + data: PropTypes.array, + renderItem: PropTypes.func, + initialNumToRender: PropTypes.number, + contentContainerStyle: PropTypes.object, +} + +export default MainGrid diff --git a/components/chart/no-temperature.js b/components/chart/no-temperature.js new file mode 100644 index 0000000..931b035 --- /dev/null +++ b/components/chart/no-temperature.js @@ -0,0 +1,19 @@ +import React from 'react' +import { StyleSheet } from 'react-native' +import AppText from '../common/app-text' + +import { shared } from '../../i18n/en/labels' + +import { Spacing } from '../../styles' + +function NoTemperature() { + return {shared.noTemperatureWarning} +} + +const styles = StyleSheet.create({ + warning: { + padding: Spacing.large, + }, +}) + +export default NoTemperature