Redesign chart
This commit is contained in:
committed by
Sofiya Tepikin
parent
550b1e6314
commit
ef16cfd041
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,32 +1,38 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import AppText from '../common/app-text'
|
import AppText from '../common/app-text'
|
||||||
import DripHomeIcon from '../../assets/drip-home-icons'
|
|
||||||
|
|
||||||
import styles from './styles'
|
|
||||||
import { cycleDayColor } from '../../styles'
|
|
||||||
|
|
||||||
|
import { Typography } from '../../styles/redesign'
|
||||||
|
import { CHART_YAXIS_WIDTH } from '../../config'
|
||||||
import { shared as labels } from '../../i18n/en/labels'
|
import { shared as labels } from '../../i18n/en/labels'
|
||||||
|
|
||||||
const ChartLegend = ({ xAxisHeight }) => {
|
const ChartLegend = ({ height }) => {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.yAxis, styles.chartLegend, {height: xAxisHeight}]}>
|
<View style={[styles.container, { height }]}>
|
||||||
<DripHomeIcon
|
<AppText style={styles.textBold}>#</AppText>
|
||||||
name="circle"
|
<AppText style={styles.text}>{labels.date}</AppText>
|
||||||
size={styles.yAxis.width - 7}
|
|
||||||
color={cycleDayColor}
|
|
||||||
/>
|
|
||||||
<AppText style={styles.yAxisLabels.dateLabel}>
|
|
||||||
{labels.date.toLowerCase()}
|
|
||||||
</AppText>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ChartLegend.propTypes = {
|
ChartLegend.propTypes = {
|
||||||
xAxisHeight: PropTypes.number.isRequired
|
height: PropTypes.number.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
width: CHART_YAXIS_WIDTH
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
...Typography.label
|
||||||
|
},
|
||||||
|
textBold: {
|
||||||
|
...Typography.labelBold
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default ChartLegend
|
export default ChartLegend
|
||||||
|
|||||||
@@ -3,20 +3,16 @@ import PropTypes from 'prop-types'
|
|||||||
|
|
||||||
import { Shape } from 'react-native/Libraries/ART/ReactNativeART'
|
import { Shape } from 'react-native/Libraries/ART/ReactNativeART'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors } from '../../styles/redesign'
|
||||||
|
import { CHART_STROKE_WIDTH, CHART_GRID_LINE_HORIZONTAL_WIDTH } from '../../config'
|
||||||
|
|
||||||
const ChartLine = ({ path, isNfpLine = false }) => {
|
const ChartLine = ({ path, isNfpLine }) => {
|
||||||
const strokeStyle =
|
const color = isNfpLine ? Colors.orange : Colors.grey
|
||||||
isNfpLine ? styles.nfpLine.stroke : styles.column.stroke.color
|
const width = isNfpLine
|
||||||
const strokeWidth =
|
? CHART_STROKE_WIDTH : CHART_GRID_LINE_HORIZONTAL_WIDTH
|
||||||
isNfpLine ? styles.nfpLine.strokeWidth : styles.column.stroke.width
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Shape
|
<Shape d={path} stroke={color} strokeWidth={width} />
|
||||||
stroke={strokeStyle}
|
|
||||||
strokeWidth={strokeWidth}
|
|
||||||
d={path}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,4 +21,8 @@ ChartLine.propTypes = {
|
|||||||
isNfpLine: PropTypes.bool,
|
isNfpLine: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChartLine.defaultProps = {
|
||||||
|
isNfpLine: false
|
||||||
|
}
|
||||||
|
|
||||||
export default ChartLine
|
export default ChartLine
|
||||||
|
|||||||
+112
-60
@@ -1,26 +1,33 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View, FlatList, ActivityIndicator } from 'react-native'
|
import { ActivityIndicator, FlatList, StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import NoData from './no-data'
|
|
||||||
import AppLoadingView from '../common/app-loading'
|
import AppLoadingView from '../common/app-loading'
|
||||||
import YAxis from './y-axis'
|
import AppPage from '../common/app-page'
|
||||||
import nfpLines from './nfp-lines'
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import DayColumn from './day-column'
|
import DayColumn from './day-column'
|
||||||
import HorizontalGrid from './horizontal-grid'
|
import HorizontalGrid from './horizontal-grid'
|
||||||
import AppText from '../common/app-text'
|
import NoData from './no-data'
|
||||||
|
import Tutorial from './tutorial'
|
||||||
|
import YAxis from './y-axis'
|
||||||
|
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { navigate } from '../../slices/navigation'
|
import { navigate } from '../../slices/navigation'
|
||||||
|
|
||||||
import { getCycleDaysSortedByDate } from '../../db'
|
import { getCycleDaysSortedByDate } from '../../db'
|
||||||
import nothingChanged from '../../db/db-unchanged'
|
import nothingChanged from '../../db/db-unchanged'
|
||||||
import { scaleObservable } from '../../local-storage'
|
import { getChartFlag, scaleObservable, setChartFlag } from '../../local-storage'
|
||||||
import { makeColumnInfo } from '../helpers/chart'
|
import { makeColumnInfo, nfpLines } from '../helpers/chart'
|
||||||
|
|
||||||
import config from '../../config'
|
import {
|
||||||
|
CHART_COLUMN_WIDTH,
|
||||||
|
SYMPTOMS,
|
||||||
|
CHART_SYMPTOM_HEIGHT_RATIO,
|
||||||
|
CHART_XAXIS_HEIGHT_RATIO
|
||||||
|
} from '../../config'
|
||||||
import { shared } from '../../i18n/en/labels'
|
import { shared } from '../../i18n/en/labels'
|
||||||
import styles from './styles'
|
import { Colors, Spacing } from '../../styles/redesign'
|
||||||
|
|
||||||
class CycleChart extends Component {
|
class CycleChart extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -36,11 +43,35 @@ class CycleChart extends Component {
|
|||||||
this.getFhmAndLtlInfo = nfpLines()
|
this.getFhmAndLtlInfo = nfpLines()
|
||||||
this.shouldShowTemperatureColumn = false
|
this.shouldShowTemperatureColumn = false
|
||||||
|
|
||||||
|
this.checkShouldShowHint()
|
||||||
this.prepareSymptomData()
|
this.prepareSymptomData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.cycleDaysSortedByDate.removeListener(this.handleDbChange)
|
||||||
|
this.removeObvListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
checkShouldShowHint = async () => {
|
||||||
|
const flag = await getChartFlag()
|
||||||
|
const shouldShowHint = flag === 'true' ? true : false
|
||||||
|
this.setState({ shouldShowHint })
|
||||||
|
}
|
||||||
|
|
||||||
|
setShouldShowHint = async () => {
|
||||||
|
await setChartFlag()
|
||||||
|
this.setState({ shouldShowHint: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
onLayout = ({ nativeEvent }) => {
|
||||||
|
if (this.state.chartHeight) return false
|
||||||
|
|
||||||
|
this.reCalculateChartInfo(nativeEvent)
|
||||||
|
this.updateListeners(this.reCalculateChartInfo)
|
||||||
|
}
|
||||||
|
|
||||||
prepareSymptomData = () => {
|
prepareSymptomData = () => {
|
||||||
this.symptomRowSymptoms = config.symptoms.filter((symptomName) => {
|
this.symptomRowSymptoms = SYMPTOMS.filter((symptomName) => {
|
||||||
return this.cycleDaysSortedByDate.some(cycleDay => {
|
return this.cycleDaysSortedByDate.some(cycleDay => {
|
||||||
return cycleDay[symptomName]
|
return cycleDay[symptomName]
|
||||||
})
|
})
|
||||||
@@ -71,33 +102,21 @@ class CycleChart extends Component {
|
|||||||
|
|
||||||
reCalculateChartInfo = (nativeEvent) => {
|
reCalculateChartInfo = (nativeEvent) => {
|
||||||
const { height, width } = nativeEvent.layout
|
const { height, width } = nativeEvent.layout
|
||||||
const xAxisCoefficient = this.shouldShowTemperatureColumn ?
|
|
||||||
config.xAxisHeightPercentage : config.xAxisHeightPercentageLarge
|
|
||||||
const symptomCoefficient = this.shouldShowTemperatureColumn ?
|
|
||||||
config.symptomHeightPercentage : config.symptomHeightPercentageLarge
|
|
||||||
|
|
||||||
this.xAxisHeight = height * xAxisCoefficient
|
this.xAxisHeight = height * CHART_XAXIS_HEIGHT_RATIO
|
||||||
const remainingHeight = height - this.xAxisHeight
|
const remainingHeight = height - this.xAxisHeight
|
||||||
this.symptomHeight = remainingHeight * symptomCoefficient
|
this.symptomHeight = remainingHeight * CHART_SYMPTOM_HEIGHT_RATIO
|
||||||
this.symptomRowHeight = this.symptomRowSymptoms.length *
|
this.symptomRowHeight = this.symptomRowSymptoms.length *
|
||||||
this.symptomHeight
|
this.symptomHeight
|
||||||
this.columnHeight = remainingHeight - this.symptomRowHeight
|
this.columnHeight = remainingHeight - this.symptomRowHeight
|
||||||
const chartHeight = this.shouldShowTemperatureColumn ?
|
const chartHeight = this.shouldShowTemperatureColumn ?
|
||||||
height : (this.symptomRowHeight + this.xAxisHeight)
|
height : (this.symptomRowHeight + this.xAxisHeight)
|
||||||
|
const numberOfColumnsToRender = Math.round(width / CHART_COLUMN_WIDTH)
|
||||||
const numberOfColumnsToRender = Math.round(width / config.columnWidth)
|
|
||||||
const columns = makeColumnInfo()
|
const columns = makeColumnInfo()
|
||||||
|
|
||||||
this.setState({ columns, chartHeight, numberOfColumnsToRender })
|
this.setState({ columns, chartHeight, numberOfColumnsToRender })
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayout = ({ nativeEvent }) => {
|
|
||||||
if (this.state.chartHeight) return
|
|
||||||
|
|
||||||
this.reCalculateChartInfo(nativeEvent)
|
|
||||||
this.updateListeners(this.reCalculateChartInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateListeners(dataUpdateHandler) {
|
updateListeners(dataUpdateHandler) {
|
||||||
// remove existing listeners
|
// remove existing listeners
|
||||||
if(this.handleDbChange) {
|
if(this.handleDbChange) {
|
||||||
@@ -114,38 +133,47 @@ class CycleChart extends Component {
|
|||||||
this.removeObvListener = scaleObservable(dataUpdateHandler, false)
|
this.removeObvListener = scaleObservable(dataUpdateHandler, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.cycleDaysSortedByDate.removeListener(this.handleDbChange)
|
|
||||||
this.removeObvListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { chartHeight, chartLoaded, numberOfColumnsToRender } = this.state
|
const {
|
||||||
const shouldShowChart = this.chartSymptoms.length > 0 ? true : false
|
chartHeight,
|
||||||
|
chartLoaded,
|
||||||
|
shouldShowHint,
|
||||||
|
numberOfColumnsToRender
|
||||||
|
} = this.state
|
||||||
|
const hasDataToDisplay = this.chartSymptoms.length > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View onLayout={this.onLayout} style={styles.container}>
|
<AppPage
|
||||||
{!shouldShowChart && <NoData navigate={this.props.navigate}/>}
|
contentContainerStyle={styles.pageContainer}
|
||||||
{shouldShowChart && !chartHeight && !chartLoaded && <AppLoadingView />}
|
onLayout={this.onLayout}
|
||||||
|
scrollViewStyle={styles.page}
|
||||||
|
>
|
||||||
|
{!hasDataToDisplay && <NoData />}
|
||||||
|
{hasDataToDisplay && !chartHeight && !chartLoaded && <AppLoadingView />}
|
||||||
<View style={styles.chartContainer}>
|
<View style={styles.chartContainer}>
|
||||||
{shouldShowChart && (
|
{shouldShowHint && chartLoaded &&
|
||||||
|
<Tutorial onClose={this.setShouldShowHint} />
|
||||||
|
}
|
||||||
|
{hasDataToDisplay && chartLoaded &&
|
||||||
|
!this.shouldShowTemperatureColumn &&
|
||||||
|
<View style={styles.centerItem}>
|
||||||
|
<AppText style={styles.warning}>
|
||||||
|
{shared.noTemperatureWarning}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
{hasDataToDisplay && (
|
||||||
<View style={styles.chartArea}>
|
<View style={styles.chartArea}>
|
||||||
|
|
||||||
{chartHeight && chartLoaded && (
|
{chartHeight && chartLoaded && (
|
||||||
<React.Fragment>
|
<YAxis
|
||||||
<YAxis
|
height={this.columnHeight}
|
||||||
height={this.columnHeight}
|
symptomsToDisplay={this.symptomRowSymptoms}
|
||||||
symptomsToDisplay={this.symptomRowSymptoms}
|
symptomsSectionHeight={this.symptomRowHeight}
|
||||||
symptomsSectionHeight={this.symptomRowHeight}
|
shouldShowTemperatureColumn=
|
||||||
shouldShowTemperatureColumn=
|
{this.shouldShowTemperatureColumn}
|
||||||
{this.shouldShowTemperatureColumn}
|
xAxisHeight={this.xAxisHeight}
|
||||||
xAxisHeight={this.xAxisHeight}
|
/>
|
||||||
/>
|
|
||||||
{this.shouldShowTemperatureColumn && (<HorizontalGrid
|
|
||||||
height={this.columnHeight}
|
|
||||||
startPosition={this.symptomRowHeight}
|
|
||||||
/>)}
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{chartHeight &&
|
{chartHeight &&
|
||||||
@@ -162,27 +190,28 @@ class CycleChart extends Component {
|
|||||||
onEndReached={() => this.setState({end: true})}
|
onEndReached={() => this.setState({end: true})}
|
||||||
ListFooterComponent={<LoadingMoreView end={this.state.end}/>}
|
ListFooterComponent={<LoadingMoreView end={this.state.end}/>}
|
||||||
updateCellsBatchingPeriod={800}
|
updateCellsBatchingPeriod={800}
|
||||||
contentContainerStyle={{height: chartHeight}}
|
contentContainerStyle={{ height: chartHeight }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
{chartHeight && chartLoaded && (
|
||||||
|
<React.Fragment>
|
||||||
|
{this.shouldShowTemperatureColumn &&
|
||||||
|
<HorizontalGrid height={this.columnHeight} />
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{shouldShowChart && chartLoaded && !this.shouldShowTemperatureColumn
|
</AppPage>
|
||||||
&& (
|
|
||||||
<View style={styles.centerItem}>
|
|
||||||
<AppText style={{textAlign: 'center'}}>{shared.noTemperatureWarning}</AppText>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function LoadingMoreView({ end }) {
|
function LoadingMoreView({ end }) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.loadingMore}>
|
<View style={styles.loadingContainer}>
|
||||||
{!end && <ActivityIndicator size={'large'} color={'white'}/>}
|
{!end && <ActivityIndicator size={'large'} color={Colors.orange}/>}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -191,6 +220,29 @@ LoadingMoreView.propTypes = {
|
|||||||
end: PropTypes.bool
|
end: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
chartArea: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
chartContainer: {
|
||||||
|
flexDirection: 'column'
|
||||||
|
},
|
||||||
|
loadingContainer: {
|
||||||
|
height: '100%',
|
||||||
|
backgroundColor: Colors.tourquiseLight,
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
marginVertical: Spacing.small
|
||||||
|
},
|
||||||
|
pageContainer: {
|
||||||
|
paddingHorizontal: Spacing.base
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
padding: Spacing.large
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => {
|
const mapDispatchToProps = (dispatch) => {
|
||||||
return({
|
return({
|
||||||
navigate: (page) => dispatch(navigate(page)),
|
navigate: (page) => dispatch(navigate(page)),
|
||||||
|
|||||||
@@ -1,32 +1,33 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
import { Text, View } from 'react-native'
|
|
||||||
|
|
||||||
import moment from 'moment'
|
|
||||||
import { LocalDate } from 'js-joda'
|
import { LocalDate } from 'js-joda'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import styles from './styles'
|
|
||||||
import cycleModule from '../../lib/cycle'
|
import cycleModule from '../../lib/cycle'
|
||||||
|
import { dateEnding } from '../helpers/home'
|
||||||
|
import { Containers, Typography } from '../../styles/redesign'
|
||||||
|
|
||||||
const CycleDayLabel = ({ height, date }) => {
|
const CycleDayLabel = ({ height, date }) => {
|
||||||
const { label } = styles.column
|
|
||||||
const dayDate = LocalDate.parse(date)
|
const dayDate = LocalDate.parse(date)
|
||||||
const cycleDayNumber = cycleModule().getCycleDayNumber(date)
|
const cycleDayNumber = cycleModule().getCycleDayNumber(date)
|
||||||
|
|
||||||
const isFirstDayOfMonth = dayDate.dayOfMonth() === 1
|
const isFirstDayOfMonth = dayDate.dayOfMonth() === 1
|
||||||
const dateFormatting = isFirstDayOfMonth ? 'MMM' : 'Do'
|
const dateFormatting = isFirstDayOfMonth ? 'MMM' : 'D'
|
||||||
const shortDate = moment(date, "YYYY-MM-DD").format(dateFormatting)
|
const shortDate = moment(date, "YYYY-MM-DD").format(dateFormatting)
|
||||||
const boldDateLabel = isFirstDayOfMonth ? {fontWeight: 'bold'} : {}
|
const ending = isFirstDayOfMonth ?
|
||||||
|
'' : dateEnding[this.cycleDayNumber] || dateEnding['default']
|
||||||
|
const cycleDayLabel = cycleDayNumber ? cycleDayNumber : ' '
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.chartLegend, { height }]}>
|
<View style={[styles.container, { height }]}>
|
||||||
<Text style={label.number}>
|
<AppText style={styles.textBold}>{cycleDayLabel}</AppText>
|
||||||
{cycleDayNumber ? cycleDayNumber : ' '}
|
<View style={{flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center'}}>
|
||||||
</Text>
|
<AppText style={styles.text}>{shortDate}</AppText>
|
||||||
<Text style={[label.date, boldDateLabel]}>
|
<AppText style={styles.textLight}>{ending}</AppText>
|
||||||
{shortDate}
|
</View>
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -36,4 +37,24 @@ CycleDayLabel.propTypes = {
|
|||||||
date: PropTypes.string,
|
date: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
left: 4,
|
||||||
|
},
|
||||||
|
containerRow: {
|
||||||
|
...Containers.rowContainer
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
...Typography.label
|
||||||
|
},
|
||||||
|
textBold: {
|
||||||
|
...Typography.labelBold
|
||||||
|
},
|
||||||
|
textLight: {
|
||||||
|
...Typography.labelLight
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default CycleDayLabel
|
export default CycleDayLabel
|
||||||
|
|||||||
@@ -92,6 +92,18 @@ class DayColumn extends Component {
|
|||||||
activeOpacity={1}
|
activeOpacity={1}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
{shouldShowTemperatureColumn && <TemperatureColumn
|
||||||
|
horizontalLinePosition={this.fhmAndLtl.drawLtlAt}
|
||||||
|
isVerticalLine={this.fhmAndLtl.drawFhmLine}
|
||||||
|
data={this.data && this.data.temperature}
|
||||||
|
columnHeight={columnHeight}
|
||||||
|
/>}
|
||||||
|
|
||||||
|
<CycleDayLabel
|
||||||
|
height={xAxisHeight}
|
||||||
|
date={dateString}
|
||||||
|
/>
|
||||||
|
|
||||||
{ symptomRowSymptoms.map(symptom => {
|
{ symptomRowSymptoms.map(symptom => {
|
||||||
const hasSymptomData = this.data.hasOwnProperty(symptom)
|
const hasSymptomData = this.data.hasOwnProperty(symptom)
|
||||||
return (
|
return (
|
||||||
@@ -107,18 +119,6 @@ class DayColumn extends Component {
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{shouldShowTemperatureColumn && <TemperatureColumn
|
|
||||||
horizontalLinePosition={this.fhmAndLtl.drawLtlAt}
|
|
||||||
isVerticalLine={this.fhmAndLtl.drawFhmLine}
|
|
||||||
data={this.data && this.data.temperature}
|
|
||||||
columnHeight={columnHeight}
|
|
||||||
/>}
|
|
||||||
|
|
||||||
<CycleDayLabel
|
|
||||||
height={xAxisHeight}
|
|
||||||
date={dateString}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,14 @@ import React, { Component } from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Path, Shape } from 'react-native/Libraries/ART/ReactNativeART'
|
import { Path, Shape } from 'react-native/Libraries/ART/ReactNativeART'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors } from '../../styles/redesign'
|
||||||
import config from '../../config'
|
|
||||||
|
import {
|
||||||
|
CHART_COLUMN_WIDTH,
|
||||||
|
CHART_COLUMN_MIDDLE,
|
||||||
|
CHART_DOT_RADIUS,
|
||||||
|
CHART_STROKE_WIDTH
|
||||||
|
} from '../../config'
|
||||||
|
|
||||||
export default class DotAndLine extends Component {
|
export default class DotAndLine extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -20,48 +26,56 @@ export default class DotAndLine extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const y = this.props.y
|
const {
|
||||||
const exclude = this.props.exclude
|
exclude,
|
||||||
let lineToRight
|
leftTemperatureExclude,
|
||||||
let lineToLeft
|
leftY,
|
||||||
|
rightTemperatureExclude,
|
||||||
|
rightY,
|
||||||
|
y
|
||||||
|
} = this.props
|
||||||
|
let excludeLeftLine, excludeRightLine, lineLeft, lineRight
|
||||||
|
|
||||||
if (this.props.leftY) {
|
if (leftY) {
|
||||||
const middleY = ((this.props.leftY - y) / 2) + y
|
const middleY = ((leftY - y) / 2) + y
|
||||||
const excludedLine = this.props.leftTemperatureExclude || exclude
|
excludeLeftLine = leftTemperatureExclude || exclude
|
||||||
lineToLeft = makeLine(y, middleY, 0, excludedLine)
|
lineLeft = new Path()
|
||||||
|
.moveTo(CHART_COLUMN_MIDDLE - CHART_DOT_RADIUS, y)
|
||||||
|
.lineTo(0, middleY)
|
||||||
}
|
}
|
||||||
if (this.props.rightY) {
|
if (rightY) {
|
||||||
const middleY = ((y - this.props.rightY) / 2) + this.props.rightY
|
const middleY = ((y - rightY) / 2) + rightY
|
||||||
const excludedLine = this.props.rightTemperatureExclude || exclude
|
excludeRightLine = rightTemperatureExclude || exclude
|
||||||
lineToRight = makeLine(y, middleY, config.columnWidth, excludedLine)
|
lineRight = new Path()
|
||||||
|
.moveTo(CHART_COLUMN_MIDDLE + CHART_DOT_RADIUS, y)
|
||||||
|
.lineTo(CHART_COLUMN_WIDTH, middleY)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dotStyle = exclude ? styles.curveDotsExcluded : styles.curveDots
|
const dot = new Path().moveTo(CHART_COLUMN_MIDDLE , y - CHART_DOT_RADIUS)
|
||||||
const radius = dotStyle.r
|
.arc(0, CHART_DOT_RADIUS * 2, CHART_DOT_RADIUS)
|
||||||
const dot = (
|
.arc(0, CHART_DOT_RADIUS * -2, CHART_DOT_RADIUS)
|
||||||
<Shape
|
const dotColor = exclude ? Colors.tourquise : Colors.tourquiseDark
|
||||||
d={new Path()
|
const lineColorLeft = excludeLeftLine ?
|
||||||
.moveTo(config.columnMiddle, y - radius)
|
Colors.tourquise : Colors.tourquiseDark
|
||||||
.arc(0, radius * 2, radius)
|
const lineColorRight = excludeRightLine ?
|
||||||
.arc(0, radius * -2, radius)
|
Colors.tourquise : Colors.tourquiseDark
|
||||||
}
|
|
||||||
fill={dotStyle.fill}
|
return(
|
||||||
key='dot'
|
<React.Fragment>
|
||||||
/>
|
<Shape
|
||||||
|
d={lineLeft}
|
||||||
|
stroke={lineColorLeft}
|
||||||
|
strokeWidth={CHART_STROKE_WIDTH}
|
||||||
|
key={y}
|
||||||
|
/>
|
||||||
|
<Shape
|
||||||
|
d={lineRight}
|
||||||
|
stroke={lineColorRight}
|
||||||
|
strokeWidth={CHART_STROKE_WIDTH}
|
||||||
|
key={y + CHART_DOT_RADIUS}
|
||||||
|
/>
|
||||||
|
<Shape d={dot} stroke={dotColor} strokeWidth={CHART_STROKE_WIDTH} key='dot' />
|
||||||
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
return [lineToLeft, lineToRight, dot]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeLine(currY, middleY, x, excludeLine) {
|
|
||||||
const lineStyle = excludeLine ? styles.curveExcluded : styles.curve
|
|
||||||
|
|
||||||
return <Shape
|
|
||||||
stroke={lineStyle.stroke}
|
|
||||||
d={new Path()
|
|
||||||
.moveTo(config.columnMiddle, currY)
|
|
||||||
.lineTo(x, middleY)
|
|
||||||
}
|
|
||||||
key={x.toString()}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,33 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import { getTickPositions } from '../helpers/chart'
|
import { getTickPositions } from '../helpers/chart'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors } from '../../styles/redesign'
|
||||||
|
import { CHART_GRID_LINE_HORIZONTAL_WIDTH, CHART_YAXIS_WIDTH } from '../../config'
|
||||||
|
|
||||||
const HorizontalGrid = ({ height, startPosition }) => {
|
const HorizontalGrid = ({ height }) => {
|
||||||
return getTickPositions(height).map(tick => {
|
return getTickPositions(height).map(tick => {
|
||||||
return (
|
return (
|
||||||
<View
|
<View key={tick} top={tick} {...styles.line}/>
|
||||||
top={startPosition + tick}
|
|
||||||
{...styles.horizontalGrid}
|
|
||||||
key={tick}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalGrid.propTypes = {
|
HorizontalGrid.propTypes = {
|
||||||
height: PropTypes.number,
|
height: PropTypes.number
|
||||||
startPosition: PropTypes.number,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
line: {
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderBottomColor: Colors.grey,
|
||||||
|
borderBottomWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
|
||||||
|
left: CHART_YAXIS_WIDTH,
|
||||||
|
position:'absolute',
|
||||||
|
right: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default HorizontalGrid
|
export default HorizontalGrid
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import { getCycleStatusForDay } from '../../lib/sympto-adapter'
|
|
||||||
import { normalizeToScale } from '../helpers/chart'
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
const cycle = {
|
|
||||||
status: null
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCurrentCycle(dateString) {
|
|
||||||
// for the NFP lines, we don't care about potentially extending the
|
|
||||||
// preOvu phase, so we don't include all earlier cycles, as that is
|
|
||||||
// an expensive db operation at the moment
|
|
||||||
cycle.status = getCycleStatusForDay(
|
|
||||||
dateString, { excludeEarlierCycles: true }
|
|
||||||
)
|
|
||||||
if(!cycle.status) {
|
|
||||||
cycle.noMoreCycles = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (cycle.status.phases.preOvulatory) {
|
|
||||||
cycle.startDate = cycle.status.phases.preOvulatory.start.date
|
|
||||||
} else {
|
|
||||||
cycle.startDate = cycle.status.phases.periOvulatory.start.date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dateIsInPeriOrPostPhase(dateString) {
|
|
||||||
return (
|
|
||||||
dateString >= cycle.status.phases.periOvulatory.start.date
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function precededByAnotherTempValue(dateString) {
|
|
||||||
return (
|
|
||||||
// we are only interested in days that have a preceding
|
|
||||||
// temp
|
|
||||||
Object.keys(cycle.status.phases).some(phaseName => {
|
|
||||||
return cycle.status.phases[phaseName].cycleDays.some(day => {
|
|
||||||
return day.temperature && day.date < dateString
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// and also a following temp, so we don't draw the line
|
|
||||||
// longer than necessary
|
|
||||||
&&
|
|
||||||
cycle.status.phases.postOvulatory.cycleDays.some(day => {
|
|
||||||
return day.temperature && day.date > dateString
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInTempMeasuringPhase(temperature, dateString) {
|
|
||||||
return (
|
|
||||||
temperature || precededByAnotherTempValue(dateString)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(dateString, temperature, columnHeight) {
|
|
||||||
const ret = {
|
|
||||||
drawLtlAt: null,
|
|
||||||
drawFhmLine: false
|
|
||||||
}
|
|
||||||
if (!cycle.status && !cycle.noMoreCycles) updateCurrentCycle(dateString)
|
|
||||||
if (cycle.noMoreCycles) return ret
|
|
||||||
|
|
||||||
if (dateString < cycle.startDate) updateCurrentCycle(dateString)
|
|
||||||
if (cycle.noMoreCycles) return ret
|
|
||||||
|
|
||||||
const tempShift = cycle.status.temperatureShift
|
|
||||||
|
|
||||||
if (tempShift) {
|
|
||||||
if (tempShift.firstHighMeasurementDay.date === dateString) {
|
|
||||||
ret.drawFhmLine = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
dateIsInPeriOrPostPhase(dateString) &&
|
|
||||||
isInTempMeasuringPhase(temperature, dateString)
|
|
||||||
) {
|
|
||||||
ret.drawLtlAt = normalizeToScale(tempShift.ltl, columnHeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+28
-15
@@ -1,31 +1,44 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
|
||||||
|
|
||||||
import AppText from '../common/app-text'
|
import AppText from '../common/app-text'
|
||||||
import SettingsButton from '../settings/shared/settings-button'
|
import Button from '../common/button'
|
||||||
|
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { navigate } from '../../slices/navigation'
|
||||||
|
|
||||||
|
import { Containers } from '../../styles/redesign'
|
||||||
import { shared } from '../../i18n/en/labels'
|
import { shared } from '../../i18n/en/labels'
|
||||||
import styles from './styles'
|
|
||||||
|
|
||||||
const NoData = ({ navigate }) => {
|
const NoData = ({ navigate }) => {
|
||||||
return (
|
return (
|
||||||
<View flex={1}>
|
<View style={styles.container}>
|
||||||
<View style={styles.centerItem}>
|
<AppText>{shared.noDataWarning}</AppText>
|
||||||
<AppText>{shared.noDataWarning}</AppText>
|
<Button isCTA onPress={() => {navigate('CycleDay')}}>
|
||||||
<SettingsButton
|
{shared.noDataButtonText}
|
||||||
onPress={() => {navigate('CycleDay')}}
|
</Button>
|
||||||
style={{marginHorizontal: 40}}
|
|
||||||
>
|
|
||||||
{shared.noDataButtonText}
|
|
||||||
</SettingsButton>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
NoData.propTypes = {
|
NoData.propTypes = {
|
||||||
navigate: PropTypes.func,
|
navigate: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NoData
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.centerItems
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return({
|
||||||
|
navigate: (page) => dispatch(navigate(page)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(NoData)
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
import config from '../../config'
|
|
||||||
import { shadesOfRed, cycleDayColor } from '../../styles/index'
|
|
||||||
|
|
||||||
const colorTemperature = '#765285'
|
|
||||||
const colorTemperatureLight = '#a67fb5'
|
|
||||||
export const dotRadius = 5
|
|
||||||
const lineWidth = 1.5
|
|
||||||
const colorLtl = '#feb47b'
|
|
||||||
const gridColor = '#d3d3d3'
|
|
||||||
const gridLineWidthVertical = 0.6
|
|
||||||
const gridLineWidthHorizontal = 0.3
|
|
||||||
const numberLabelFontSize = 13
|
|
||||||
|
|
||||||
const redColor = '#c3000d'
|
|
||||||
const violetColor = '#6a7b98'
|
|
||||||
const shadesOfViolet = ['#e3e7ed', '#c8cfdc', '#acb8cb', '#91a0ba', '#7689a9', violetColor] // light to dark
|
|
||||||
const yellowColor = '#dbb40c'
|
|
||||||
const shadesOfYellow = ['#f0e19d', '#e9d26d', '#e2c33c', yellowColor] // light to dark
|
|
||||||
const magentaColor = '#6f2565'
|
|
||||||
const shadesOfMagenta = ['#a87ca2', '#8b5083', magentaColor] // light to dark
|
|
||||||
const pinkColor = '#9e346c'
|
|
||||||
const shadesOfPink = ['#c485a6', '#b15c89', pinkColor] // light to dark
|
|
||||||
const lightGreenColor = '#bccd67'
|
|
||||||
const orangeColor = '#bc6642'
|
|
||||||
const mintColor = '#6ca299'
|
|
||||||
|
|
||||||
const styles = {
|
|
||||||
container: { flex: 1 },
|
|
||||||
chartContainer: { flexDirection: 'column' },
|
|
||||||
chartArea: { flexDirection: 'row' },
|
|
||||||
centerItem: {
|
|
||||||
flex:1,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
marginHorizontal: 25,
|
|
||||||
},
|
|
||||||
curve: {
|
|
||||||
stroke: colorTemperature,
|
|
||||||
strokeWidth: lineWidth,
|
|
||||||
},
|
|
||||||
curveExcluded: {
|
|
||||||
stroke: colorTemperatureLight,
|
|
||||||
strokeWidth: lineWidth
|
|
||||||
},
|
|
||||||
curveDots: {
|
|
||||||
fill: colorTemperature,
|
|
||||||
r: dotRadius
|
|
||||||
},
|
|
||||||
curveDotsExcluded: {
|
|
||||||
fill: colorTemperatureLight,
|
|
||||||
r: dotRadius
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
label: {
|
|
||||||
date: {
|
|
||||||
color: 'grey',
|
|
||||||
fontSize: 9,
|
|
||||||
fontWeight: '100',
|
|
||||||
textAlign: 'center',
|
|
||||||
paddingTop: 2.5
|
|
||||||
},
|
|
||||||
number: {
|
|
||||||
color: cycleDayColor,
|
|
||||||
fontSize: numberLabelFontSize,
|
|
||||||
textAlign: 'center',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stroke: {
|
|
||||||
color: gridColor,
|
|
||||||
width: gridLineWidthVertical,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
symptomDot: {
|
|
||||||
width: 12,
|
|
||||||
height: 12,
|
|
||||||
borderRadius: 50,
|
|
||||||
},
|
|
||||||
iconColors: {
|
|
||||||
'bleeding': {
|
|
||||||
color: redColor,
|
|
||||||
shades: shadesOfRed,
|
|
||||||
},
|
|
||||||
'mucus': {
|
|
||||||
color: violetColor,
|
|
||||||
shades: shadesOfViolet,
|
|
||||||
},
|
|
||||||
'cervix': {
|
|
||||||
color: yellowColor,
|
|
||||||
shades: shadesOfYellow,
|
|
||||||
},
|
|
||||||
'sex': {
|
|
||||||
color: magentaColor,
|
|
||||||
shades: shadesOfMagenta,
|
|
||||||
},
|
|
||||||
'desire': {
|
|
||||||
color: pinkColor,
|
|
||||||
shades: shadesOfPink,
|
|
||||||
},
|
|
||||||
'pain': {
|
|
||||||
color: lightGreenColor,
|
|
||||||
shades: [lightGreenColor],
|
|
||||||
},
|
|
||||||
'mood': {
|
|
||||||
color: orangeColor,
|
|
||||||
shades: [orangeColor],
|
|
||||||
},
|
|
||||||
'note': {
|
|
||||||
color: mintColor,
|
|
||||||
shades: [mintColor],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
width: 27,
|
|
||||||
borderRightWidth: 1,
|
|
||||||
borderColor: 'lightgrey',
|
|
||||||
borderStyle: 'solid'
|
|
||||||
},
|
|
||||||
yAxisLabels: {
|
|
||||||
tempScale: {
|
|
||||||
position: 'absolute',
|
|
||||||
right: 2,
|
|
||||||
color: 'grey',
|
|
||||||
fontSize: 9,
|
|
||||||
textAlign: 'left'
|
|
||||||
},
|
|
||||||
cycleDayLabel: {
|
|
||||||
textAlign: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
fontSize: Math.ceil(numberLabelFontSize / 2)
|
|
||||||
},
|
|
||||||
dateLabel: {
|
|
||||||
textAlign: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
color: 'grey',
|
|
||||||
fontSize: 9,
|
|
||||||
fontWeight: '100',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
symptomIcon: {
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
chartLegend: {
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
},
|
|
||||||
boldTick: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
fontSize: 11,
|
|
||||||
},
|
|
||||||
horizontalGrid: {
|
|
||||||
position:'absolute',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderBottomColor: gridColor,
|
|
||||||
borderBottomWidth: gridLineWidthHorizontal,
|
|
||||||
width: '100%',
|
|
||||||
left: config.columnWidth
|
|
||||||
},
|
|
||||||
nfpLine: {
|
|
||||||
stroke: colorLtl,
|
|
||||||
strokeWidth: lineWidth,
|
|
||||||
},
|
|
||||||
symptomRow: {
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
loadingMore: {
|
|
||||||
height: '100%',
|
|
||||||
backgroundColor: 'lightgrey',
|
|
||||||
justifyContent: 'center'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default styles
|
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors, Containers } from '../../styles/redesign'
|
||||||
import config from '../../config'
|
import {
|
||||||
|
CHART_COLUMN_WIDTH,
|
||||||
|
CHART_DOT_RADIUS,
|
||||||
|
CHART_GRID_LINE_HORIZONTAL_WIDTH
|
||||||
|
} from '../../config'
|
||||||
|
|
||||||
const SymptomCell = ({
|
const SymptomCell = ({
|
||||||
height,
|
height,
|
||||||
@@ -13,29 +17,23 @@ const SymptomCell = ({
|
|||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const shouldDrawDot = symptomValue !== false
|
const shouldDrawDot = symptomValue !== false
|
||||||
const styleParent = [styles.symptomRow, { height, width: config.columnWidth }]
|
const styleCell = [styles.cell, { height, width: CHART_COLUMN_WIDTH }]
|
||||||
let styleChild
|
let styleDot
|
||||||
|
|
||||||
if (shouldDrawDot) {
|
if (shouldDrawDot) {
|
||||||
const styleSymptom = styles.iconColors[symptom]
|
const styleSymptom = Colors.iconColors[symptom]
|
||||||
const symptomColor = styleSymptom.shades[symptomValue]
|
const symptomColor = styleSymptom.shades[symptomValue]
|
||||||
|
|
||||||
const isMucusOrCervix = (symptom === 'mucus') || (symptom === 'cervix')
|
const isMucusOrCervix = (symptom === 'mucus') || (symptom === 'cervix')
|
||||||
|
|
||||||
const backgroundColor = (isMucusOrCervix && !isSymptomDataComplete) ?
|
const backgroundColor = (isMucusOrCervix && !isSymptomDataComplete) ?
|
||||||
'white' : symptomColor
|
'white' : symptomColor
|
||||||
const borderWidth = (isMucusOrCervix && !isSymptomDataComplete) ? 2 : 0
|
const borderWidth = (isMucusOrCervix && !isSymptomDataComplete) ? 2 : 0
|
||||||
const borderColor = symptomColor
|
const borderColor = symptomColor
|
||||||
styleChild = [styles.symptomDot, {
|
styleDot = [styles.dot, { backgroundColor, borderColor, borderWidth }]
|
||||||
backgroundColor,
|
|
||||||
borderColor,
|
|
||||||
borderWidth
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styleParent} key={symptom}>
|
<View style={styleCell} key={symptom}>
|
||||||
{shouldDrawDot && <View style={styleChild} />}
|
{shouldDrawDot && <View style={styleDot} />}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -50,4 +48,17 @@ SymptomCell.propTypes = {
|
|||||||
isSymptomDataComplete: PropTypes.bool,
|
isSymptomDataComplete: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
cell: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderColor: Colors.greyLight,
|
||||||
|
borderWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
|
||||||
|
...Containers.centerItems
|
||||||
|
},
|
||||||
|
dot: {
|
||||||
|
width: CHART_DOT_RADIUS * 2,
|
||||||
|
height: CHART_DOT_RADIUS * 2,
|
||||||
|
borderRadius: 50
|
||||||
|
}
|
||||||
|
})
|
||||||
export default SymptomCell
|
export default SymptomCell
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet , View } from 'react-native'
|
||||||
|
|
||||||
import DripIcon from '../../assets/drip-icons'
|
import DripIcon from '../../assets/drip-icons'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors, Containers } from '../../styles/redesign'
|
||||||
|
import { CHART_YAXIS_WIDTH, CHART_ICON_SIZE } from '../../config'
|
||||||
|
|
||||||
const SymptomIcon = ({ symptom, height }) => {
|
const SymptomIcon = ({ symptom, height }) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.symptomIcon} width={styles.yAxis.width} height={height}>
|
<View style={styles.container} width={CHART_YAXIS_WIDTH} height={height}>
|
||||||
<DripIcon
|
<DripIcon
|
||||||
size={16}
|
size={CHART_ICON_SIZE}
|
||||||
name={`drip-icon-${symptom}`}
|
name={`drip-icon-${symptom}`}
|
||||||
color={styles.iconColors[symptom].color}
|
color={Colors.iconColors[symptom].color}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
@@ -23,4 +24,10 @@ SymptomIcon.propTypes = {
|
|||||||
symptom: PropTypes.string,
|
symptom: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.centerItems
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default SymptomIcon
|
export default SymptomIcon
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet } from 'react-native'
|
||||||
|
|
||||||
import { Surface , Path } from 'react-native/Libraries/ART/ReactNativeART'
|
import { Surface , Path } from 'react-native/Libraries/ART/ReactNativeART'
|
||||||
|
|
||||||
import ChartLine from './chart-line'
|
import ChartLine from './chart-line'
|
||||||
import DotAndLine from './dot-and-line'
|
import DotAndLine from './dot-and-line'
|
||||||
|
|
||||||
import styles from './styles'
|
import { CHART_COLUMN_WIDTH, CHART_STROKE_WIDTH } from '../../config'
|
||||||
import config from '../../config'
|
|
||||||
|
|
||||||
const TemperatureColumn = ({
|
const TemperatureColumn = ({
|
||||||
horizontalLinePosition,
|
horizontalLinePosition,
|
||||||
@@ -15,20 +15,21 @@ const TemperatureColumn = ({
|
|||||||
data,
|
data,
|
||||||
columnHeight
|
columnHeight
|
||||||
}) => {
|
}) => {
|
||||||
|
const x = CHART_STROKE_WIDTH / 2
|
||||||
const x = styles.nfpLine.strokeWidth / 2
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Surface width={config.columnWidth} height={columnHeight}>
|
<Surface
|
||||||
|
width={CHART_COLUMN_WIDTH}
|
||||||
|
height={columnHeight}
|
||||||
|
style={styles.container}
|
||||||
|
>
|
||||||
|
|
||||||
<ChartLine
|
<ChartLine path={new Path().lineTo(0, columnHeight)}/>
|
||||||
path={new Path().lineTo(0, columnHeight)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{horizontalLinePosition && <ChartLine
|
{horizontalLinePosition && <ChartLine
|
||||||
path={new Path()
|
path={new Path()
|
||||||
.moveTo(0, horizontalLinePosition)
|
.moveTo(0, horizontalLinePosition)
|
||||||
.lineTo(config.columnWidth, horizontalLinePosition)
|
.lineTo(CHART_COLUMN_WIDTH, horizontalLinePosition)
|
||||||
}
|
}
|
||||||
isNfpLine={true}
|
isNfpLine={true}
|
||||||
key='ltl'
|
key='ltl'
|
||||||
@@ -61,4 +62,10 @@ TemperatureColumn.propTypes = {
|
|||||||
columnHeight: PropTypes.number,
|
columnHeight: PropTypes.number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default TemperatureColumn
|
export default TemperatureColumn
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import Tick from './tick'
|
import Tick from './tick'
|
||||||
|
|
||||||
import { getTickList } from '../helpers/chart'
|
import { getTickList } from '../helpers/chart'
|
||||||
|
|
||||||
import styles from './styles'
|
|
||||||
|
|
||||||
const TickList = ({ height }) => {
|
const TickList = ({ height }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.yAxis, { height }]}>{
|
<View style={[styles.container, height]}>
|
||||||
getTickList(height)
|
{
|
||||||
.map(({ label, position, isBold, shouldShowLabel}) => {
|
getTickList(height)
|
||||||
return (
|
.map(({ isBold, label, position, shouldShowLabel, tickHeight}) => {
|
||||||
<Tick
|
return (
|
||||||
key={label}
|
<Tick
|
||||||
yPosition={position}
|
height={tickHeight}
|
||||||
isBold={isBold}
|
isBold={isBold}
|
||||||
shouldShowLabel={shouldShowLabel}
|
key={label}
|
||||||
label={label}
|
label={label}
|
||||||
/>
|
shouldShowLabel={shouldShowLabel}
|
||||||
)
|
yPosition={position}
|
||||||
})
|
/>
|
||||||
}</View>
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,4 +33,10 @@ TickList.propTypes = {
|
|||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default TickList
|
export default TickList
|
||||||
|
|||||||
+36
-12
@@ -1,29 +1,53 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import AppText from '../common/app-text'
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Sizes } from '../../styles/redesign'
|
||||||
|
|
||||||
const Tick = ({ yPosition, isBold, shouldShowLabel, label }) => {
|
const Tick = ({ yPosition, height, isBold, shouldShowLabel, label }) => {
|
||||||
// this eyeballing is sadly necessary because RN does not
|
const top = yPosition - height / 2
|
||||||
// support percentage values for transforms, which we'd need
|
const containerStyle = [ styles.container, { flexBasis: height, height, top }]
|
||||||
// to reliably place the label vertically centered to the grid
|
const textStyle = isBold ? styles.textBold : styles.textNormal
|
||||||
const topPosition = yPosition - 8
|
|
||||||
const style = [
|
|
||||||
styles.yAxisLabels.tempScale,
|
|
||||||
{top: topPosition},
|
|
||||||
isBold && styles.boldTick
|
|
||||||
]
|
|
||||||
|
|
||||||
return <AppText style={style}>{shouldShowLabel && label}</AppText>
|
return(
|
||||||
|
<View style={containerStyle}>
|
||||||
|
<AppText style={textStyle}>{shouldShowLabel && label}</AppText>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Tick.propTypes = {
|
Tick.propTypes = {
|
||||||
yPosition: PropTypes.number,
|
yPosition: PropTypes.number,
|
||||||
|
height: PropTypes.number.isRequired,
|
||||||
isBold: PropTypes.bool,
|
isBold: PropTypes.bool,
|
||||||
shouldShowLabel: PropTypes.bool,
|
shouldShowLabel: PropTypes.bool,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const text = {
|
||||||
|
right: 4,
|
||||||
|
lineHeight: Sizes.base,
|
||||||
|
textAlign: 'right',
|
||||||
|
textAlignVertical: 'center'
|
||||||
|
}
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
width: 40
|
||||||
|
},
|
||||||
|
textBold: {
|
||||||
|
fontSize: Sizes.base,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
...text
|
||||||
|
},
|
||||||
|
textNormal: {
|
||||||
|
fontSize: Sizes.small,
|
||||||
|
...text
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default Tick
|
export default Tick
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Image, StyleSheet, TouchableOpacity, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppIcon from '../common/app-icon'
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
|
import { Colors, Containers, Sizes, Spacing } from '../../styles/redesign'
|
||||||
|
import { chart } from '../../i18n/en/labels'
|
||||||
|
|
||||||
|
const image = require('../../assets/swipe.png')
|
||||||
|
|
||||||
|
const Tutorial = ({ onClose }) => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Image resizeMode='contain' source={image} style={styles.image} />
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<AppText>{chart.tutorial}</AppText>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity onPress={onClose} style={styles.iconContainer}>
|
||||||
|
<AppIcon name='cross' color={Colors.orange} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Tutorial.propTypes = {
|
||||||
|
onClose: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.rowContainer,
|
||||||
|
padding: Spacing.large
|
||||||
|
},
|
||||||
|
iconContainer: {
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
marginBottom: Sizes.base
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
height: 40
|
||||||
|
},
|
||||||
|
textContainer: {
|
||||||
|
width: '70%'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Tutorial
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import SymptomIcon from './symptom-icon'
|
import SymptomIcon from './symptom-icon'
|
||||||
import TickList from './tick-list'
|
import TickList from './tick-list'
|
||||||
import ChartLegend from './chart-legend'
|
import ChartLegend from './chart-legend'
|
||||||
|
|
||||||
import styles from './styles'
|
import { CHART_YAXIS_WIDTH } from '../../config'
|
||||||
|
|
||||||
const YAxis = ({
|
const YAxis = ({
|
||||||
height,
|
height,
|
||||||
@@ -19,6 +19,8 @@ const YAxis = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
|
{shouldShowTemperatureColumn && <TickList height={height} />}
|
||||||
|
<ChartLegend height={xAxisHeight} />
|
||||||
<View style={[styles.yAxis, {height: symptomsSectionHeight}]}>
|
<View style={[styles.yAxis, {height: symptomsSectionHeight}]}>
|
||||||
{symptomsToDisplay.map(symptom => (
|
{symptomsToDisplay.map(symptom => (
|
||||||
<SymptomIcon
|
<SymptomIcon
|
||||||
@@ -29,8 +31,6 @@ const YAxis = ({
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{shouldShowTemperatureColumn && <TickList height={height} />}
|
|
||||||
<ChartLegend xAxisHeight={xAxisHeight} />
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -43,4 +43,10 @@ YAxis.propTypes = {
|
|||||||
xAxisHeight: PropTypes.number.isRequired
|
xAxisHeight: PropTypes.number.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
yAxis: {
|
||||||
|
width: CHART_YAXIS_WIDTH
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default YAxis
|
export default YAxis
|
||||||
|
|||||||
@@ -1,26 +1,36 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { ScrollView, StyleSheet } from 'react-native'
|
import { ScrollView, StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import AppText from '../common/app-text'
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import { Colors, Typography } from '../../styles/redesign'
|
import { Colors, Typography } from '../../styles/redesign'
|
||||||
|
|
||||||
const AppPage = ({ children, contentContainerStyle, title }) => {
|
const AppPage = ({
|
||||||
|
children,
|
||||||
|
contentContainerStyle,
|
||||||
|
scrollViewStyle,
|
||||||
|
title,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
return(
|
return(
|
||||||
<ScrollView
|
<View style={styles.container}>
|
||||||
style={styles.container}
|
<ScrollView
|
||||||
contentContainerStyle={contentContainerStyle}
|
contentContainerStyle={[styles.scrollView, contentContainerStyle]}
|
||||||
>
|
style={scrollViewStyle}
|
||||||
{title && <AppText style={styles.title}>{title}</AppText>}
|
{...props}
|
||||||
{children}
|
>
|
||||||
</ScrollView>
|
{title && <AppText style={styles.title}>{title}</AppText>}
|
||||||
|
{children}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
AppPage.propTypes = {
|
AppPage.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
contentContainerStyle: PropTypes.object,
|
contentContainerStyle: PropTypes.object,
|
||||||
|
scrollViewStyle: PropTypes.object,
|
||||||
title: PropTypes.string
|
title: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +39,10 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: Colors.tourquiseLight,
|
backgroundColor: Colors.tourquiseLight,
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
|
scrollView: {
|
||||||
|
backgroundColor: Colors.tourquiseLight,
|
||||||
|
flexGrow: 1
|
||||||
|
},
|
||||||
title: {
|
title: {
|
||||||
...Typography.title
|
...Typography.title
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import styles from '../../../styles'
|
|||||||
|
|
||||||
import { getPreviousTemperature } from '../../../db'
|
import { getPreviousTemperature } from '../../../db'
|
||||||
import { scaleObservable } from '../../../local-storage'
|
import { scaleObservable } from '../../../local-storage'
|
||||||
import config from '../../../config'
|
import { TEMP_MAX, TEMP_MIN } from '../../../config'
|
||||||
|
|
||||||
export default class TemperatureInput extends Component {
|
export default class TemperatureInput extends Component {
|
||||||
|
|
||||||
@@ -86,8 +86,7 @@ const OutOfRangeWarning = ({ temperature }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const value = Number(temperature)
|
const value = Number(temperature)
|
||||||
const { min, max } = config.temperatureScale
|
const range = { min: TEMP_MIN, max: TEMP_MAX }
|
||||||
const range = { min, max }
|
|
||||||
const scale = scaleObservable.value
|
const scale = scaleObservable.value
|
||||||
|
|
||||||
let warningMsg
|
let warningMsg
|
||||||
|
|||||||
+108
-17
@@ -1,10 +1,9 @@
|
|||||||
import { LocalDate } from 'js-joda'
|
import { LocalDate } from 'js-joda'
|
||||||
|
|
||||||
import { scaleObservable, unitObservable } from '../../local-storage'
|
import { scaleObservable, unitObservable } from '../../local-storage'
|
||||||
|
import { getCycleStatusForDay } from '../../lib/sympto-adapter'
|
||||||
import { getCycleDay, getAmountOfCycleDays } from '../../db'
|
import { getCycleDay, getAmountOfCycleDays } from '../../db'
|
||||||
|
|
||||||
import config from '../../config'
|
|
||||||
|
|
||||||
//YAxis helpers
|
//YAxis helpers
|
||||||
|
|
||||||
export function normalizeToScale(temp, columnHeight) {
|
export function normalizeToScale(temp, columnHeight) {
|
||||||
@@ -14,17 +13,21 @@ export function normalizeToScale(temp, columnHeight) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAbsoluteValue(relative, columnHeight) {
|
function getAbsoluteValue(relative, columnHeight) {
|
||||||
// we add some height to have some breathing room
|
return columnHeight * relative
|
||||||
const verticalPadding = columnHeight * config.temperatureScale.verticalPadding
|
}
|
||||||
const scaleHeight = columnHeight - 2 * verticalPadding
|
|
||||||
return scaleHeight * relative + verticalPadding
|
function getTickConfig() {
|
||||||
|
const unit = unitObservable.value
|
||||||
|
//Add 1 tick above the max value to display on chart
|
||||||
|
const scaleMax = scaleObservable.value.max + unit
|
||||||
|
const scaleMin = scaleObservable.value.min - unit
|
||||||
|
const numberOfTicks = (scaleMax - scaleMin) / unit + 1
|
||||||
|
|
||||||
|
return { numberOfTicks, scaleMax, scaleMin, unit }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTickPositions(columnHeight) {
|
export function getTickPositions(columnHeight) {
|
||||||
const units = unitObservable.value
|
const { numberOfTicks } = getTickConfig()
|
||||||
const scaleMin = scaleObservable.value.min
|
|
||||||
const scaleMax = scaleObservable.value.max
|
|
||||||
const numberOfTicks = (scaleMax - scaleMin) * (1 / units) + 1
|
|
||||||
const tickDistance = 1 / (numberOfTicks - 1)
|
const tickDistance = 1 / (numberOfTicks - 1)
|
||||||
const tickPositions = []
|
const tickPositions = []
|
||||||
for (let i = 0; i < numberOfTicks; i++) {
|
for (let i = 0; i < numberOfTicks; i++) {
|
||||||
@@ -35,13 +38,12 @@ export function getTickPositions(columnHeight) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getTickList(columnHeight) {
|
export function getTickList(columnHeight) {
|
||||||
|
const { numberOfTicks, scaleMax, unit } = getTickConfig()
|
||||||
const units = unitObservable.value
|
const tickHeight = columnHeight / numberOfTicks
|
||||||
const scaleMax = scaleObservable.value.max
|
|
||||||
|
|
||||||
return getTickPositions(columnHeight).map((tickPosition, i) => {
|
return getTickPositions(columnHeight).map((tickPosition, i) => {
|
||||||
|
|
||||||
const tick = scaleMax - i * units
|
const tick = scaleMax - i * unit
|
||||||
let isBold, label, shouldShowLabel
|
let isBold, label, shouldShowLabel
|
||||||
|
|
||||||
if (Number.isInteger(tick)) {
|
if (Number.isInteger(tick)) {
|
||||||
@@ -52,10 +54,10 @@ export function getTickList(columnHeight) {
|
|||||||
label = tick.toString()
|
label = tick.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
// when temp range <= 3, units === 0.1 we show temp values with step 0.2
|
// when temp range <= 2, units === 0.1 we show temp values with step 0.2
|
||||||
// when temp range > 3, units === 0.5 we show temp values with step 0.5
|
// when temp range > 2, units === 0.5 we show temp values with step 0.5
|
||||||
|
|
||||||
if (units === 0.1) {
|
if (unit === 0.1) {
|
||||||
// show label with step 0.2
|
// show label with step 0.2
|
||||||
shouldShowLabel = !(tick * 10 % 2)
|
shouldShowLabel = !(tick * 10 % 2)
|
||||||
} else {
|
} else {
|
||||||
@@ -63,11 +65,17 @@ export function getTickList(columnHeight) {
|
|||||||
shouldShowLabel = !(tick * 10 % 5)
|
shouldShowLabel = !(tick * 10 % 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// don't show label, if first or last tick
|
||||||
|
if ( i === 0 || i === (numberOfTicks - 1) ) {
|
||||||
|
shouldShowLabel = false
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
position: tickPosition,
|
position: tickPosition,
|
||||||
label,
|
label,
|
||||||
isBold,
|
isBold,
|
||||||
shouldShowLabel,
|
shouldShowLabel,
|
||||||
|
tickHeight
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -201,4 +209,87 @@ function getTodayAndPreviousDays(n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return getDaysInRange(today, [])
|
return getDaysInRange(today, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nfpLines() {
|
||||||
|
const cycle = {
|
||||||
|
status: null
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCurrentCycle(dateString) {
|
||||||
|
// for the NFP lines, we don't care about potentially extending the
|
||||||
|
// preOvu phase, so we don't include all earlier cycles, as that is
|
||||||
|
// an expensive db operation at the moment
|
||||||
|
cycle.status = getCycleStatusForDay(
|
||||||
|
dateString, { excludeEarlierCycles: true }
|
||||||
|
)
|
||||||
|
if(!cycle.status) {
|
||||||
|
cycle.noMoreCycles = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (cycle.status.phases.preOvulatory) {
|
||||||
|
cycle.startDate = cycle.status.phases.preOvulatory.start.date
|
||||||
|
} else {
|
||||||
|
cycle.startDate = cycle.status.phases.periOvulatory.start.date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dateIsInPeriOrPostPhase(dateString) {
|
||||||
|
return (
|
||||||
|
dateString >= cycle.status.phases.periOvulatory.start.date
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function precededByAnotherTempValue(dateString) {
|
||||||
|
return (
|
||||||
|
// we are only interested in days that have a preceding
|
||||||
|
// temp
|
||||||
|
Object.keys(cycle.status.phases).some(phaseName => {
|
||||||
|
return cycle.status.phases[phaseName].cycleDays.some(day => {
|
||||||
|
return day.temperature && day.date < dateString
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// and also a following temp, so we don't draw the line
|
||||||
|
// longer than necessary
|
||||||
|
&&
|
||||||
|
cycle.status.phases.postOvulatory.cycleDays.some(day => {
|
||||||
|
return day.temperature && day.date > dateString
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInTempMeasuringPhase(temperature, dateString) {
|
||||||
|
return (
|
||||||
|
temperature || precededByAnotherTempValue(dateString)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(dateString, temperature, columnHeight) {
|
||||||
|
const ret = {
|
||||||
|
drawLtlAt: null,
|
||||||
|
drawFhmLine: false
|
||||||
|
}
|
||||||
|
if (!cycle.status && !cycle.noMoreCycles) updateCurrentCycle(dateString)
|
||||||
|
if (cycle.noMoreCycles) return ret
|
||||||
|
|
||||||
|
if (dateString < cycle.startDate) updateCurrentCycle(dateString)
|
||||||
|
if (cycle.noMoreCycles) return ret
|
||||||
|
|
||||||
|
const tempShift = cycle.status.temperatureShift
|
||||||
|
|
||||||
|
if (tempShift) {
|
||||||
|
if (tempShift.firstHighMeasurementDay.date === dateString) {
|
||||||
|
ret.drawFhmLine = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
dateIsInPeriOrPostPhase(dateString) &&
|
||||||
|
isInTempMeasuringPhase(temperature, dateString)
|
||||||
|
) {
|
||||||
|
ret.drawLtlAt = normalizeToScale(tempShift.ltl, columnHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ import SliderLabel from './slider-label'
|
|||||||
import { scaleObservable, saveTempScale } from '../../../local-storage'
|
import { scaleObservable, saveTempScale } from '../../../local-storage'
|
||||||
import { Colors, Sizes } from '../../../styles/redesign'
|
import { Colors, Sizes } from '../../../styles/redesign'
|
||||||
import labels from '../../../i18n/en/settings'
|
import labels from '../../../i18n/en/settings'
|
||||||
import config from '../../../config'
|
import { TEMP_MIN, TEMP_MAX, TEMP_SLIDER_STEP } from '../../../config'
|
||||||
|
|
||||||
export default class TemperatureSlider extends Component {
|
export default class TemperatureSlider extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -41,11 +41,11 @@ export default class TemperatureSlider extends Component {
|
|||||||
enableLabel={true}
|
enableLabel={true}
|
||||||
markerStyle={styles.marker}
|
markerStyle={styles.marker}
|
||||||
markerOffsetY={Sizes.tiny}
|
markerOffsetY={Sizes.tiny}
|
||||||
max={config.temperatureScale.max}
|
max={TEMP_MAX}
|
||||||
min={config.temperatureScale.min}
|
min={TEMP_MIN}
|
||||||
onValuesChange={this.onTemperatureSliderChange}
|
onValuesChange={this.onTemperatureSliderChange}
|
||||||
selectedStyle={styles.sliderAccentBackground}
|
selectedStyle={styles.sliderAccentBackground}
|
||||||
step={config.temperatureScale.step}
|
step={TEMP_SLIDER_STEP}
|
||||||
trackStyle={styles.slider}
|
trackStyle={styles.slider}
|
||||||
unselectedStyle={styles.sliderBackground}
|
unselectedStyle={styles.sliderBackground}
|
||||||
values={[minTemperature, maxTemperature]}
|
values={[minTemperature, maxTemperature]}
|
||||||
|
|||||||
@@ -1,34 +1,31 @@
|
|||||||
const config = {
|
|
||||||
columnWidth: 25,
|
|
||||||
xAxisHeightPercentage: 0.08,
|
|
||||||
xAxisHeightPercentageLarge: 0.12,
|
|
||||||
symptomHeightPercentage: 0.05,
|
|
||||||
symptomHeightPercentageLarge: 0.1,
|
|
||||||
temperatureScale: {
|
|
||||||
defaultLow: 35,
|
|
||||||
defaultHigh: 38,
|
|
||||||
min: 34,
|
|
||||||
max: 40,
|
|
||||||
step: 0.5,
|
|
||||||
units: 0.1,
|
|
||||||
verticalPadding: 0.03
|
|
||||||
},
|
|
||||||
symptoms: [
|
|
||||||
'bleeding',
|
|
||||||
'mucus',
|
|
||||||
'cervix',
|
|
||||||
'sex',
|
|
||||||
'desire',
|
|
||||||
'pain',
|
|
||||||
'mood',
|
|
||||||
'note'
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
config.columnMiddle = config.columnWidth / 2
|
|
||||||
|
|
||||||
export const ACTION_DELETE = 'delete'
|
export const ACTION_DELETE = 'delete'
|
||||||
export const ACTION_EXPORT = 'export'
|
export const ACTION_EXPORT = 'export'
|
||||||
export const ACTION_IMPORT = 'import'
|
export const ACTION_IMPORT = 'import'
|
||||||
|
|
||||||
export default config
|
export const SYMPTOMS = [
|
||||||
|
'bleeding',
|
||||||
|
'mucus',
|
||||||
|
'cervix',
|
||||||
|
'sex',
|
||||||
|
'desire',
|
||||||
|
'pain',
|
||||||
|
'mood',
|
||||||
|
'note'
|
||||||
|
]
|
||||||
|
|
||||||
|
export const CHART_COLUMN_WIDTH = 32
|
||||||
|
export const CHART_COLUMN_MIDDLE = CHART_COLUMN_WIDTH / 2
|
||||||
|
export const CHART_DOT_RADIUS = 6
|
||||||
|
export const CHART_GRID_LINE_HORIZONTAL_WIDTH = 0.3
|
||||||
|
export const CHART_ICON_SIZE = 20
|
||||||
|
export const CHART_STROKE_WIDTH = 3
|
||||||
|
export const CHART_SYMPTOM_HEIGHT_RATIO = 0.1
|
||||||
|
export const CHART_XAXIS_HEIGHT_RATIO = 0.14
|
||||||
|
export const CHART_YAXIS_WIDTH = 32
|
||||||
|
|
||||||
|
export const TEMP_SCALE_MAX = 38
|
||||||
|
export const TEMP_SCALE_MIN = 35
|
||||||
|
export const TEMP_SCALE_UNITS = 0.1
|
||||||
|
export const TEMP_MAX = 40
|
||||||
|
export const TEMP_MIN = 34
|
||||||
|
export const TEMP_SLIDER_STEP = 0.5
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ export const homeRedesign = {
|
|||||||
addData: 'add data for today'
|
addData: 'add data for today'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const chart = {
|
||||||
|
tutorial: 'You can swipe the chart to view more dates.'
|
||||||
|
}
|
||||||
|
|
||||||
export const shared = {
|
export const shared = {
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
|
|||||||
+14
-7
@@ -1,18 +1,16 @@
|
|||||||
import { AsyncStorage } from 'react-native'
|
import { AsyncStorage } from 'react-native'
|
||||||
import Observable from 'obv'
|
import Observable from 'obv'
|
||||||
import config from './config'
|
import { TEMP_SCALE_MIN, TEMP_SCALE_MAX, TEMP_SCALE_UNITS } from './config'
|
||||||
|
|
||||||
export const scaleObservable = Observable()
|
export const scaleObservable = Observable()
|
||||||
setObvWithInitValue('tempScale', scaleObservable, {
|
setObvWithInitValue('tempScale',
|
||||||
min: config.temperatureScale.defaultLow,
|
scaleObservable, { min: TEMP_SCALE_MIN, max: TEMP_SCALE_MAX })
|
||||||
max: config.temperatureScale.defaultHigh
|
|
||||||
})
|
|
||||||
|
|
||||||
export const unitObservable = Observable()
|
export const unitObservable = Observable()
|
||||||
unitObservable.set(config.temperatureScale.units)
|
unitObservable.set(TEMP_SCALE_UNITS)
|
||||||
scaleObservable((scale) => {
|
scaleObservable((scale) => {
|
||||||
const scaleRange = scale.max - scale.min
|
const scaleRange = scale.max - scale.min
|
||||||
if (scaleRange <= 3) {
|
if (scaleRange <= 2) {
|
||||||
unitObservable.set(0.1)
|
unitObservable.set(0.1)
|
||||||
} else {
|
} else {
|
||||||
unitObservable.set(0.5)
|
unitObservable.set(0.5)
|
||||||
@@ -69,6 +67,15 @@ export async function saveLicenseFlag() {
|
|||||||
await AsyncStorage.setItem('agreedToLicense', JSON.stringify(true))
|
await AsyncStorage.setItem('agreedToLicense', JSON.stringify(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getChartFlag() {
|
||||||
|
const isFirstChartView = await AsyncStorage.getItem('isFirstChartView')
|
||||||
|
return isFirstChartView === null ? 'true' : isFirstChartView
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setChartFlag() {
|
||||||
|
await AsyncStorage.setItem('isFirstChartView', JSON.stringify(false))
|
||||||
|
}
|
||||||
|
|
||||||
async function setObvWithInitValue(key, obv, defaultValue) {
|
async function setObvWithInitValue(key, obv, defaultValue) {
|
||||||
const result = await AsyncStorage.getItem(key)
|
const result = await AsyncStorage.getItem(key)
|
||||||
let value
|
let value
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
|
const redColor = '#c3000d'
|
||||||
|
export const shadesOfRed = ['#e7999e', '#db666d', '#cf323d', '#c3000d'] // light to dark
|
||||||
|
const violetColor = '#6a7b98'
|
||||||
|
const shadesOfViolet = ['#e3e7ed', '#c8cfdc', '#acb8cb', '#91a0ba', '#7689a9', violetColor] // light to dark
|
||||||
|
const yellowColor = '#dbb40c'
|
||||||
|
const shadesOfYellow = ['#f0e19d', '#e9d26d', '#e2c33c', yellowColor] // light to dark
|
||||||
|
const magentaColor = '#6f2565'
|
||||||
|
const shadesOfMagenta = ['#a87ca2', '#8b5083', magentaColor] // light to dark
|
||||||
|
const pinkColor = '#9e346c'
|
||||||
|
const shadesOfPink = ['#c485a6', '#b15c89', pinkColor] // light to dark
|
||||||
|
const lightGreenColor = '#bccd67'
|
||||||
|
const orangeColor = '#bc6642'
|
||||||
|
const mintColor = '#6ca299'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
greyDark: '#A5A5A5',
|
greyDark: '#A5A5A5',
|
||||||
grey: '#D2D2D2',
|
grey: '#D2D2D2',
|
||||||
@@ -8,4 +22,38 @@ export default {
|
|||||||
tourquiseDark: '#69CBC1',
|
tourquiseDark: '#69CBC1',
|
||||||
tourquise: '#CFECEA',
|
tourquise: '#CFECEA',
|
||||||
tourquiseLight: '#E9F2ED',
|
tourquiseLight: '#E9F2ED',
|
||||||
|
iconColors: {
|
||||||
|
'bleeding': {
|
||||||
|
color: redColor,
|
||||||
|
shades: shadesOfRed,
|
||||||
|
},
|
||||||
|
'mucus': {
|
||||||
|
color: violetColor,
|
||||||
|
shades: shadesOfViolet,
|
||||||
|
},
|
||||||
|
'cervix': {
|
||||||
|
color: yellowColor,
|
||||||
|
shades: shadesOfYellow,
|
||||||
|
},
|
||||||
|
'sex': {
|
||||||
|
color: magentaColor,
|
||||||
|
shades: shadesOfMagenta,
|
||||||
|
},
|
||||||
|
'desire': {
|
||||||
|
color: pinkColor,
|
||||||
|
shades: shadesOfPink,
|
||||||
|
},
|
||||||
|
'pain': {
|
||||||
|
color: lightGreenColor,
|
||||||
|
shades: [lightGreenColor],
|
||||||
|
},
|
||||||
|
'mood': {
|
||||||
|
color: orangeColor,
|
||||||
|
shades: [orangeColor],
|
||||||
|
},
|
||||||
|
'note': {
|
||||||
|
color: mintColor,
|
||||||
|
shades: [mintColor],
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
+23
-5
@@ -8,6 +8,7 @@ export const fonts = {
|
|||||||
|
|
||||||
export const sizes = {
|
export const sizes = {
|
||||||
tiny: 7,
|
tiny: 7,
|
||||||
|
footnote: 10,
|
||||||
small: 14,
|
small: 14,
|
||||||
base: 18,
|
base: 18,
|
||||||
subtitle: 22,
|
subtitle: 22,
|
||||||
@@ -15,11 +16,6 @@ export const sizes = {
|
|||||||
huge: 40
|
huge: 40
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = {
|
|
||||||
color: Colors.purple,
|
|
||||||
marginVertical: Spacing.large
|
|
||||||
}
|
|
||||||
|
|
||||||
const accentText = {
|
const accentText = {
|
||||||
fontFamily: fonts.bold,
|
fontFamily: fonts.bold,
|
||||||
textAlignVertical: 'center',
|
textAlignVertical: 'center',
|
||||||
@@ -46,6 +42,16 @@ const accentTextSmall = {
|
|||||||
fontSize: sizes.small
|
fontSize: sizes.small
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const title = {
|
||||||
|
color: Colors.purple,
|
||||||
|
marginVertical: Spacing.large
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = {
|
||||||
|
fontSize: sizes.small,
|
||||||
|
textTransform: 'uppercase'
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
accentOrange: {
|
accentOrange: {
|
||||||
...accentTextSmall,
|
...accentTextSmall,
|
||||||
@@ -67,6 +73,18 @@ export default {
|
|||||||
fontFamily: fonts.main,
|
fontFamily: fonts.main,
|
||||||
fontSize: sizes.base
|
fontSize: sizes.base
|
||||||
},
|
},
|
||||||
|
label: {
|
||||||
|
...label
|
||||||
|
},
|
||||||
|
labelBold: {
|
||||||
|
color: Colors.greyDark,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
...label
|
||||||
|
},
|
||||||
|
labelLight: {
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: sizes.footnote,
|
||||||
|
},
|
||||||
subtitle: {
|
subtitle: {
|
||||||
fontSize: sizes.subtitle,
|
fontSize: sizes.subtitle,
|
||||||
...title
|
...title
|
||||||
|
|||||||
Reference in New Issue
Block a user