merges master into branch

This commit is contained in:
tina
2018-09-28 17:07:27 +02:00
41 changed files with 1322 additions and 165 deletions
+19 -5
View File
@@ -9,9 +9,20 @@ import symptomViews from './cycle-day/symptoms'
import Chart from './chart/chart'
import Settings from './settings'
import Stats from './stats'
import {headerTitles as titles} from './labels'
import {headerTitles, menuTitles} from './labels'
import setupNotifications from '../lib/notifications'
// design wants everyhting lowercased, but we don't
// have CSS pseudo properties
const headerTitlesLowerCase = Object.keys(headerTitles).reduce((acc, curr) => {
acc[curr] = headerTitles[curr].toLowerCase()
return acc
}, {})
const menuTitlesLowerCase = Object.keys(menuTitles).reduce((acc, curr) => {
acc[curr] = menuTitles[curr].toLowerCase()
return acc
}, {})
const isSymptomView = name => Object.keys(symptomViews).indexOf(name) > -1
export default class App extends Component {
@@ -55,25 +66,28 @@ export default class App extends Component {
}[this.state.currentPage]
return (
<View style={{flex: 1}}>
{this.state.currentPage != 'CycleDay' && !isSymptomView(this.state.currentPage) &&
<Header
title={titles[this.state.currentPage]}
title={headerTitlesLowerCase[this.state.currentPage]}
/>}
{isSymptomView(this.state.currentPage) &&
<Header
title={titles[this.state.currentPage]}
title={headerTitlesLowerCase[this.state.currentPage]}
isSymptomView={true}
goBack={this.handleBackButtonPress}
/>}
{React.createElement(page, {
navigate: this.navigate,
...this.state.currentProps
})}
{!isSymptomView(this.state.currentPage) &&
<Menu navigate={this.navigate} />
<Menu
navigate={this.navigate}
titles={menuTitlesLowerCase}
/>
}
</View>
)
+23 -9
View File
@@ -10,6 +10,7 @@ import styles from './styles'
import { scaleObservable } from '../../local-storage'
import config from '../../config'
import { AppText } from '../app-text'
import { shared as labels } from '../labels'
export default class CycleChart extends Component {
constructor(props) {
@@ -111,7 +112,7 @@ export default class CycleChart extends Component {
(cycleDay.cervix.opening + cycleDay.cervix.firmness)
} else if (symptom === 'sex') {
// solo = 1 + partner = 2
acc.sex = cycleDay.sex && (cycleDay.sex.solo + cycleDay.sex.partner)
acc.sex = cycleDay.sex && (cycleDay.sex.solo + 2 * cycleDay.sex.partner)
} else if (symptom === 'pain') {
// is any pain documented?
acc.pain = cycleDay.pain &&
@@ -144,20 +145,33 @@ export default class CycleChart extends Component {
>
{!this.state.chartLoaded &&
<View style={{width: '100%', justifyContent: 'center', alignItems: 'center'}}>
<AppText>Loading...</AppText>
<AppText>{labels.loading}</AppText>
</View>
}
{this.state.chartHeight && this.state.chartLoaded &&
<View
style={[styles.yAxis, {
height: this.columnHeight,
marginTop: this.symptomRowHeight
}]}
>
{makeYAxisLabels(this.columnHeight)}
<View>
<View style={[styles.yAxis, {height: this.symptomRowHeight}]}>
{this.symptomRowSymptoms.map(symptomName => {
return <View key={symptomName} style={{flex: 1}}>
<AppText>{symptomName[0]}</AppText>
</View>
})}
</View>
<View style={[styles.yAxis, {height: this.columnHeight}]}>
{makeYAxisLabels(this.columnHeight)}
</View>
<View style={[styles.yAxis, {height: this.xAxisHeight}]}>
<AppText style = {[styles.column.label.number, styles.yAxisLabels.cycleDayLabel]}>
{labels.cycleDayWithLinebreak}
</AppText>
<AppText style={[styles.column.label.date,styles.yAxisLabels.dateLabel]}>
{labels.date}
</AppText>
</View>
</View>}
{this.state.chartHeight && this.state.chartLoaded &&
makeHorizontalGrid(this.columnHeight, this.symptomRowHeight)
}
+24 -19
View File
@@ -3,7 +3,8 @@ import {
Text, View, TouchableOpacity
} from 'react-native'
import Svg,{ G, Rect, Line } from 'react-native-svg'
import Icon from 'react-native-vector-icons/Entypo'
import { LocalDate } from 'js-joda'
import moment from 'moment'
import styles from './styles'
import config from '../../config'
import { getOrCreateCycleDay } from '../../db'
@@ -88,13 +89,18 @@ export default class DayColumn extends Component {
}
const cycleDayNumber = this.getCycleDayNumber(dateString)
const shortDate = dateString.split('-').slice(1).join('-')
const dayDate = LocalDate.parse(dateString)
const shortDate = dayDate.dayOfMonth() === 1 ?
moment(dateString, "YYYY-MM-DD").format('MMM')
:
moment(dateString, "YYYY-MM-DD").format('Do')
const boldDateLabel = dayDate.dayOfMonth() === 1 ? {fontWeight: 'bold'} : {}
const cycleDayLabel = (
<Text style = {label.number}>
{cycleDayNumber ? cycleDayNumber : ' '}
</Text>)
const dateLabel = (
<Text style = {label.date}>
<Text style = {[label.date, boldDateLabel]}>
{shortDate}
</Text>
)
@@ -116,10 +122,9 @@ export default class DayColumn extends Component {
symptomHeight={symptomHeight}
key='bleeding'
>
<Icon
name='drop'
size={12}
color={styles.bleedingIconShades[this.props.bleeding]}
<View
{...styles.symptomIcon}
backgroundColor={styles.iconShades.bleeding[this.props.bleeding]}
/>
</SymptomIconView>
),
@@ -130,8 +135,8 @@ export default class DayColumn extends Component {
key='mucus'
>
<View
{...styles.mucusIcon}
backgroundColor={styles.mucusIconShades[this.props.mucus]}
{...styles.symptomIcon}
backgroundColor={styles.iconShades.mucus[this.props.mucus]}
/>
</SymptomIconView>
),
@@ -142,9 +147,9 @@ export default class DayColumn extends Component {
key='cervix'
>
<View
{...styles.mucusIcon}
{...styles.symptomIcon}
// cervix is sum of openess and firmness - fertile only when closed and hard (=0)
backgroundColor={this.props.cervix > 0 ? 'blue' : 'green'}
backgroundColor={this.props.cervix > 0 ? styles.iconShades.cervix[2] : styles.iconShades.cervix[0]}
/>
</SymptomIconView>
),
@@ -155,8 +160,8 @@ export default class DayColumn extends Component {
key='sex'
>
<View
{...styles.mucusIcon}
backgroundColor='orange'
{...styles.symptomIcon}
backgroundColor={styles.iconShades.sex[this.props.sex - 1]}
/>
</SymptomIconView>
),
@@ -167,8 +172,8 @@ export default class DayColumn extends Component {
key='desire'
>
<View
{...styles.mucusIcon}
backgroundColor='red'
{...styles.symptomIcon}
backgroundColor={styles.iconShades.desire[this.props.desire]}
/>
</SymptomIconView>
),
@@ -179,8 +184,8 @@ export default class DayColumn extends Component {
key='pain'
>
<View
{...styles.mucusIcon}
backgroundColor='blue'
{...styles.symptomIcon}
backgroundColor={styles.iconShades.pain}
/>
</SymptomIconView>
),
@@ -191,8 +196,8 @@ export default class DayColumn extends Component {
key='note'
>
<View
{...styles.mucusIcon}
backgroundColor='green'
{...styles.symptomIcon}
backgroundColor={styles.iconShades.note}
/>
</SymptomIconView>
)
+50 -23
View File
@@ -8,6 +8,7 @@ const lineWidth = 1.5
const colorLtl = '#feb47b'
const gridColor = 'lightgrey'
const gridLineWidth = 0.5
const numberLabelFontSize = 13
const styles = {
curve: {
@@ -32,10 +33,11 @@ const styles = {
color: 'grey',
fontSize: 9,
fontWeight: '100',
textAlign: 'center',
},
number: {
color: primaryColor,
fontSize: 13,
fontSize: numberLabelFontSize,
textAlign: 'center',
}
},
@@ -48,37 +50,62 @@ const styles = {
fill: 'transparent'
}
},
bleedingIcon: {
fill: '#fb2e01',
scale: 0.6,
x: 6,
y: 3
},
bleedingIconShades: shadesOfRed,
mucusIcon: {
symptomIcon: {
width: 12,
height: 12,
borderRadius: 50,
},
mucusIconShades: [
'#fef0e4',
'#fee1ca',
'#fed2af',
'#fec395',
'#feb47b'
],
iconShades: {
'bleeding': shadesOfRed,
'mucus': [
'#e3e7ed',
'#c8cfdc',
'#acb8cb',
'#91a0ba',
'#7689a9'
],
'cervix': [
'#f0e19d',
'#e9d26d',
'#e2c33c',
'#dbb40c',
],
'sex': [
'#a87ca2',
'#8b5083',
'#6f2565',
],
'desire': [
'#c485a6',
'#b15c89',
'#9e346c',
],
'pain': ['#bccd67'],
'note': ['#6CA299']
},
yAxis: {
width: 27,
borderRightWidth: 0.5,
borderRightWidth: 1,
borderColor: 'lightgrey',
borderStyle: 'solid'
},
yAxisLabel: {
position: 'absolute',
left: 3,
color: 'grey',
fontSize: 11,
textAlign: 'left'
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'
}
},
horizontalGrid: {
position:'absolute',
+3 -3
View File
@@ -8,7 +8,7 @@ import { AppText } from '../app-text'
export function makeYAxisLabels(columnHeight) {
const units = unitObservable.value
const scaleMax = scaleObservable.value.max
const style = styles.yAxisLabel
const style = styles.yAxisLabels.tempScale
return getTickPositions(columnHeight).map((y, i) => {
const tick = scaleMax - i * units
@@ -17,10 +17,10 @@ export function makeYAxisLabels(columnHeight) {
let tickBold
if (units === 0.1) {
showTick = (tick * 10 % 2) ? false : true
tickBold = tick * 10 % 5 ? {} : {fontWeight: 'bold'}
tickBold = tick * 10 % 5 ? {} : {fontWeight: 'bold', fontSize: 11}
} else {
showTick = (tick * 10 % 5) ? false : true
tickBold = tick * 10 % 10 ? {} : {fontWeight: 'bold'}
tickBold = tick * 10 % 10 ? {} : {fontWeight: 'bold', fontSize: 11}
}
// this eyeballing is sadly necessary because RN does not
// support percentage values for transforms, which we'd need
+49 -18
View File
@@ -6,13 +6,21 @@ import {
Dimensions
} from 'react-native'
import { LocalDate } from 'js-joda'
import Svg, { G } from 'react-native-svg'
import Header from '../header'
import { getOrCreateCycleDay } from '../../db'
import cycleModule from '../../lib/cycle'
import Icon from 'react-native-vector-icons/FontAwesome'
import styles, { iconStyles } from '../../styles'
import styles from '../../styles'
import * as labels from './labels/labels'
import { AppText } from '../app-text'
import BleedingIcon from '../../assets/bleeding'
import CervixIcon from '../../assets/cervix'
import DesireIcon from '../../assets/desire'
import MucusIcon from '../../assets/mucus'
import NoteIcon from '../../assets/note'
import PainIcon from '../../assets/pain'
import SexIcon from '../../assets/sex'
import TemperatureIcon from '../../assets/temperature'
const bleedingLabels = labels.bleeding
const feelingLabels = labels.mucus.feeling.categories
@@ -68,47 +76,64 @@ export default class CycleDayOverView extends Component {
onPress={() => this.navigate('BleedingEditView')}
data={getLabel('bleeding', cycleDay.bleeding)}
disabled={dateInFuture}
/>
>
<BleedingIcon viewBox='10 10 320 400' />
</SymptomBox>
<SymptomBox
title='Temperature'
onPress={() => this.navigate('TemperatureEditView')}
data={getLabel('temperature', cycleDay.temperature)}
disabled={dateInFuture}
/>
>
<TemperatureIcon viewBox='10 10 320 400' />
</SymptomBox>
<SymptomBox
title='Mucus'
onPress={() => this.navigate('MucusEditView')}
data={getLabel('mucus', cycleDay.mucus)}
disabled={dateInFuture}
/>
>
<MucusIcon viewBox='10 10 320 400' />
</SymptomBox>
<SymptomBox
title='Cervix'
onPress={() => this.navigate('CervixEditView')}
data={getLabel('cervix', cycleDay.cervix)}
disabled={dateInFuture}
/>
>
<CervixIcon viewBox='10 10 320 440' />
</SymptomBox>
<SymptomBox
title='Desire'
onPress={() => this.navigate('DesireEditView')}
data={getLabel('desire', cycleDay.desire)}
disabled={dateInFuture}
/>
>
<DesireIcon viewBox='10 10 320 380' />
</SymptomBox>
<SymptomBox
title='Sex'
onPress={() => this.navigate('SexEditView')}
data={getLabel('sex', cycleDay.sex)}
disabled={dateInFuture}
/>
>
<SexIcon viewBox='10 10 320 400' />
</SymptomBox>
<SymptomBox
title='Pain'
onPress={() => this.navigate('PainEditView')}
data={getLabel('pain', cycleDay.pain)}
/>
disabled={dateInFuture}
>
<PainIcon viewBox='10 10 300 400' />
</SymptomBox>
<SymptomBox
title='Note'
onPress={() => this.navigate('NoteEditView')}
data={getLabel('note', cycleDay.note)}
/>
>
<NoteIcon viewBox='10 10 270 400' />
</SymptomBox>
{/* this is just to make the last row adhere to the grid
(and) because there are no pseudo properties in RN */}
<FillerBoxes />
@@ -221,10 +246,6 @@ class SymptomBox extends Component {
render() {
const d = this.props.data
const boxActive = d ? styles.symptomBoxActive : {}
const iconActive = d ? iconStyles.symptomBoxActive : {}
const iconStyle = Object.assign(
{}, iconStyles.symptomBox, iconActive, disabledStyle
)
const textActive = d ? styles.symptomTextActive : {}
const disabledStyle = this.props.disabled ? styles.symptomInFuture : {}
@@ -234,10 +255,20 @@ class SymptomBox extends Component {
disabled={this.props.disabled}
>
<View style={[styles.symptomBox, boxActive, disabledStyle]}>
<Icon
name='thermometer'
{...iconStyle}
/>
{this.props.children ?
React.Children.map(this.props.children, child => {
return (
<Svg width={100} height={50} viewBox={child.props.viewBox}>
<G fill={d ? 'white' : 'black'}>
{child}
</G>
</Svg>
)
})
: null
}
<AppText style={[textActive, disabledStyle]}>
{this.props.title}
</AppText>
+2
View File
@@ -43,6 +43,8 @@ export const sex = {
patch: 'Patch',
ring: 'Ring',
implant: 'Implant',
diaphragm: 'Diaphragm',
none: 'None',
other: 'Other',
activityExplainer: 'Were you sexually active today?',
contraceptiveExplainer: 'Did you use contraceptives?'
@@ -53,7 +53,8 @@ export default class ActionButtonFooter extends Component {
return (
<View style={styles.menu}>
{buttons.map(({ title, action, disabledCondition, icon }, i) => {
const textStyle = disabledCondition ? styles.menuTextInActive : styles.menuText
const textStyle = [styles.menuText]
if (disabledCondition) textStyle.push(styles.menuTextInActive)
const iconStyle = disabledCondition ?
Object.assign({}, iconStyles.menuIcon, iconStyles.menuIconInactive) :
iconStyles.menuIcon
+6
View File
@@ -37,6 +37,12 @@ const contraceptiveBoxes = [{
}, {
label: labels.implant,
stateKey: 'implant'
}, {
label: labels.diaphragm,
stateKey: 'diaphragm'
}, {
label: labels.none,
stateKey: 'none'
}, {
label: labels.other,
stateKey: 'other'
+7 -2
View File
@@ -1,7 +1,8 @@
import React, { Component } from 'react'
import {
View,
Text
Text,
Dimensions
} from 'react-native'
import styles, { iconStyles } from '../styles'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
@@ -10,9 +11,11 @@ import { formatDateForViewHeader } from '../components/cycle-day/labels/format'
export default class Header extends Component {
render() {
const middle = Dimensions.get('window').width / 2
return (
this.props.isCycleDayOverView ?
<View style={[styles.header, styles.headerCycleDay]}>
<View style={styles.accentCircle} left={middle - styles.accentCircle.width / 2}/>
<Icon
name='arrow-left-drop-circle'
{...iconStyles.navigationArrow}
@@ -35,6 +38,7 @@ export default class Header extends Component {
</View >
: this.props.isSymptomView ?
<View style={[styles.header, styles.headerSymptom]}>
<View style={styles.accentCircle} left={middle - styles.accentCircle.width / 2}/>
<Icon
name='keyboard-backspace'
{...iconStyles.symptomHeaderIcons}
@@ -53,7 +57,8 @@ export default class Header extends Component {
</View>
:
<View style={styles.header}>
<Text style={styles.dateHeader}>
<View style={styles.accentCircle} />
<Text style={styles.headerText}>
{this.props.title}
</Text>
</View >
+17 -1
View File
@@ -8,7 +8,10 @@ export const shared = {
incorrectPasswordMessage: 'That password is incorrect.',
tryAgain: 'Try again',
ok: 'OK',
unlock: 'Unlock'
unlock: 'Unlock',
date: 'Date',
cycleDayWithLinebreak: 'Cycle\nday',
loading: 'Loading ...'
}
export const settings = {
@@ -54,6 +57,11 @@ export const settings = {
timeSet: time => `Daily reminder set for ${time}`,
notification: 'Record your morning temperature'
},
periodReminder: {
title: 'Next period reminder',
reminderText: 'Get a notification 3 days before your next period is likely to start.',
notification: daysToEndOfPrediction => `Your next period is likely to start in 3 to ${daysToEndOfPrediction} days.`
},
passwordSettings: {
title: 'App password',
explainerDisabled: "Encrypt the app's database with a password. You need to enter the password every time the app is started.",
@@ -86,6 +94,14 @@ export const headerTitles = {
PainEditView: 'Pain'
}
export const menuTitles = {
Home: 'Home',
Calendar: 'Calendar',
Chart: 'Chart',
Stats: 'Stats',
Settings: 'Settings',
}
export const stats = {
cycleLengthTitle: 'Cycle length',
cycleLengthExplainer: 'Basic statistics about the length of your cycles.',
+6 -5
View File
@@ -28,14 +28,15 @@ export default class Menu extends Component {
}
render() {
const t = this.props.titles
return (
<View style={styles.menu}>
{[
{ title: 'Home', icon: 'home', onPress: () => this.goTo('Home') },
{ title: 'Calendar', icon: 'calendar-range', onPress: () => this.goTo('Calendar') },
{ title: 'Chart', icon: 'chart-line', onPress: () => this.goTo('Chart') },
{ title: 'Stats', icon: 'chart-pie', onPress: () => this.goTo('Stats') },
{ title: 'Settings', icon: 'settings', onPress: () => this.goTo('Settings') },
{ title: t.Home, icon: 'home', onPress: () => this.goTo('Home') },
{ title: t.Calendar, icon: 'calendar-range', onPress: () => this.goTo('Calendar') },
{ title: t.Chart, icon: 'chart-line', onPress: () => this.goTo('Chart') },
{ title: t.Stats, icon: 'chart-pie', onPress: () => this.goTo('Stats') },
{ title: t.Settings, icon: 'settings', onPress: () => this.goTo('Settings') },
].map(this.makeMenuItem)}
</View >
)
+2
View File
@@ -8,6 +8,7 @@ import styles from '../../styles/index'
import { settings as labels } from '../labels'
import { AppText } from '../app-text'
import TempReminderPicker from './temp-reminder-picker'
import PeriodReminderPicker from './period-reminder'
import TempSlider from './temp-slider'
import openImportDialogAndImport from './import-dialog'
import openShareDialogAndExport from './export-dialog'
@@ -30,6 +31,7 @@ export default class Settings extends Component {
<AppText>{labels.tempScale.segmentExplainer}</AppText>
<TempSlider/>
</View>
<PeriodReminderPicker/>
<PasswordSetting />
<View style={styles.settingsSegment}>
<AppText style={styles.settingsSegmentTitle}>
+41
View File
@@ -0,0 +1,41 @@
import React, { Component } from 'react'
import {
View,
Switch
} from 'react-native'
import { AppText } from '../app-text'
import {
periodReminderObservable,
savePeriodReminder
} from '../../local-storage'
import styles from '../../styles/index'
import { settings as labels } from '../labels'
export default class PeriodReminderPicker extends Component {
constructor(props) {
super(props)
this.state = periodReminderObservable.value
}
render() {
return (
<View style={styles.settingsSegment}>
<AppText style={styles.settingsSegmentTitle}>
{labels.periodReminder.title}
</AppText>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<View style={{ flex: 1 }}>
<AppText>{labels.periodReminder.reminderText}</AppText>
</View>
<Switch
value={this.state.enabled}
onValueChange={switchOn => {
this.setState({ enabled: switchOn })
savePeriodReminder({enabled: switchOn})
}}
/>
</View>
</View>
)
}
}