Redesign chart

This commit is contained in:
Maria Zadnepryanets
2020-08-01 11:37:20 +00:00
committed by Sofiya Tepikin
parent 550b1e6314
commit ef16cfd041
27 changed files with 718 additions and 575 deletions
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+22 -16
View File
@@ -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
+11 -11
View File
@@ -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
View File
@@ -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)),
+36 -15
View File
@@ -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
+12 -12
View File
@@ -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>
) )
} }
+54 -40
View File
@@ -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()}
/>
}
+17 -10
View File
@@ -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
-85
View File
@@ -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
View File
@@ -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)
-174
View File
@@ -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
+26 -15
View File
@@ -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
+12 -5
View File
@@ -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
+16 -9
View File
@@ -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
+25 -17
View File
@@ -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
View File
@@ -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
+48
View File
@@ -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
+10 -4
View File
@@ -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
+23 -9
View File
@@ -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
View File
@@ -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]}
+27 -30
View File
@@ -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
+4
View File
@@ -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
View File
@@ -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
+48
View File
@@ -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
View File
@@ -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