Merge branch '133-make-temp-scale-a-config-setting-and-read-it-in-chart-and-temp-screen' into 'master'
Resolve "make temp scale a config setting and read it in chart and temp screen" Closes #133 See merge request bloodyhealth/drip!59
This commit is contained in:
@@ -49,6 +49,13 @@
|
|||||||
"prefer-const": "error",
|
"prefer-const": "error",
|
||||||
"no-trailing-spaces": "error",
|
"no-trailing-spaces": "error",
|
||||||
"react/prop-types": 0,
|
"react/prop-types": 0,
|
||||||
"max-len": [1, {"ignoreStrings": true}]
|
"max-len": [
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
"ignoreStrings": true,
|
||||||
|
"ignoreComments": true,
|
||||||
|
"ignoreTemplateLiterals": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+94
-60
@@ -1,51 +1,123 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { View, FlatList, ScrollView } from 'react-native'
|
import { View, FlatList, Text } from 'react-native'
|
||||||
import range from 'date-range'
|
import range from 'date-range'
|
||||||
import { LocalDate } from 'js-joda'
|
import { LocalDate } from 'js-joda'
|
||||||
import { yAxis, normalizeToScale, horizontalGrid } from './y-axis'
|
import { makeYAxisLabels, normalizeToScale, makeHorizontalGrid } from './y-axis'
|
||||||
import setUpFertilityStatusFunc from './nfp-lines'
|
import nfpLines from './nfp-lines'
|
||||||
import DayColumn from './day-column'
|
import DayColumn from './day-column'
|
||||||
import { getCycleDay, cycleDaysSortedByDate, getAmountOfCycleDays } from '../../db'
|
import { getCycleDay, cycleDaysSortedByDate, getAmountOfCycleDays } from '../../db'
|
||||||
import styles from './styles'
|
import styles from './styles'
|
||||||
|
import { scaleObservable } from '../../local-storage'
|
||||||
const yAxisView = <View {...styles.yAxis}>{yAxis.labels}</View>
|
import config from '../../config'
|
||||||
|
|
||||||
export default class CycleChart extends Component {
|
export default class CycleChart extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {}
|
||||||
columns: makeColumnInfo(setUpFertilityStatusFunc()),
|
|
||||||
}
|
|
||||||
this.renderColumn = ({item, index}) => {
|
this.renderColumn = ({item, index}) => {
|
||||||
return (
|
return (
|
||||||
<DayColumn
|
<DayColumn
|
||||||
{...item}
|
{...item}
|
||||||
index={index}
|
index={index}
|
||||||
navigate={this.props.navigate}
|
navigate={this.props.navigate}
|
||||||
|
chartHeight={this.state.chartHeight}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.reCalculateChartInfo = (function(Chart) {
|
onLayout = ({ nativeEvent }) => {
|
||||||
return function() {
|
if (this.state.chartHeight) return
|
||||||
Chart.setState({columns: makeColumnInfo(setUpFertilityStatusFunc())})
|
|
||||||
}
|
const height = nativeEvent.layout.height
|
||||||
})(this)
|
this.setState({ chartHeight: height })
|
||||||
|
this.reCalculateChartInfo = () => {
|
||||||
|
this.setState({ columns: this.makeColumnInfo(nfpLines(height)) })
|
||||||
|
}
|
||||||
|
|
||||||
cycleDaysSortedByDate.addListener(this.reCalculateChartInfo)
|
cycleDaysSortedByDate.addListener(this.reCalculateChartInfo)
|
||||||
|
this.removeObvListener = scaleObservable(this.reCalculateChartInfo, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
cycleDaysSortedByDate.removeListener(this.reCalculateChartInfo)
|
cycleDaysSortedByDate.removeListener(this.reCalculateChartInfo)
|
||||||
|
this.removeObvListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
makeColumnInfo(getFhmAndLtlInfo) {
|
||||||
|
let amountOfCycleDays = getAmountOfCycleDays()
|
||||||
|
// if there's not much data yet, we want to show at least 30 days on the chart
|
||||||
|
if (amountOfCycleDays < 30) {
|
||||||
|
amountOfCycleDays = 30
|
||||||
|
} else {
|
||||||
|
// we don't want the chart to end abruptly before the first data day
|
||||||
|
amountOfCycleDays += 5
|
||||||
|
}
|
||||||
|
const jsDates = getTodayAndPreviousDays(amountOfCycleDays)
|
||||||
|
const xAxisDates = jsDates.map(jsDate => {
|
||||||
|
return LocalDate.of(
|
||||||
|
jsDate.getFullYear(),
|
||||||
|
jsDate.getMonth() + 1,
|
||||||
|
jsDate.getDate()
|
||||||
|
).toString()
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns = xAxisDates.map(dateString => {
|
||||||
|
const cycleDay = getCycleDay(dateString)
|
||||||
|
const symptoms = ['temperature', 'mucus', 'bleeding'].reduce((acc, symptom) => {
|
||||||
|
acc[symptom] = cycleDay && cycleDay[symptom] && cycleDay[symptom].value
|
||||||
|
acc[`${symptom}Exclude`] = cycleDay && cycleDay[symptom] && cycleDay[symptom].exclude
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
const temp = symptoms.temperature
|
||||||
|
const columnHeight = this.state.chartHeight * config.columnHeightPercentage
|
||||||
|
return {
|
||||||
|
dateString,
|
||||||
|
y: temp ? normalizeToScale(temp, columnHeight) : null,
|
||||||
|
...symptoms,
|
||||||
|
...getFhmAndLtlInfo(dateString, temp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return columns.map((col, i) => {
|
||||||
|
const info = getInfoForNeighborColumns(i, columns)
|
||||||
|
return Object.assign(col, info)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let columnHeight
|
||||||
|
let symptomRowHeight
|
||||||
|
if (this.state.chartHeight) {
|
||||||
|
columnHeight = this.state.chartHeight * config.columnHeightPercentage
|
||||||
|
symptomRowHeight = this.state.chartHeight * config.symptomRowHeightPercentage
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<ScrollView>
|
<View
|
||||||
<View style={{ flexDirection: 'row', marginTop: 50 }}>
|
onLayout={this.onLayout}
|
||||||
{yAxisView}
|
style={{ flexDirection: 'row', flex: 1 }}
|
||||||
{horizontalGrid}
|
>
|
||||||
{<FlatList
|
{!this.state.chartLoaded &&
|
||||||
|
<View style={{width: '100%', justifyContent: 'center', alignItems: 'center'}}>
|
||||||
|
<Text>Loading...</Text>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
|
||||||
|
{this.state.chartHeight && this.state.chartLoaded &&
|
||||||
|
<View
|
||||||
|
style={[styles.yAxis, {
|
||||||
|
height: columnHeight,
|
||||||
|
marginTop: symptomRowHeight
|
||||||
|
}]}
|
||||||
|
>
|
||||||
|
{makeYAxisLabels(columnHeight)}
|
||||||
|
</View>}
|
||||||
|
|
||||||
|
{this.state.chartHeight && this.state.chartLoaded && makeHorizontalGrid(columnHeight, symptomRowHeight)}
|
||||||
|
|
||||||
|
{this.state.chartHeight &&
|
||||||
|
<FlatList
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
inverted={true}
|
inverted={true}
|
||||||
showsHorizontalScrollIndicator={false}
|
showsHorizontalScrollIndicator={false}
|
||||||
@@ -54,52 +126,14 @@ export default class CycleChart extends Component {
|
|||||||
keyExtractor={item => item.dateString}
|
keyExtractor={item => item.dateString}
|
||||||
initialNumToRender={15}
|
initialNumToRender={15}
|
||||||
maxToRenderPerBatch={5}
|
maxToRenderPerBatch={5}
|
||||||
>
|
onLayout={() => this.setState({chartLoaded: true})}
|
||||||
</FlatList>}
|
/>
|
||||||
</View>
|
}
|
||||||
</ScrollView>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeColumnInfo(getFhmAndLtlInfo) {
|
|
||||||
let amountOfCycleDays = getAmountOfCycleDays()
|
|
||||||
// if there's not much data yet, we want to show at least 30 days on the chart
|
|
||||||
if (amountOfCycleDays < 30) {
|
|
||||||
amountOfCycleDays = 30
|
|
||||||
} else {
|
|
||||||
// we don't want the chart to end abruptly before the first data day
|
|
||||||
amountOfCycleDays += 5
|
|
||||||
}
|
|
||||||
const xAxisDates = getTodayAndPreviousDays(amountOfCycleDays).map(jsDate => {
|
|
||||||
return LocalDate.of(
|
|
||||||
jsDate.getFullYear(),
|
|
||||||
jsDate.getMonth() + 1,
|
|
||||||
jsDate.getDate()
|
|
||||||
).toString()
|
|
||||||
})
|
|
||||||
|
|
||||||
const columns = xAxisDates.map(dateString => {
|
|
||||||
const cycleDay = getCycleDay(dateString)
|
|
||||||
const symptoms = ['temperature', 'mucus', 'bleeding'].reduce((acc, symptom) => {
|
|
||||||
acc[symptom] = cycleDay && cycleDay[symptom] && cycleDay[symptom].value
|
|
||||||
acc[`${symptom}Exclude`] = cycleDay && cycleDay[symptom] && cycleDay[symptom].exclude
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
return {
|
|
||||||
dateString,
|
|
||||||
y: symptoms.temperature ? normalizeToScale(symptoms.temperature) : null,
|
|
||||||
...symptoms,
|
|
||||||
...getFhmAndLtlInfo(dateString, symptoms.temperature)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return columns.map((col, i) => {
|
|
||||||
const info = getInfoForNeighborColumns(i, columns)
|
|
||||||
return Object.assign(col, info)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTodayAndPreviousDays(n) {
|
function getTodayAndPreviousDays(n) {
|
||||||
const today = new Date()
|
const today = new Date()
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
const config = {
|
|
||||||
chartHeight: 350,
|
|
||||||
columnWidth: 25,
|
|
||||||
temperatureScale: {
|
|
||||||
low: 35,
|
|
||||||
high: 38,
|
|
||||||
units: 0.1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const margin = 3
|
|
||||||
config.columnMiddle = config.columnWidth / 2,
|
|
||||||
config.dateRowY = config.chartHeight - 15 - margin
|
|
||||||
config.cycleDayNumberRowY = config.chartHeight - margin
|
|
||||||
|
|
||||||
export default config
|
|
||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import Icon from 'react-native-vector-icons/Entypo'
|
import Icon from 'react-native-vector-icons/Entypo'
|
||||||
import styles from './styles'
|
import styles from './styles'
|
||||||
import config from './config'
|
import config from '../../config'
|
||||||
import { getOrCreateCycleDay } from '../../db'
|
import { getOrCreateCycleDay } from '../../db'
|
||||||
import cycleModule from '../../lib/cycle'
|
import cycleModule from '../../lib/cycle'
|
||||||
import DotAndLine from './dot-and-line'
|
import DotAndLine from './dot-and-line'
|
||||||
@@ -34,48 +34,15 @@ export default class DayColumn extends Component {
|
|||||||
rightY,
|
rightY,
|
||||||
rightTemperatureExclude,
|
rightTemperatureExclude,
|
||||||
leftY,
|
leftY,
|
||||||
leftTemperatureExclude
|
leftTemperatureExclude,
|
||||||
|
chartHeight
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
|
const columnHeight = chartHeight * config.columnHeightPercentage
|
||||||
|
const xAxisHeight = chartHeight * config.xAxisHeightPercentage
|
||||||
|
const symptomHeight = chartHeight * config.symptomRowHeightPercentage
|
||||||
|
|
||||||
const columnElements = []
|
const columnElements = []
|
||||||
if (typeof bleeding === 'number') {
|
|
||||||
columnElements.push(
|
|
||||||
<Icon
|
|
||||||
name='drop'
|
|
||||||
position='absolute'
|
|
||||||
size={18}
|
|
||||||
color='#900'
|
|
||||||
style={{ marginTop: 10, marginLeft: 3 }}
|
|
||||||
key='bleeding'
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof mucus === 'number') {
|
|
||||||
const mucusIcon = (
|
|
||||||
<View
|
|
||||||
position='absolute'
|
|
||||||
top = {40}
|
|
||||||
left = {config.columnMiddle - styles.mucusIcon.width / 2}
|
|
||||||
{...styles.mucusIcon}
|
|
||||||
backgroundColor={styles.mucusIconShades[mucus]}
|
|
||||||
key='mucus'
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
columnElements.push(mucusIcon)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(drawFhmLine) {
|
|
||||||
const fhmLine = (<View
|
|
||||||
position = 'absolute'
|
|
||||||
top={100}
|
|
||||||
width={styles.nfpLine.strokeWidth}
|
|
||||||
height={200}
|
|
||||||
{...styles.nfpLine}
|
|
||||||
key='fhm'
|
|
||||||
/>)
|
|
||||||
columnElements.push(fhmLine)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(drawLtlAt) {
|
if(drawLtlAt) {
|
||||||
const ltlLine = (<View
|
const ltlLine = (<View
|
||||||
@@ -105,25 +72,31 @@ export default class DayColumn extends Component {
|
|||||||
const cycleDayNumber = getCycleDayNumber(dateString)
|
const cycleDayNumber = getCycleDayNumber(dateString)
|
||||||
const shortDate = dateString.split('-').slice(1).join('-')
|
const shortDate = dateString.split('-').slice(1).join('-')
|
||||||
const cycleDayLabel = (
|
const cycleDayLabel = (
|
||||||
<Text style={label.number} y={config.cycleDayNumberRowY}>
|
<Text style={label.number}>
|
||||||
{cycleDayNumber}
|
{cycleDayNumber}
|
||||||
</Text>)
|
</Text>)
|
||||||
const dateLabel = (
|
const dateLabel = (
|
||||||
<Text style = {label.date} y={config.dateRowY}>
|
<Text style = {label.date}>
|
||||||
{shortDate}
|
{shortDate}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
columnElements.push(
|
|
||||||
<View position='absolute' bottom={0} key='date'>
|
|
||||||
{cycleDayLabel}
|
|
||||||
{dateLabel}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
|
|
||||||
return React.createElement(
|
// we merge the colors here instead of from the stylesheet because of a RN
|
||||||
|
// bug that doesn't apply borderLeftColor otherwise
|
||||||
|
const customStyle = {
|
||||||
|
height: columnHeight,
|
||||||
|
borderLeftColor: 'grey',
|
||||||
|
borderRightColor: 'grey'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawFhmLine) {
|
||||||
|
customStyle.borderLeftColor = styles.nfpLine.borderColor
|
||||||
|
customStyle.borderLeftWidth = 3
|
||||||
|
}
|
||||||
|
const column = React.createElement(
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
{
|
{
|
||||||
style: styles.column.rect,
|
style: [styles.column.rect, customStyle],
|
||||||
key: this.props.index.toString(),
|
key: this.props.index.toString(),
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
this.passDateToDayView(dateString)
|
this.passDateToDayView(dateString)
|
||||||
@@ -132,5 +105,34 @@ export default class DayColumn extends Component {
|
|||||||
},
|
},
|
||||||
columnElements
|
columnElements
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<View style={[styles.symptomRow, {height: symptomHeight}]}>
|
||||||
|
{typeof mucus === 'number' &&
|
||||||
|
<View
|
||||||
|
{...styles.mucusIcon}
|
||||||
|
backgroundColor={styles.mucusIconShades[mucus]}
|
||||||
|
key='mucus'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{typeof bleeding === 'number' &&
|
||||||
|
<Icon
|
||||||
|
name='drop'
|
||||||
|
size={18}
|
||||||
|
color='#900'
|
||||||
|
key='bleeding'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{column}
|
||||||
|
|
||||||
|
<View style={{height: xAxisHeight}}>
|
||||||
|
{cycleDayLabel}
|
||||||
|
{dateLabel}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
import styles from './styles'
|
import styles from './styles'
|
||||||
import config from './config'
|
import config from '../../config'
|
||||||
|
|
||||||
export default class DotAndLine extends Component {
|
export default class DotAndLine extends Component {
|
||||||
shouldComponentUpdate(newProps) {
|
shouldComponentUpdate(newProps) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getCycleStatusForDay } from '../../lib/sympto-adapter'
|
import { getCycleStatusForDay } from '../../lib/sympto-adapter'
|
||||||
import { normalizeToScale } from './y-axis'
|
import { normalizeToScale } from './y-axis'
|
||||||
|
|
||||||
export default function () {
|
export default function (chartHeight) {
|
||||||
const cycle = {
|
const cycle = {
|
||||||
status: null
|
status: null
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ export default function () {
|
|||||||
dateIsInPeriOrPostPhase(dateString) &&
|
dateIsInPeriOrPostPhase(dateString) &&
|
||||||
isInTempMeasuringPhase(temperature, dateString)
|
isInTempMeasuringPhase(temperature, dateString)
|
||||||
) {
|
) {
|
||||||
ret.drawLtlAt = normalizeToScale(tempShift.ltl)
|
ret.drawLtlAt = normalizeToScale(tempShift.ltl, chartHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import config from './config'
|
import config from '../../config'
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
curve: {
|
curve: {
|
||||||
@@ -38,10 +38,9 @@ const styles = {
|
|||||||
},
|
},
|
||||||
rect: {
|
rect: {
|
||||||
width: config.columnWidth,
|
width: config.columnWidth,
|
||||||
height: config.chartHeight,
|
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
borderColor: 'grey',
|
borderLeftWidth: 0.5,
|
||||||
borderWidth: 0.5
|
borderRightWidth: 0.5,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bleedingIcon: {
|
bleedingIcon: {
|
||||||
@@ -63,7 +62,6 @@ const styles = {
|
|||||||
'#993299'
|
'#993299'
|
||||||
],
|
],
|
||||||
yAxis: {
|
yAxis: {
|
||||||
height: config.chartHeight,
|
|
||||||
width: config.columnWidth,
|
width: config.columnWidth,
|
||||||
borderRightWidth: 0.5,
|
borderRightWidth: 0.5,
|
||||||
borderColor: 'lightgrey',
|
borderColor: 'lightgrey',
|
||||||
@@ -88,6 +86,10 @@ const styles = {
|
|||||||
borderColor: '#00b159',
|
borderColor: '#00b159',
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
borderStyle: 'solid'
|
borderStyle: 'solid'
|
||||||
|
},
|
||||||
|
symptomRow: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+51
-36
@@ -1,53 +1,68 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Text, View } from 'react-native'
|
import { Text, View } from 'react-native'
|
||||||
import config from './config'
|
import config from '../../config'
|
||||||
import styles from './styles'
|
import styles from './styles'
|
||||||
|
import { scaleObservable } from '../../local-storage'
|
||||||
|
|
||||||
function makeYAxis() {
|
export function makeYAxisLabels(columnHeight) {
|
||||||
const scale = config.temperatureScale
|
const units = config.temperatureScale.units
|
||||||
const scaleMin = scale.low
|
const scaleMax = scaleObservable.value.max
|
||||||
const scaleMax = scale.high
|
const style = styles.yAxisLabel
|
||||||
const numberOfTicks = (scaleMax - scaleMin) * (1 / scale.units)
|
|
||||||
const tickDistance = config.chartHeight / numberOfTicks
|
|
||||||
|
|
||||||
const tickPositions = []
|
return getTickPositions(columnHeight).map((y, i) => {
|
||||||
const labels = []
|
|
||||||
// for style reasons, we don't want the first and last tick
|
|
||||||
for (let i = 1; i < numberOfTicks - 1; i++) {
|
|
||||||
const y = tickDistance * i
|
|
||||||
const style = styles.yAxisLabel
|
|
||||||
// this eyeballing is sadly necessary because RN does not
|
// this eyeballing is sadly necessary because RN does not
|
||||||
// support percentage values for transforms, which we'd need
|
// support percentage values for transforms, which we'd need
|
||||||
// to reliably place the label vertically centered to the grid
|
// to reliably place the label vertically centered to the grid
|
||||||
style.top = y - 8
|
if (scaleMax - i * units === 37) console.log(y)
|
||||||
labels.push(
|
return (
|
||||||
<Text
|
<Text
|
||||||
style={{...style}}
|
style={[style, {top: y - 8}]}
|
||||||
key={i}>
|
key={i}>
|
||||||
{scaleMax - i * scale.units}
|
{scaleMax - i * units}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
tickPositions.push(y)
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return {labels, tickPositions}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const yAxis = makeYAxis()
|
export function makeHorizontalGrid(columnHeight, symptomRowHeight) {
|
||||||
|
return getTickPositions(columnHeight).map(tick => {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
top={tick + symptomRowHeight}
|
||||||
|
{...styles.horizontalGrid}
|
||||||
|
key={tick}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const horizontalGrid = yAxis.tickPositions.map(tick => {
|
function getTickPositions(columnHeight) {
|
||||||
return (
|
const units = config.temperatureScale.units
|
||||||
<View
|
const scaleMin = scaleObservable.value.min
|
||||||
top={tick}
|
const scaleMax = scaleObservable.value.max
|
||||||
{...styles.horizontalGrid}
|
const numberOfTicks = (scaleMax - scaleMin) * (1 / units) + 1
|
||||||
key={tick}
|
const tickDistance = 1 / (numberOfTicks - 1)
|
||||||
/>
|
|
||||||
)
|
const tickPositions = []
|
||||||
})
|
for (let i = 0; i < numberOfTicks; i++) {
|
||||||
|
const position = getAbsoluteValue(tickDistance * i, columnHeight)
|
||||||
|
tickPositions.push(position)
|
||||||
|
}
|
||||||
|
return tickPositions
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeToScale(temp, columnHeight) {
|
||||||
|
const scale = scaleObservable.value
|
||||||
|
const valueRelativeToScale = (scale.max - temp) / (scale.max - scale.min)
|
||||||
|
return getAbsoluteValue(valueRelativeToScale, columnHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAbsoluteValue(relative, columnHeight) {
|
||||||
|
// we add some height to have some breathing room
|
||||||
|
const verticalPadding = columnHeight * config.temperatureScale.verticalPadding
|
||||||
|
const scaleHeight = columnHeight - 2 * verticalPadding
|
||||||
|
console.log(scaleHeight)
|
||||||
|
console.log(columnHeight)
|
||||||
|
return scaleHeight * relative + verticalPadding
|
||||||
|
|
||||||
export function normalizeToScale(temp) {
|
|
||||||
const scale = config.temperatureScale
|
|
||||||
const valueRelativeToScale = (scale.high - temp) / (scale.high - scale.low)
|
|
||||||
const scaleHeight = config.chartHeight
|
|
||||||
return scaleHeight * valueRelativeToScale
|
|
||||||
}
|
}
|
||||||
@@ -26,3 +26,9 @@ export const fertilityStatus = {
|
|||||||
fertileUntilEvening: 'Fertile phase ends in the evening',
|
fertileUntilEvening: 'Fertile phase ends in the evening',
|
||||||
unknown: 'We cannot show any cycle information because no menses has been entered'
|
unknown: 'We cannot show any cycle information because no menses has been entered'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const temperature = {
|
||||||
|
outOfRangeWarning: 'This temperature value is out of the current range for the temperature chart. You can change the range in the settings.',
|
||||||
|
outOfAbsoluteRangeWarning: 'This temperature value is too high or low to be shown on the temperature chart.',
|
||||||
|
saveAnyway: 'Save anyway'
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,14 @@ import styles, {iconStyles} from '../../../styles'
|
|||||||
|
|
||||||
export default class ActionButtonFooter extends Component {
|
export default class ActionButtonFooter extends Component {
|
||||||
render() {
|
render() {
|
||||||
const { symptom, cycleDay, saveAction, saveDisabled, navigate} = this.props
|
const {
|
||||||
|
symptom,
|
||||||
|
cycleDay,
|
||||||
|
saveAction,
|
||||||
|
saveDisabled,
|
||||||
|
navigate,
|
||||||
|
autoShowDayView = true}
|
||||||
|
= this.props
|
||||||
const navigateToOverView = () => navigate('CycleDay', {cycleDay})
|
const navigateToOverView = () => navigate('CycleDay', {cycleDay})
|
||||||
const buttons = [
|
const buttons = [
|
||||||
{
|
{
|
||||||
@@ -28,7 +35,7 @@ export default class ActionButtonFooter extends Component {
|
|||||||
title: 'Save',
|
title: 'Save',
|
||||||
action: () => {
|
action: () => {
|
||||||
saveAction()
|
saveAction()
|
||||||
navigateToOverView()
|
if (autoShowDayView) navigateToOverView()
|
||||||
},
|
},
|
||||||
disabledCondition: saveDisabled,
|
disabledCondition: saveDisabled,
|
||||||
icon: 'content-save-outline'
|
icon: 'content-save-outline'
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
TextInput,
|
TextInput,
|
||||||
Switch,
|
Switch,
|
||||||
Keyboard,
|
Keyboard,
|
||||||
|
Alert,
|
||||||
ScrollView
|
ScrollView
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import DateTimePicker from 'react-native-modal-datetime-picker-nevo'
|
import DateTimePicker from 'react-native-modal-datetime-picker-nevo'
|
||||||
@@ -12,35 +13,84 @@ import DateTimePicker from 'react-native-modal-datetime-picker-nevo'
|
|||||||
import { getPreviousTemperature, saveSymptom } from '../../../db'
|
import { getPreviousTemperature, saveSymptom } from '../../../db'
|
||||||
import styles from '../../../styles'
|
import styles from '../../../styles'
|
||||||
import { LocalTime, ChronoUnit } from 'js-joda'
|
import { LocalTime, ChronoUnit } from 'js-joda'
|
||||||
|
import { temperature as tempLabels } from '../labels/labels'
|
||||||
|
import { scaleObservable } from '../../../local-storage'
|
||||||
|
import { shared } from '../../labels'
|
||||||
import ActionButtonFooter from './action-button-footer'
|
import ActionButtonFooter from './action-button-footer'
|
||||||
|
import config from '../../../config'
|
||||||
|
|
||||||
const MINUTES = ChronoUnit.MINUTES
|
const minutes = ChronoUnit.MINUTES
|
||||||
|
|
||||||
export default class Temp extends Component {
|
export default class Temp extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.cycleDay = props.cycleDay
|
this.cycleDay = props.cycleDay
|
||||||
this.makeActionButtons = props.makeActionButtons
|
this.makeActionButtons = props.makeActionButtons
|
||||||
let initialValue
|
|
||||||
|
|
||||||
const temp = this.cycleDay.temperature
|
const temp = this.cycleDay.temperature
|
||||||
|
|
||||||
if (temp) {
|
this.state = {
|
||||||
initialValue = temp.value.toString()
|
exclude: temp ? temp.exclude : false,
|
||||||
this.time = temp.time
|
time: temp ? temp.time : LocalTime.now().truncatedTo(minutes).toString(),
|
||||||
} else {
|
isTimePickerVisible: false,
|
||||||
const prevTemp = getPreviousTemperature(this.cycleDay)
|
outOfRange: null
|
||||||
initialValue = prevTemp ? prevTemp.toString() : ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = {
|
if (temp) {
|
||||||
currentValue: initialValue,
|
this.state.temperature = temp.value.toString()
|
||||||
exclude: temp ? temp.exclude : false,
|
if (temp.value === Math.floor(temp.value)) {
|
||||||
time: this.time || LocalTime.now().truncatedTo(MINUTES).toString(),
|
this.state.temperature = `${this.state.temperature}.0`
|
||||||
isTimePickerVisible: false
|
}
|
||||||
|
} else {
|
||||||
|
const prevTemp = getPreviousTemperature(this.cycleDay)
|
||||||
|
if (prevTemp) {
|
||||||
|
this.state.temperature = prevTemp.toString()
|
||||||
|
this.state.isSuggestion = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveTemperature = () => {
|
||||||
|
const dataToSave = {
|
||||||
|
value: Number(this.state.temperature),
|
||||||
|
exclude: this.state.exclude,
|
||||||
|
time: this.state.time
|
||||||
|
}
|
||||||
|
saveSymptom('temperature', this.cycleDay, dataToSave)
|
||||||
|
this.props.navigate('CycleDay', {cycleDay: this.cycleDay})
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRangeAndSave = () => {
|
||||||
|
const value = Number(this.state.temperature)
|
||||||
|
|
||||||
|
const absolute = {
|
||||||
|
min: config.temperatureScale.min,
|
||||||
|
max: config.temperatureScale.max
|
||||||
|
}
|
||||||
|
const scale = scaleObservable.value
|
||||||
|
let warningMsg
|
||||||
|
if (value < absolute.min || value > absolute.max) {
|
||||||
|
warningMsg = tempLabels.outOfAbsoluteRangeWarning
|
||||||
|
} else if (value < scale.min || value > scale.max) {
|
||||||
|
warningMsg = tempLabels.outOfRangeWarning
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warningMsg) {
|
||||||
|
Alert.alert(
|
||||||
|
shared.warning,
|
||||||
|
warningMsg,
|
||||||
|
[
|
||||||
|
{ text: shared.cancel },
|
||||||
|
{ text: shared.save, onPress: this.saveTemperature}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.saveTemperature()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
@@ -48,14 +98,10 @@ export default class Temp extends Component {
|
|||||||
<View>
|
<View>
|
||||||
<View style={styles.symptomViewRowInline}>
|
<View style={styles.symptomViewRowInline}>
|
||||||
<Text style={styles.symptomDayView}>Temperature (°C)</Text>
|
<Text style={styles.symptomDayView}>Temperature (°C)</Text>
|
||||||
<TextInput
|
<TempInput
|
||||||
style={styles.temperatureTextInput}
|
value={this.state.temperature}
|
||||||
placeholder="Enter"
|
setState={(val) => this.setState(val)}
|
||||||
onChangeText={(val) => {
|
isSuggestion={this.state.isSuggestion}
|
||||||
this.setState({ currentValue: val })
|
|
||||||
}}
|
|
||||||
keyboardType='numeric'
|
|
||||||
value={this.state.currentValue}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.symptomViewRowInline}>
|
<View style={styles.symptomViewRowInline}>
|
||||||
@@ -94,22 +140,42 @@ export default class Temp extends Component {
|
|||||||
<ActionButtonFooter
|
<ActionButtonFooter
|
||||||
symptom='temperature'
|
symptom='temperature'
|
||||||
cycleDay={this.cycleDay}
|
cycleDay={this.cycleDay}
|
||||||
saveAction={() => {
|
saveAction={() => this.checkRangeAndSave()}
|
||||||
const dataToSave = {
|
saveDisabled={
|
||||||
value: Number(this.state.currentValue),
|
this.state.temperature === '' ||
|
||||||
exclude: this.state.exclude,
|
isNaN(Number(this.state.temperature)) ||
|
||||||
time: this.state.time
|
isInvalidTime(this.state.time)
|
||||||
}
|
}
|
||||||
saveSymptom('temperature', this.props.cycleDay, dataToSave)
|
|
||||||
}}
|
|
||||||
saveDisabled={this.state.currentValue === '' || isInvalidTime(this.state.time)}
|
|
||||||
navigate={this.props.navigate}
|
navigate={this.props.navigate}
|
||||||
|
autoShowDayView={false}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TempInput extends Component {
|
||||||
|
render() {
|
||||||
|
const style = [styles.temperatureTextInput]
|
||||||
|
if (this.props.isSuggestion) {
|
||||||
|
style.push(styles.temperatureTextInputSuggestion)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<TextInput
|
||||||
|
style={style}
|
||||||
|
onChangeText={(val) => {
|
||||||
|
if (isNaN(Number(val))) return
|
||||||
|
this.props.setState({ temperature: val, isSuggestion: false })
|
||||||
|
}}
|
||||||
|
keyboardType='numeric'
|
||||||
|
value={this.props.value}
|
||||||
|
onBlur={this.checkRange}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isInvalidTime(timeString) {
|
function isInvalidTime(timeString) {
|
||||||
try {
|
try {
|
||||||
LocalTime.parse(timeString)
|
LocalTime.parse(timeString)
|
||||||
|
|||||||
+19
-6
@@ -1,9 +1,12 @@
|
|||||||
|
export const shared = {
|
||||||
|
cancel: 'Cancel',
|
||||||
|
save: 'Save',
|
||||||
|
errorTitle: 'Error',
|
||||||
|
successTitle: 'Success',
|
||||||
|
warning: 'Warning'
|
||||||
|
}
|
||||||
|
|
||||||
export const settings = {
|
export const settings = {
|
||||||
shared: {
|
|
||||||
cancel: 'Cancel',
|
|
||||||
errorTitle: 'Error',
|
|
||||||
successTitle: 'Success'
|
|
||||||
},
|
|
||||||
export: {
|
export: {
|
||||||
errors: {
|
errors: {
|
||||||
noData: 'There is no data to export',
|
noData: 'There is no data to export',
|
||||||
@@ -13,6 +16,7 @@ export const settings = {
|
|||||||
title: 'My Drip data export',
|
title: 'My Drip data export',
|
||||||
subject: 'My Drip data export',
|
subject: 'My Drip data export',
|
||||||
button: 'Export data',
|
button: 'Export data',
|
||||||
|
segmentExplainer: 'Export data in CSV format for backup or so you can use it elsewhere'
|
||||||
},
|
},
|
||||||
import: {
|
import: {
|
||||||
button: 'Import data',
|
button: 'Import data',
|
||||||
@@ -28,7 +32,16 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
success: {
|
success: {
|
||||||
message: 'Data successfully imported'
|
message: 'Data successfully imported'
|
||||||
}
|
},
|
||||||
|
segmentExplainer: 'Import data in CSV format'
|
||||||
|
},
|
||||||
|
tempScale: {
|
||||||
|
segmentTitle: 'Temperature scale',
|
||||||
|
segmentExplainer: 'Change the minimum and maximum value for the temperature chart',
|
||||||
|
min: 'Min',
|
||||||
|
max: 'Max',
|
||||||
|
loadError: 'Could not load saved temperature scale settings',
|
||||||
|
saveError: 'Could not save temperature scale settings'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+114
-33
@@ -1,80 +1,161 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Button,
|
TouchableOpacity,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
Alert
|
Alert,
|
||||||
|
Text
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
|
import Slider from '@ptomasroos/react-native-multi-slider'
|
||||||
import Share from 'react-native-share'
|
import Share from 'react-native-share'
|
||||||
import { DocumentPicker, DocumentPickerUtil } from 'react-native-document-picker'
|
import { DocumentPicker, DocumentPickerUtil } from 'react-native-document-picker'
|
||||||
import rnfs from 'react-native-fs'
|
import rnfs from 'react-native-fs'
|
||||||
import styles from '../styles/index'
|
import styles, { secondaryColor } from '../styles/index'
|
||||||
import { settings as labels } from './labels'
|
import config from '../config'
|
||||||
|
import { settings as settingsLabels, shared as sharedLabels } from './labels'
|
||||||
import getDataAsCsvDataUri from '../lib/import-export/export-to-csv'
|
import getDataAsCsvDataUri from '../lib/import-export/export-to-csv'
|
||||||
import importCsv from '../lib/import-export/import-from-csv'
|
import importCsv from '../lib/import-export/import-from-csv'
|
||||||
|
import { scaleObservable, saveTempScale } from '../local-storage'
|
||||||
|
|
||||||
export default class Settings extends Component {
|
export default class Settings extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<View style={styles.homeButtons}>
|
<View style={styles.settingsSegment}>
|
||||||
<View style={styles.homeButton}>
|
<Text style={styles.settingsSegmentTitle}>
|
||||||
<Button
|
{settingsLabels.tempScale.segmentTitle}
|
||||||
onPress={ openShareDialogAndExport }
|
</Text>
|
||||||
title={labels.export.button}>
|
<Text>{settingsLabels.tempScale.segmentExplainer}</Text>
|
||||||
</Button>
|
<TempSlider/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.homeButton}>
|
<View style={styles.settingsSegment}>
|
||||||
<Button
|
<Text style={styles.settingsSegmentTitle}>
|
||||||
title={labels.import.button}
|
{settingsLabels.export.button}
|
||||||
onPress={ openImportDialogAndImport }>
|
</Text>
|
||||||
</Button>
|
<Text>{settingsLabels.export.segmentExplainer}</Text>
|
||||||
</View>
|
<TouchableOpacity
|
||||||
|
onPress={openShareDialogAndExport}
|
||||||
|
style={styles.settingsButton}>
|
||||||
|
<Text style={styles.settingsButtonText}>
|
||||||
|
{settingsLabels.export.button}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
<View style={styles.settingsSegment}>
|
||||||
|
<Text style={styles.settingsSegmentTitle}>
|
||||||
|
{settingsLabels.import.button}
|
||||||
|
</Text>
|
||||||
|
<Text>{settingsLabels.import.segmentExplainer}</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={openImportDialogAndImport}
|
||||||
|
style={styles.settingsButton}>
|
||||||
|
<Text style={styles.settingsButtonText}>
|
||||||
|
{settingsLabels.import.button}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TempSlider extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = Object.assign({}, scaleObservable.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
onValuesChange = (values) => {
|
||||||
|
this.setState({
|
||||||
|
min: values[0],
|
||||||
|
max: values[1]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onValuesChangeFinish = (values) => {
|
||||||
|
this.setState({
|
||||||
|
min: values[0],
|
||||||
|
max: values[1]
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
saveTempScale(this.state)
|
||||||
|
} catch(err) {
|
||||||
|
alertError(settingsLabels.tempScale.saveError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={{alignItems: 'center'}}>
|
||||||
|
<Text>{`${settingsLabels.tempScale.min} ${this.state.min}`}</Text>
|
||||||
|
<Text>{`${settingsLabels.tempScale.max} ${this.state.max}`}</Text>
|
||||||
|
<Slider
|
||||||
|
values={[this.state.min, this.state.max]}
|
||||||
|
min={config.temperatureScale.min}
|
||||||
|
max={config.temperatureScale.max}
|
||||||
|
step={0.5}
|
||||||
|
onValuesChange={this.onValuesChange}
|
||||||
|
onValuesChangeFinish={this.onValuesChangeFinish}
|
||||||
|
selectedStyle={{
|
||||||
|
backgroundColor: 'darkgrey',
|
||||||
|
}}
|
||||||
|
unselectedStyle={{
|
||||||
|
backgroundColor: 'silver',
|
||||||
|
}}
|
||||||
|
trackStyle={{
|
||||||
|
height:10,
|
||||||
|
}}
|
||||||
|
markerStyle={{
|
||||||
|
backgroundColor: secondaryColor,
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
borderRadius: 100,
|
||||||
|
marginTop: 10
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function openShareDialogAndExport() {
|
async function openShareDialogAndExport() {
|
||||||
let data
|
let data
|
||||||
try {
|
try {
|
||||||
data = getDataAsCsvDataUri()
|
data = getDataAsCsvDataUri()
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return alertError(labels.errors.noData)
|
return alertError(settingsLabels.errors.noData)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
return alertError(labels.errors.couldNotConvert)
|
return alertError(settingsLabels.errors.couldNotConvert)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Share.open({
|
await Share.open({
|
||||||
title: labels.export.title,
|
title: settingsLabels.export.title,
|
||||||
url: data,
|
url: data,
|
||||||
subject: labels.export.subject,
|
subject: settingsLabels.export.subject,
|
||||||
type: 'text/csv',
|
type: 'text/csv',
|
||||||
showAppsToView: true
|
showAppsToView: true
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
return alertError(labels.export.errors.problemSharing)
|
return alertError(settingsLabels.export.errors.problemSharing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openImportDialogAndImport() {
|
function openImportDialogAndImport() {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
labels.import.title,
|
settingsLabels.import.title,
|
||||||
labels.import.message,
|
settingsLabels.import.message,
|
||||||
[{
|
[{
|
||||||
text: labels.import.replaceOption,
|
text: settingsLabels.import.replaceOption,
|
||||||
onPress: () => getFileContentAndImport({ deleteExisting: false })
|
onPress: () => getFileContentAndImport({ deleteExisting: false })
|
||||||
}, {
|
}, {
|
||||||
text: labels.import.deleteOption,
|
text: settingsLabels.import.deleteOption,
|
||||||
onPress: () => getFileContentAndImport({ deleteExisting: true })
|
onPress: () => getFileContentAndImport({ deleteExisting: true })
|
||||||
}, {
|
}, {
|
||||||
text: labels.shared.cancel, style: 'cancel', onPress: () => { }
|
text: sharedLabels.cancel, style: 'cancel', onPress: () => { }
|
||||||
}]
|
}]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -99,22 +180,22 @@ async function getFileContentAndImport({ deleteExisting }) {
|
|||||||
try {
|
try {
|
||||||
fileContent = await rnfs.readFile(fileInfo.uri, 'utf8')
|
fileContent = await rnfs.readFile(fileInfo.uri, 'utf8')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return importError(labels.import.errors.couldNotOpenFile)
|
return importError(settingsLabels.import.errors.couldNotOpenFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await importCsv(fileContent, deleteExisting)
|
await importCsv(fileContent, deleteExisting)
|
||||||
Alert.alert(labels.import.success.title, labels.import.success.message)
|
Alert.alert(sharedLabels.successTitle, settingsLabels.import.success.message)
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
importError(err.message)
|
importError(err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function alertError(msg) {
|
function alertError(msg) {
|
||||||
Alert.alert(labels.shared.errorTitle, msg)
|
Alert.alert(sharedLabels.errorTitle, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
function importError(msg) {
|
function importError(msg) {
|
||||||
const postFixed = `${msg}\n\n${labels.import.errors.postFix}`
|
const postFixed = `${msg}\n\n${settingsLabels.import.errors.postFix}`
|
||||||
alertError(postFixed)
|
alertError(postFixed)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
const config = {
|
||||||
|
columnWidth: 25,
|
||||||
|
columnHeightPercentage: 0.84,
|
||||||
|
xAxisHeightPercentage: 0.08,
|
||||||
|
symptomRowHeightPercentage: 0.08,
|
||||||
|
temperatureScale: {
|
||||||
|
defaultLow: 35,
|
||||||
|
defaultHigh: 38,
|
||||||
|
min: 34,
|
||||||
|
max: 40,
|
||||||
|
units: 0.1,
|
||||||
|
verticalPadding: 0.03
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.columnMiddle = config.columnWidth / 2
|
||||||
|
|
||||||
|
export default config
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { AsyncStorage } from 'react-native'
|
||||||
|
import Observable from 'obv'
|
||||||
|
import config from '../config'
|
||||||
|
|
||||||
|
export const scaleObservable = Observable()
|
||||||
|
setTempScaleInitially()
|
||||||
|
|
||||||
|
async function setTempScaleInitially() {
|
||||||
|
const result = await AsyncStorage.getItem('tempScale')
|
||||||
|
let value
|
||||||
|
if (result) {
|
||||||
|
value = JSON.parse(result)
|
||||||
|
} else {
|
||||||
|
value = {
|
||||||
|
min: config.temperatureScale.defaultLow,
|
||||||
|
max: config.temperatureScale.defaultHigh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scaleObservable.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveTempScale(scale) {
|
||||||
|
await AsyncStorage.setItem('tempScale', JSON.stringify(scale))
|
||||||
|
scaleObservable.set(scale)
|
||||||
|
}
|
||||||
|
|
||||||
Generated
+1498
-1474
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@
|
|||||||
"lint": "eslint components lib test"
|
"lint": "eslint components lib test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ptomasroos/react-native-multi-slider": "^1.0.0",
|
||||||
"assert": "^1.4.1",
|
"assert": "^1.4.1",
|
||||||
"csvtojson": "^2.0.8",
|
"csvtojson": "^2.0.8",
|
||||||
"date-range": "0.0.2",
|
"date-range": "0.0.2",
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
"js-joda": "^1.8.2",
|
"js-joda": "^1.8.2",
|
||||||
"moment": "^2.22.1",
|
"moment": "^2.22.1",
|
||||||
"object-path": "^0.11.4",
|
"object-path": "^0.11.4",
|
||||||
|
"obv": "0.0.1",
|
||||||
"react": "16.4.1",
|
"react": "16.4.1",
|
||||||
"react-native": "^0.56.0",
|
"react-native": "^0.56.0",
|
||||||
"react-native-calendars": "^1.19.3",
|
"react-native-calendars": "^1.19.3",
|
||||||
|
|||||||
+28
-7
@@ -92,16 +92,17 @@ export default StyleSheet.create({
|
|||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
backgroundColor: primaryColor,
|
backgroundColor: primaryColor,
|
||||||
paddingVertical: 18,
|
|
||||||
paddingHorizontal: 15,
|
paddingHorizontal: 15,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center',
|
||||||
|
height: '10%'
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
backgroundColor: primaryColor,
|
backgroundColor: primaryColor,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
flexDirection: 'row'
|
flexDirection: 'row',
|
||||||
|
height: '12%'
|
||||||
},
|
},
|
||||||
menuItem: {
|
menuItem: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -116,7 +117,8 @@ export default StyleSheet.create({
|
|||||||
},
|
},
|
||||||
headerCycleDay: {
|
headerCycleDay: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between'
|
justifyContent: 'space-between',
|
||||||
|
height: '15%'
|
||||||
},
|
},
|
||||||
navigationArrow: {
|
navigationArrow: {
|
||||||
fontSize: 60,
|
fontSize: 60,
|
||||||
@@ -129,9 +131,11 @@ export default StyleSheet.create({
|
|||||||
marginBottom: 15
|
marginBottom: 15
|
||||||
},
|
},
|
||||||
temperatureTextInput: {
|
temperatureTextInput: {
|
||||||
width: 80,
|
fontSize: 20,
|
||||||
textAlign: 'center',
|
color: 'black'
|
||||||
fontSize: 20
|
},
|
||||||
|
temperatureTextInputSuggestion: {
|
||||||
|
color: '#939393'
|
||||||
},
|
},
|
||||||
actionButtonRow: {
|
actionButtonRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -152,6 +156,23 @@ export default StyleSheet.create({
|
|||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
textAlignVertical: 'center'
|
textAlignVertical: 'center'
|
||||||
},
|
},
|
||||||
|
settingsSegment: {
|
||||||
|
backgroundColor: 'lightgrey',
|
||||||
|
padding: 10,
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
settingsSegmentTitle: {
|
||||||
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
settingsButton: {
|
||||||
|
backgroundColor: secondaryColor,
|
||||||
|
padding: 10,
|
||||||
|
alignItems: 'center',
|
||||||
|
margin: 10
|
||||||
|
},
|
||||||
|
settingsButtonText: {
|
||||||
|
color: fontOnPrimaryColor
|
||||||
|
},
|
||||||
statsRow: {
|
statsRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
width: '100%'
|
width: '100%'
|
||||||
|
|||||||
Reference in New Issue
Block a user