Symptom view redesign
This commit is contained in:
committed by
Sofiya Tepikin
parent
ef16cfd041
commit
885da5c293
+10
-12
@@ -1,5 +1,5 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { View, BackHandler } from 'react-native'
|
import { BackHandler, StyleSheet, View } from 'react-native'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
@@ -10,7 +10,7 @@ import { getNavigation, navigate, goBack } from '../slices/navigation'
|
|||||||
import Header from './header'
|
import Header from './header'
|
||||||
import Menu from './menu'
|
import Menu from './menu'
|
||||||
import { viewsList } from './views'
|
import { viewsList } from './views'
|
||||||
import { isSymptomView, isSettingsView } from './pages'
|
import { isSettingsView } from './pages'
|
||||||
|
|
||||||
import { headerTitles } from '../i18n/en/labels'
|
import { headerTitles } from '../i18n/en/labels'
|
||||||
import setupNotifications from '../lib/notifications'
|
import setupNotifications from '../lib/notifications'
|
||||||
@@ -64,9 +64,7 @@ class App extends Component {
|
|||||||
const Page = viewsList[currentPage]
|
const Page = viewsList[currentPage]
|
||||||
const title = headerTitles[currentPage]
|
const title = headerTitles[currentPage]
|
||||||
|
|
||||||
const isSymptomEditView = isSymptomView(currentPage)
|
|
||||||
const isSettingsSubView = isSettingsView(currentPage)
|
const isSettingsSubView = isSettingsView(currentPage)
|
||||||
const isCycleDayView = currentPage === 'CycleDay'
|
|
||||||
|
|
||||||
const headerProps = {
|
const headerProps = {
|
||||||
title,
|
title,
|
||||||
@@ -79,21 +77,21 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={styles.container}>
|
||||||
{
|
|
||||||
!isSymptomEditView &&
|
|
||||||
!isCycleDayView &&
|
|
||||||
<Header { ...headerProps } />
|
<Header { ...headerProps } />
|
||||||
}
|
|
||||||
|
|
||||||
<Page { ...pageProps } />
|
<Page { ...pageProps } />
|
||||||
|
<Menu />
|
||||||
{ !isSymptomEditView && <Menu /> }
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
return({
|
return({
|
||||||
date: getDate(state),
|
date: getDate(state),
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class CycleChart extends Component {
|
|||||||
prepareSymptomData = () => {
|
prepareSymptomData = () => {
|
||||||
this.symptomRowSymptoms = SYMPTOMS.filter((symptomName) => {
|
this.symptomRowSymptoms = SYMPTOMS.filter((symptomName) => {
|
||||||
return this.cycleDaysSortedByDate.some(cycleDay => {
|
return this.cycleDaysSortedByDate.some(cycleDay => {
|
||||||
return cycleDay[symptomName]
|
return (symptomName !== 'temperature') && cycleDay[symptomName]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
this.chartSymptoms = [...this.symptomRowSymptoms]
|
this.chartSymptoms = [...this.symptomRowSymptoms]
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Image, StyleSheet, TouchableOpacity, View } from 'react-native'
|
import { Image, StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import AppIcon from '../common/app-icon'
|
|
||||||
import AppText from '../common/app-text'
|
import AppText from '../common/app-text'
|
||||||
|
import CloseIcon from '../common/close-icon'
|
||||||
|
|
||||||
import { Colors, Containers, Sizes, Spacing } from '../../styles/redesign'
|
import { Containers, Spacing } from '../../styles/redesign'
|
||||||
import { chart } from '../../i18n/en/labels'
|
import { chart } from '../../i18n/en/labels'
|
||||||
|
|
||||||
const image = require('../../assets/swipe.png')
|
const image = require('../../assets/swipe.png')
|
||||||
@@ -17,9 +17,7 @@ const Tutorial = ({ onClose }) => {
|
|||||||
<View style={styles.textContainer}>
|
<View style={styles.textContainer}>
|
||||||
<AppText>{chart.tutorial}</AppText>
|
<AppText>{chart.tutorial}</AppText>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity onPress={onClose} style={styles.iconContainer}>
|
<CloseIcon onClose={onClose} />
|
||||||
<AppIcon name='cross' color={Colors.orange} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -33,10 +31,6 @@ const styles = StyleSheet.create({
|
|||||||
...Containers.rowContainer,
|
...Containers.rowContainer,
|
||||||
padding: Spacing.large
|
padding: Spacing.large
|
||||||
},
|
},
|
||||||
iconContainer: {
|
|
||||||
alignSelf: 'flex-start',
|
|
||||||
marginBottom: Sizes.base
|
|
||||||
},
|
|
||||||
image: {
|
image: {
|
||||||
height: 40
|
height: 40
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Modal, StyleSheet, TouchableOpacity } from 'react-native'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
const AppModal = ({ children, onClose }) => {
|
||||||
|
return(
|
||||||
|
<Modal
|
||||||
|
animationType='fade'
|
||||||
|
onRequestClose={onClose}
|
||||||
|
transparent={true}
|
||||||
|
visible={true}
|
||||||
|
>
|
||||||
|
<TouchableOpacity onPress={onClose} style={styles.blackBackground} />
|
||||||
|
{children}
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppModal.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
onClose: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
blackBackground: {
|
||||||
|
backgroundColor: 'black',
|
||||||
|
flex: 1,
|
||||||
|
opacity: 0.5,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppModal
|
||||||
@@ -20,7 +20,7 @@ const AppSwitch = ({ onToggle, text, value }) => {
|
|||||||
AppSwitch.propTypes = {
|
AppSwitch.propTypes = {
|
||||||
onToggle: PropTypes.func.isRequired,
|
onToggle: PropTypes.func.isRequired,
|
||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
value: PropTypes.bool.isRequired
|
value: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { StyleSheet, TextInput } from 'react-native'
|
import { StyleSheet, TextInput } from 'react-native'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import { Colors, Spacing, Typography } from '../../styles/redesign'
|
import { Colors, Spacing, Typography } from '../../styles/redesign'
|
||||||
|
|
||||||
const AppTextInput = ({ ...props }) => {
|
const AppTextInput = ({ style, ...props }) => {
|
||||||
return <TextInput style={styles.input} {...props} />
|
return <TextInput style={[styles.input, style]} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
AppTextInput.propTypes = {
|
||||||
|
style: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
@@ -16,6 +21,7 @@ const styles = StyleSheet.create({
|
|||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
color: Colors.greyDark,
|
color: Colors.greyDark,
|
||||||
marginTop: Spacing.base,
|
marginTop: Spacing.base,
|
||||||
|
minWidth: '80%',
|
||||||
paddingHorizontal: Spacing.base,
|
paddingHorizontal: Spacing.base,
|
||||||
...Typography.mainText
|
...Typography.mainText
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,20 @@ import React from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { StyleSheet, TouchableOpacity } from 'react-native'
|
import { StyleSheet, TouchableOpacity } from 'react-native'
|
||||||
|
|
||||||
|
import AppIcon from './app-icon'
|
||||||
import AppText from './app-text'
|
import AppText from './app-text'
|
||||||
|
|
||||||
import { Colors, Fonts, Spacing } from '../../styles/redesign'
|
import { Colors, Fonts, Spacing } from '../../styles/redesign'
|
||||||
|
|
||||||
const Button = ({ children, isCTA, isSmall, onPress, testID, ...props }) => {
|
const Button = ({
|
||||||
|
children,
|
||||||
|
iconName,
|
||||||
|
isCTA,
|
||||||
|
isSmall,
|
||||||
|
onPress,
|
||||||
|
testID,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
const buttonStyle = isCTA ? styles.cta : styles.regular
|
const buttonStyle = isCTA ? styles.cta : styles.regular
|
||||||
const textCTA = isCTA ? styles.buttonTextBold : styles.buttonTextRegular
|
const textCTA = isCTA ? styles.buttonTextBold : styles.buttonTextRegular
|
||||||
const textStyle = [textCTA, isSmall ? textSmall : text]
|
const textStyle = [textCTA, isSmall ? textSmall : text]
|
||||||
@@ -19,12 +28,14 @@ const Button = ({ children, isCTA, isSmall, onPress, testID, ...props }) => {
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<AppText style={textStyle}>{children}</AppText>
|
<AppText style={textStyle}>{children}</AppText>
|
||||||
|
{iconName && <AppIcon color={Colors.orange} name={iconName} />}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button.propTypes = {
|
Button.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
iconName: PropTypes.string,
|
||||||
isCTA: PropTypes.bool,
|
isCTA: PropTypes.bool,
|
||||||
isSmall: PropTypes.bool,
|
isSmall: PropTypes.bool,
|
||||||
onPress: PropTypes.func,
|
onPress: PropTypes.func,
|
||||||
@@ -48,8 +59,10 @@ const textSmall = {
|
|||||||
|
|
||||||
const button = {
|
const button = {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
margin: Spacing.base
|
margin: Spacing.base,
|
||||||
|
minWidth: '15%'
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, TouchableOpacity } from 'react-native'
|
||||||
|
|
||||||
|
import AppIcon from './app-icon'
|
||||||
|
|
||||||
|
import { Colors, Sizes } from '../../styles/redesign'
|
||||||
|
|
||||||
|
const CloseIcon = ({ onClose, ...props }) => {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onClose}
|
||||||
|
style={styles.container}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<AppIcon name='cross' color={Colors.orange} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseIcon.propTypes = {
|
||||||
|
onClose: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
marginBottom: Sizes.base
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default CloseIcon
|
||||||
@@ -33,7 +33,7 @@ const styles = StyleSheet.create({
|
|||||||
container: {
|
container: {
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
borderBottomWidth: 2,
|
borderBottomWidth: 2,
|
||||||
borderBottomColor: Colors.grey,
|
borderBottomColor: Colors.greyLight,
|
||||||
paddingBottom: Spacing.base,
|
paddingBottom: Spacing.base,
|
||||||
...segmentContainer
|
...segmentContainer
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { View, Dimensions } from 'react-native'
|
|
||||||
import styles from '../../styles'
|
|
||||||
|
|
||||||
export default class FillerBoxes extends Component {
|
|
||||||
render() {
|
|
||||||
const n = Dimensions.get('window').width / styles.symptomBox.width
|
|
||||||
const fillerBoxes = []
|
|
||||||
for (let i = 0; i < Math.ceil(n); i++) {
|
|
||||||
fillerBoxes.push(
|
|
||||||
<View
|
|
||||||
width={styles.symptomBox.width}
|
|
||||||
height={0}
|
|
||||||
key={i.toString()}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return fillerBoxes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { View, TouchableOpacity } from 'react-native'
|
|
||||||
|
|
||||||
import AppText from '../common/app-text'
|
|
||||||
import DripIcon from '../../assets/drip-icons'
|
|
||||||
|
|
||||||
import styles from '../../styles'
|
|
||||||
|
|
||||||
import { headerTitles as symptomTitles } from '../../i18n/en/labels'
|
|
||||||
import * as labels from '../../i18n/en/cycle-day'
|
|
||||||
const bleedingLabels = labels.bleeding.labels
|
|
||||||
const intensityLabels = labels.intensity
|
|
||||||
const sexLabels = labels.sex.categories
|
|
||||||
const contraceptiveLabels = labels.contraceptives.categories
|
|
||||||
const painLabels = labels.pain.categories
|
|
||||||
const moodLabels = labels.mood.categories
|
|
||||||
|
|
||||||
function isNumber(val) {
|
|
||||||
return typeof val === 'number'
|
|
||||||
}
|
|
||||||
|
|
||||||
const l = {
|
|
||||||
bleeding: ({ value, exclude }) => {
|
|
||||||
if (isNumber(value)) {
|
|
||||||
const bleedingLabel = bleedingLabels[value]
|
|
||||||
return exclude ? `(${bleedingLabel})` : bleedingLabel
|
|
||||||
}
|
|
||||||
},
|
|
||||||
temperature: ({ value, time, exclude }) => {
|
|
||||||
if (isNumber(value)) {
|
|
||||||
let temperatureLabel = `${value} °C`
|
|
||||||
if (time) {
|
|
||||||
temperatureLabel += ` - ${time}`
|
|
||||||
}
|
|
||||||
if (exclude) {
|
|
||||||
temperatureLabel = `(${temperatureLabel})`
|
|
||||||
}
|
|
||||||
return temperatureLabel
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mucus: mucus => {
|
|
||||||
const filledCategories = ['feeling', 'texture'].filter(c => isNumber(mucus[c]))
|
|
||||||
let label = filledCategories.map(category => {
|
|
||||||
return labels.mucus.subcategories[category] + ': ' + labels.mucus[category].categories[mucus[category]]
|
|
||||||
}).join(', ')
|
|
||||||
|
|
||||||
if (isNumber(mucus.value)) label += `\n => ${labels.mucusNFP[mucus.value]}`
|
|
||||||
if (mucus.exclude) label = `(${label})`
|
|
||||||
|
|
||||||
return label
|
|
||||||
},
|
|
||||||
cervix: cervix => {
|
|
||||||
const filledCategories = ['opening', 'firmness', 'position'].filter(c => isNumber(cervix[c]))
|
|
||||||
let label = filledCategories.map(category => {
|
|
||||||
return labels.cervix.subcategories[category] + ': ' + labels.cervix[category].categories[cervix[category]]
|
|
||||||
}).join(', ')
|
|
||||||
|
|
||||||
if (cervix.exclude) label = `(${label})`
|
|
||||||
|
|
||||||
return label
|
|
||||||
},
|
|
||||||
note: note => note.value,
|
|
||||||
desire: ({ value }) => {
|
|
||||||
if (isNumber(value)) {
|
|
||||||
return intensityLabels[value]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sex: sex => {
|
|
||||||
const sexLabel = []
|
|
||||||
if (sex && Object.values({...sex}).some(val => val)){
|
|
||||||
Object.keys(sex).forEach(key => {
|
|
||||||
if(sex[key] && key !== 'other' && key !== 'note') {
|
|
||||||
sexLabel.push(
|
|
||||||
sexLabels[key] ||
|
|
||||||
contraceptiveLabels[key]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if(key === 'other' && sex.other) {
|
|
||||||
let label = contraceptiveLabels[key]
|
|
||||||
if(sex.note) {
|
|
||||||
label = `${label} (${sex.note})`
|
|
||||||
}
|
|
||||||
sexLabel.push(label)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return sexLabel.join(', ')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pain: pain => {
|
|
||||||
const painLabel = []
|
|
||||||
if (pain && Object.values({...pain}).some(val => val)){
|
|
||||||
Object.keys(pain).forEach(key => {
|
|
||||||
if(pain[key] && key !== 'other' && key !== 'note') {
|
|
||||||
painLabel.push(painLabels[key])
|
|
||||||
}
|
|
||||||
if(key === 'other' && pain.other) {
|
|
||||||
let label = painLabels[key]
|
|
||||||
if(pain.note) {
|
|
||||||
label = `${label} (${pain.note})`
|
|
||||||
}
|
|
||||||
painLabel.push(label)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return painLabel.join(', ')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mood: mood => {
|
|
||||||
const moodLabel = []
|
|
||||||
if (mood && Object.values({...mood}).some(val => val)){
|
|
||||||
Object.keys(mood).forEach(key => {
|
|
||||||
if(mood[key] && key !== 'other' && key !== 'note') {
|
|
||||||
moodLabel.push(moodLabels[key])
|
|
||||||
}
|
|
||||||
if(key === 'other' && mood.other) {
|
|
||||||
let label = moodLabels[key]
|
|
||||||
if(mood.note) {
|
|
||||||
label = `${label} (${mood.note})`
|
|
||||||
}
|
|
||||||
moodLabel.push(label)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return moodLabel.join(', ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getLabel = (symptom, symptomData) => {
|
|
||||||
return symptomData && l[symptom](symptomData)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SymptomBox(
|
|
||||||
{ disabled, onPress, symptom, symptomData }) {
|
|
||||||
|
|
||||||
const data = getLabel(symptom, symptomData)
|
|
||||||
const iconName = `drip-icon-${symptom}`
|
|
||||||
|
|
||||||
const disabledStyle = disabled ? styles.symptomInFuture : null
|
|
||||||
const containerStyle = [
|
|
||||||
styles.symptomBox,
|
|
||||||
data && styles.symptomBoxActive,
|
|
||||||
disabledStyle
|
|
||||||
]
|
|
||||||
const titleStyle = [
|
|
||||||
data && styles.symptomTextActive,
|
|
||||||
disabledStyle,
|
|
||||||
{fontSize: 15}
|
|
||||||
]
|
|
||||||
const dataBoxStyle = [styles.symptomDataBox, disabledStyle]
|
|
||||||
const iconColor = data ? 'white' : 'black'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TouchableOpacity onPress={onPress} disabled={disabled} testID={iconName}>
|
|
||||||
<View style={containerStyle}>
|
|
||||||
<DripIcon name={iconName} size={50} color={iconColor} />
|
|
||||||
<AppText style={titleStyle} numberOfLines={1}>
|
|
||||||
{symptomTitles[symptom].toLowerCase()}
|
|
||||||
</AppText>
|
|
||||||
</View>
|
|
||||||
<View style={dataBoxStyle}>
|
|
||||||
<AppText style={styles.symptomDataText} numberOfLines={3}>
|
|
||||||
{data}
|
|
||||||
</AppText>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SymptomBox.propTypes = {
|
|
||||||
disabled: PropTypes.bool.isRequired,
|
|
||||||
onPress: PropTypes.func.isRequired,
|
|
||||||
symptom: PropTypes.string.isRequired,
|
|
||||||
symptomData: PropTypes.object
|
|
||||||
}
|
|
||||||
@@ -1,118 +1,89 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { ScrollView, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import AppPage from '../common/app-page'
|
||||||
|
import SymptomBox from './symptom-box'
|
||||||
|
import SymptomPageTitle from './symptom-page-title'
|
||||||
|
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { getDate, setDate } from '../../slices/date'
|
import { getDate, setDate } from '../../slices/date'
|
||||||
import { navigate } from '../../slices/navigation'
|
import { navigate } from '../../slices/navigation'
|
||||||
|
|
||||||
import { LocalDate } from 'js-joda'
|
|
||||||
import Header from '../header'
|
|
||||||
import FillerBoxes from './FillerBoxes'
|
|
||||||
import SymptomBox from './SymptomBox'
|
|
||||||
|
|
||||||
import cycleModule from '../../lib/cycle'
|
import cycleModule from '../../lib/cycle'
|
||||||
import formatDate from '../helpers/format-date'
|
import { dateToTitle } from '../helpers/format-date'
|
||||||
import { getCycleDay } from '../../db'
|
import { getCycleDay } from '../../db'
|
||||||
import styles from '../../styles'
|
import { getData } from '../helpers/cycle-day'
|
||||||
|
|
||||||
|
import { general as labels} from '../../i18n/en/cycle-day'
|
||||||
|
import { Spacing } from '../../styles/redesign'
|
||||||
|
import { SYMPTOMS } from '../../config'
|
||||||
|
|
||||||
class CycleDayOverView extends Component {
|
class CycleDayOverView extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigate: PropTypes.func,
|
navigate: PropTypes.func,
|
||||||
setDate: PropTypes.func,
|
setDate: PropTypes.func,
|
||||||
// The following are not being used,
|
|
||||||
// we could see if it's possible to not pass them from the <App />
|
|
||||||
cycleDay: PropTypes.object,
|
cycleDay: PropTypes.object,
|
||||||
date: PropTypes.string,
|
date: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
|
||||||
cycleDay: getCycleDay(props.date)
|
this.state = { cycleDay: getCycleDay(props.date), data: null }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCycleDay = (date) => {
|
updateCycleDay = (date) => {
|
||||||
this.props.setDate(date)
|
this.props.setDate(date)
|
||||||
this.setState({
|
this.setState({ cycleDay: getCycleDay(date) })
|
||||||
cycleDay: getCycleDay(date)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
goToPrevDay = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const prevDate = LocalDate.parse(date).minusDays(1).toString()
|
|
||||||
this.updateCycleDay(prevDate)
|
|
||||||
}
|
|
||||||
|
|
||||||
goToNextDay = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const nextDate = LocalDate.parse(date).plusDays(1).toString()
|
|
||||||
this.updateCycleDay(nextDate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { cycleDay } = this.state
|
const { cycleDay } = this.state
|
||||||
const { date } = this.props
|
const { date } = this.props
|
||||||
|
|
||||||
const dateInFuture = LocalDate.now().isBefore(LocalDate.parse(date))
|
|
||||||
|
|
||||||
const symptomBoxesList = [
|
|
||||||
'bleeding',
|
|
||||||
'temperature',
|
|
||||||
'mucus',
|
|
||||||
'cervix',
|
|
||||||
'desire',
|
|
||||||
'sex',
|
|
||||||
'pain',
|
|
||||||
'mood',
|
|
||||||
'note',
|
|
||||||
]
|
|
||||||
|
|
||||||
const { getCycleDayNumber } = cycleModule()
|
const { getCycleDayNumber } = cycleModule()
|
||||||
const cycleDayNumber = getCycleDayNumber(date)
|
const cycleDayNumber = getCycleDayNumber(date)
|
||||||
const headerSubtitle = cycleDayNumber && `Cycle day ${cycleDayNumber}`
|
const subtitle = cycleDayNumber && `${labels.cycleDayNumber}${cycleDayNumber}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<AppPage>
|
||||||
<Header
|
<SymptomPageTitle
|
||||||
handleBack={this.goToPrevDay}
|
reloadSymptomData={this.updateCycleDay}
|
||||||
handleNext={this.goToNextDay}
|
subtitle={subtitle}
|
||||||
title={formatDate(date)}
|
title={dateToTitle(date)}
|
||||||
subtitle={headerSubtitle}
|
|
||||||
/>
|
/>
|
||||||
<ScrollView>
|
<View style={styles.container}>
|
||||||
<View style={styles.symptomBoxesView}>
|
{SYMPTOMS.map(symptom => {
|
||||||
{
|
const symptomData = cycleDay && cycleDay[symptom]
|
||||||
symptomBoxesList.map(symptom => {
|
? cycleDay[symptom] : null
|
||||||
const symptomEditView =
|
|
||||||
`${symptom[0].toUpperCase() + symptom.substring(1)}EditView`
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : null
|
|
||||||
return(
|
return(
|
||||||
<SymptomBox
|
<SymptomBox
|
||||||
key={symptom}
|
key={symptom}
|
||||||
symptom={symptom}
|
symptom={symptom}
|
||||||
symptomData={symptomData}
|
symptomData={symptomData}
|
||||||
onPress={() => this.props.navigate(symptomEditView)}
|
symptomDataToDisplay={getData(symptom, symptomData)}
|
||||||
disabled={dateInFuture && symptom !== 'note'}
|
updateCycleDayData={this.updateCycleDay}
|
||||||
/>)
|
/>
|
||||||
})
|
)
|
||||||
}
|
})}
|
||||||
{
|
|
||||||
// this is just to make the last row adhere to the grid
|
|
||||||
// (and) because there are no pseudo properties in RN
|
|
||||||
}
|
|
||||||
<FillerBoxes />
|
|
||||||
</View>
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
</View>
|
||||||
|
</AppPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: Spacing.base
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
return({
|
return({
|
||||||
date: getDate(state),
|
date: getDate(state),
|
||||||
|
|||||||
@@ -1,29 +1,26 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View, TouchableOpacity } from 'react-native'
|
import { StyleSheet, TouchableOpacity, View } from 'react-native'
|
||||||
|
|
||||||
import AppText from '../common/app-text'
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import styles from '../../styles'
|
import { Colors, Containers } from '../../styles/redesign'
|
||||||
|
|
||||||
export default function SelectBoxGroup({ labels, onSelect, optionsState }) {
|
const SelectBoxGroup = ({ labels, optionsState, onSelect }) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.selectBoxSection}>
|
<View style={styles.container}>
|
||||||
{Object.keys(labels).map(key => {
|
{Object.keys(labels).map(key => {
|
||||||
const style = [styles.selectBox]
|
const isActive = optionsState[key]
|
||||||
const textStyle = []
|
const boxStyle = [styles.box, isActive && styles.boxActive]
|
||||||
if (optionsState[key]) {
|
const textStyle = [styles.text, isActive && styles.textActive]
|
||||||
style.push(styles.selectBoxActive)
|
|
||||||
textStyle.push(styles.selectBoxTextActive)
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => onSelect(key)}
|
|
||||||
key={key}
|
key={key}
|
||||||
|
onPress={() => onSelect(key)}
|
||||||
|
style={boxStyle}
|
||||||
>
|
>
|
||||||
<View style={style}>
|
|
||||||
<AppText style={textStyle}>{labels[key]}</AppText>
|
<AppText style={textStyle}>{labels[key]}</AppText>
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -36,3 +33,23 @@ SelectBoxGroup.propTypes = {
|
|||||||
onSelect: PropTypes.func.isRequired,
|
onSelect: PropTypes.func.isRequired,
|
||||||
optionsState: PropTypes.object.isRequired
|
optionsState: PropTypes.object.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
box: {
|
||||||
|
...Containers.box
|
||||||
|
},
|
||||||
|
boxActive: {
|
||||||
|
...Containers.boxActive
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
...Containers.selectGroupContainer
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: Colors.orange
|
||||||
|
},
|
||||||
|
textActive: {
|
||||||
|
color: 'white'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default SelectBoxGroup
|
||||||
|
|||||||
@@ -1,40 +1,27 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View, TouchableOpacity } from 'react-native'
|
import { StyleSheet, TouchableOpacity, View } from 'react-native'
|
||||||
|
|
||||||
import AppText from '../common/app-text'
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import styles from '../../styles'
|
import { Colors, Containers } from '../../styles/redesign'
|
||||||
|
|
||||||
export default function SelectTabGroup({ active, buttons, onSelect }) {
|
export default function SelectTabGroup({ activeButton, buttons, onSelect }) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.selectTabGroup}>
|
<View style={styles.container}>
|
||||||
{
|
{
|
||||||
buttons.map(({ label, value }, i) => {
|
buttons.map(({ label, value }, i) => {
|
||||||
let firstOrLastStyle
|
const isActive = value === activeButton
|
||||||
if (i === buttons.length - 1) {
|
const boxStyle = [styles.box, isActive && styles.boxActive]
|
||||||
firstOrLastStyle = styles.selectTabLast
|
const textStyle = [styles.text, isActive && styles.textActive]
|
||||||
} else if (i === 0) {
|
|
||||||
firstOrLastStyle = styles.selectTabFirst
|
|
||||||
}
|
|
||||||
let activeStyle
|
|
||||||
const isActive = value === active
|
|
||||||
if (isActive) activeStyle = styles.selectTabActive
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => onSelect(isActive ? null : value)}
|
onPress={() => onSelect(value)}
|
||||||
key={i}
|
key={i}
|
||||||
activeOpacity={1}
|
style={boxStyle}
|
||||||
>
|
>
|
||||||
<View>
|
<AppText style={textStyle}>{label}</AppText>
|
||||||
<View style={[
|
|
||||||
styles.selectTab,
|
|
||||||
firstOrLastStyle,
|
|
||||||
activeStyle
|
|
||||||
]}>
|
|
||||||
<AppText style={activeStyle}>{label}</AppText>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -44,7 +31,25 @@ export default function SelectTabGroup({ active, buttons, onSelect }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SelectTabGroup.propTypes = {
|
SelectTabGroup.propTypes = {
|
||||||
active: PropTypes.number,
|
activeButton: PropTypes.number,
|
||||||
buttons: PropTypes.array.isRequired,
|
buttons: PropTypes.array.isRequired,
|
||||||
onSelect: PropTypes.func.isRequired
|
onSelect: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
box: {
|
||||||
|
...Containers.box
|
||||||
|
},
|
||||||
|
boxActive: {
|
||||||
|
...Containers.boxActive
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
...Containers.selectGroupContainer
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: Colors.orange
|
||||||
|
},
|
||||||
|
textActive: {
|
||||||
|
color: 'white'
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, View, TouchableOpacity } from 'react-native'
|
||||||
|
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
import DripIcon from '../../assets/drip-icons'
|
||||||
|
import SymptomEditView from './symptom-edit-view'
|
||||||
|
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { getDate } from '../../slices/date'
|
||||||
|
import { isDateInFuture } from '../helpers/cycle-day'
|
||||||
|
|
||||||
|
import { Colors, Sizes, Spacing } from '../../styles/redesign'
|
||||||
|
import { headerTitles as symptomTitles } from '../../i18n/en/labels'
|
||||||
|
|
||||||
|
class SymptomBox extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
date: PropTypes.string.isRequired,
|
||||||
|
symptom: PropTypes.string.isRequired,
|
||||||
|
symptomData: PropTypes.object,
|
||||||
|
symptomDataToDisplay: PropTypes.string,
|
||||||
|
updateCycleDayData: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = { isSymptomEdited: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
onFinishEditing = () => {
|
||||||
|
const { date, updateCycleDayData } = this.props
|
||||||
|
|
||||||
|
updateCycleDayData(date)
|
||||||
|
this.setState({ isSymptomEdited: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditSymptom = () => {
|
||||||
|
this.setState({ isSymptomEdited: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { date, symptom, symptomData, symptomDataToDisplay } = this.props
|
||||||
|
const { isSymptomEdited } = this.state
|
||||||
|
const isSymptomDisabled = isDateInFuture(date) && symptom !== 'note'
|
||||||
|
const isExcluded = symptomData !== null ? symptomData.exclude : false
|
||||||
|
|
||||||
|
const iconColor = isSymptomDisabled ? Colors.greyLight : Colors.grey
|
||||||
|
const iconName = `drip-icon-${symptom}`
|
||||||
|
const symptomNameStyle = [
|
||||||
|
styles.symptomName,
|
||||||
|
(isSymptomDisabled && styles.symptomNameDisabled),
|
||||||
|
(isExcluded && styles.symptomNameExcluded)
|
||||||
|
]
|
||||||
|
const textStyle = [
|
||||||
|
styles.text,
|
||||||
|
(isSymptomDisabled && styles.textDisabled),
|
||||||
|
(isExcluded && styles.textExcluded)
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{isSymptomEdited &&
|
||||||
|
<SymptomEditView
|
||||||
|
symptom={symptom}
|
||||||
|
symptomData={symptomData}
|
||||||
|
onClose={this.onFinishEditing}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
disabled={isSymptomDisabled}
|
||||||
|
onPress={this.onEditSymptom}
|
||||||
|
style={styles.container}
|
||||||
|
testID={iconName}
|
||||||
|
>
|
||||||
|
<DripIcon
|
||||||
|
color={iconColor}
|
||||||
|
isActive={!isSymptomDisabled}
|
||||||
|
name={iconName}
|
||||||
|
size={40}
|
||||||
|
/>
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<AppText style={symptomNameStyle}>
|
||||||
|
{symptomTitles[symptom].toLowerCase()}
|
||||||
|
</AppText>
|
||||||
|
{symptomDataToDisplay &&
|
||||||
|
<AppText style={textStyle}>
|
||||||
|
{symptomDataToDisplay}
|
||||||
|
</AppText>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const excluded = {
|
||||||
|
textDecorationLine: 'line-through'
|
||||||
|
}
|
||||||
|
|
||||||
|
const hint = {
|
||||||
|
fontSize: Sizes.small,
|
||||||
|
fontStyle: 'italic'
|
||||||
|
}
|
||||||
|
|
||||||
|
const main = {
|
||||||
|
fontSize: Sizes.base,
|
||||||
|
height: Sizes.base * 2,
|
||||||
|
lineHeight: Sizes.base,
|
||||||
|
marginBottom: (-1) * Sizes.tiny,
|
||||||
|
textAlignVertical: 'center'
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 10,
|
||||||
|
elevation: 4,
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: 110,
|
||||||
|
marginBottom: Spacing.base,
|
||||||
|
paddingHorizontal: Spacing.small,
|
||||||
|
paddingVertical: Spacing.base,
|
||||||
|
width: Spacing.symptomTileWidth
|
||||||
|
},
|
||||||
|
symptomName: {
|
||||||
|
color: Colors.purple,
|
||||||
|
...main
|
||||||
|
},
|
||||||
|
symptomNameDisabled: {
|
||||||
|
color: Colors.grey
|
||||||
|
},
|
||||||
|
symptomNameExcluded: {
|
||||||
|
color: Colors.greyDark,
|
||||||
|
...excluded
|
||||||
|
},
|
||||||
|
textContainer: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
marginLeft: Spacing.small,
|
||||||
|
maxWidth: Spacing.textWidth
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
...hint
|
||||||
|
},
|
||||||
|
textDisabled: {
|
||||||
|
color: Colors.greyLight
|
||||||
|
},
|
||||||
|
textExcluded: {
|
||||||
|
color: Colors.grey,
|
||||||
|
...excluded
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return({
|
||||||
|
date: getDate(state),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null,
|
||||||
|
)(SymptomBox)
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { ScrollView, StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppModal from '../common/app-modal'
|
||||||
|
import AppSwitch from '../common/app-switch'
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
import AppTextInput from '../common/app-text-input'
|
||||||
|
import Button from '../common/button'
|
||||||
|
import CloseIcon from '../common/close-icon'
|
||||||
|
import Segment from '../common/segment'
|
||||||
|
import SelectBoxGroup from './select-box-group'
|
||||||
|
import SelectTabGroup from './select-tab-group'
|
||||||
|
import Temperature from './temperature'
|
||||||
|
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { getDate } from '../../slices/date'
|
||||||
|
import { blank, save, shouldShow, symtomPage } from '../helpers/cycle-day'
|
||||||
|
|
||||||
|
import { shared as sharedLabels } from '../../i18n/en/labels'
|
||||||
|
import info from '../../i18n/en/symptom-info'
|
||||||
|
import { Containers, Sizes } from '../../styles/redesign'
|
||||||
|
|
||||||
|
class SymptomEditView extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
date: PropTypes.string.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
symptom: PropTypes.string.isRequired,
|
||||||
|
symptomData: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
const { symptomData, symptom } = this.props
|
||||||
|
const data = symptomData ? symptomData : blank[symptom]
|
||||||
|
|
||||||
|
const symptomConfig = symtomPage[symptom]
|
||||||
|
const shouldShowExclude = shouldShow(symptomConfig.excludeText)
|
||||||
|
const shouldShowNote = shouldShow(symptomConfig.note)
|
||||||
|
const shouldBoxGroup = shouldShow(symptomConfig.selectBoxGroups)
|
||||||
|
const shouldTabGroup = shouldShow(symptomConfig.selectTabGroups)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
data,
|
||||||
|
shouldShowExclude,
|
||||||
|
shouldShowInfo: false,
|
||||||
|
shouldShowNote,
|
||||||
|
shouldBoxGroup,
|
||||||
|
shouldTabGroup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.saveData()
|
||||||
|
}
|
||||||
|
|
||||||
|
getParsedData = () => JSON.parse(JSON.stringify(this.state.data))
|
||||||
|
|
||||||
|
onEditNote = (note) => {
|
||||||
|
const data = this.getParsedData()
|
||||||
|
const { symptom } = this.props
|
||||||
|
|
||||||
|
if (symptom === 'note') {
|
||||||
|
Object.assign(data, { value: note })
|
||||||
|
} else {
|
||||||
|
data['note'] = note
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ data })
|
||||||
|
}
|
||||||
|
|
||||||
|
onExcludeToggle = () => {
|
||||||
|
const data = this.getParsedData()
|
||||||
|
Object.assign(data, { exclude: !data.exclude })
|
||||||
|
|
||||||
|
this.setState({ data })
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressLearnMore = () => {
|
||||||
|
this.setState({ shouldShowInfo: !this.state.shouldShowInfo })
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove = () => {
|
||||||
|
this.saveData(true)
|
||||||
|
this.props.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
onSave = () => {
|
||||||
|
this.saveData()
|
||||||
|
this.props.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
onSaveTemperature = (value, field) => {
|
||||||
|
const data = this.getParsedData()
|
||||||
|
const dataToSave = field === 'value'
|
||||||
|
? { [field]: Number(value) } : { [field]: value }
|
||||||
|
Object.assign(data, { ...dataToSave })
|
||||||
|
|
||||||
|
this.setState({ data })
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectBox = (key) => {
|
||||||
|
const data = this.getParsedData()
|
||||||
|
if (key === "other") {
|
||||||
|
Object.assign(data, {
|
||||||
|
note: null,
|
||||||
|
[key]: !this.state.data[key]
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Object.assign(data, { [key]: !this.state.data[key] })
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ data })
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectBoxNote= (value) => {
|
||||||
|
const data = this.getParsedData()
|
||||||
|
Object.assign(data, { note: value !== '' ? value : null })
|
||||||
|
|
||||||
|
this.setState({ data })
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectTab = (group, value) => {
|
||||||
|
const data = this.getParsedData()
|
||||||
|
Object.assign(data, { [group.key]: value })
|
||||||
|
|
||||||
|
this.setState({ data })
|
||||||
|
}
|
||||||
|
|
||||||
|
saveData = (shouldDeleteData) => {
|
||||||
|
const { date, symptom } = this.props
|
||||||
|
const { data } = this.state
|
||||||
|
save[symptom](data, date, shouldDeleteData)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { onClose, symptom } = this.props
|
||||||
|
const { data,
|
||||||
|
shouldShowExclude,
|
||||||
|
shouldShowInfo,
|
||||||
|
shouldShowNote,
|
||||||
|
shouldBoxGroup,
|
||||||
|
shouldTabGroup
|
||||||
|
} = this.state
|
||||||
|
const iconName = shouldShowInfo ? "chevron-down" : "chevron-up"
|
||||||
|
const noteText = symptom === 'note' ? data.value : data.note
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppModal onClose={onClose}>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.modalContainer}
|
||||||
|
pagingEnabled={true}
|
||||||
|
style={styles.modalWindow}
|
||||||
|
>
|
||||||
|
<View style={styles.headerContainer}>
|
||||||
|
<CloseIcon onClose={onClose} />
|
||||||
|
</View>
|
||||||
|
{symptom === 'temperature' &&
|
||||||
|
<Temperature
|
||||||
|
data={data}
|
||||||
|
save={(value, field) => this.onSaveTemperature(value, field)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{shouldTabGroup && symtomPage[symptom].selectTabGroups.map(group => {
|
||||||
|
return (
|
||||||
|
<Segment key={group.key}>
|
||||||
|
<AppText style={styles.title}>{group.title}</AppText>
|
||||||
|
<SelectTabGroup
|
||||||
|
activeButton={data[group.key]}
|
||||||
|
buttons={group.options}
|
||||||
|
onSelect={value => this.onSelectTab(group, value)}
|
||||||
|
/>
|
||||||
|
</Segment>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{shouldBoxGroup && symtomPage[symptom].selectBoxGroups.map(group => {
|
||||||
|
const isOtherSelected =
|
||||||
|
data['other'] !== null
|
||||||
|
&& data['other'] !== false
|
||||||
|
&& Object.keys(group.options).includes('other')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Segment key={group.key}>
|
||||||
|
<AppText style={styles.title}>{group.title}</AppText>
|
||||||
|
<SelectBoxGroup
|
||||||
|
labels={group.options}
|
||||||
|
onSelect={value => this.onSelectBox(value)}
|
||||||
|
optionsState={data}
|
||||||
|
/>
|
||||||
|
{isOtherSelected &&
|
||||||
|
<AppTextInput
|
||||||
|
multiline={true}
|
||||||
|
placeholder={sharedLabels.enter}
|
||||||
|
value={data.note}
|
||||||
|
onChangeText={value => this.onSelectBoxNote(value)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Segment>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{shouldShowExclude &&
|
||||||
|
<Segment>
|
||||||
|
<AppSwitch
|
||||||
|
onToggle={this.onExcludeToggle}
|
||||||
|
text={symtomPage[symptom].excludeText}
|
||||||
|
value={data.exclude}
|
||||||
|
/>
|
||||||
|
</Segment>
|
||||||
|
}
|
||||||
|
{shouldShowNote &&
|
||||||
|
<Segment>
|
||||||
|
<AppText>{symtomPage[symptom].note}</AppText>
|
||||||
|
<AppTextInput
|
||||||
|
multiline={true}
|
||||||
|
placeholder={sharedLabels.enter}
|
||||||
|
onChangeText={this.onEditNote}
|
||||||
|
value={noteText !== null ? noteText : ''}
|
||||||
|
testID='noteInput'
|
||||||
|
/>
|
||||||
|
</Segment>
|
||||||
|
}
|
||||||
|
<View style={styles.buttonsContainer}>
|
||||||
|
<Button iconName={iconName} isSmall onPress={this.onPressLearnMore}>
|
||||||
|
learn more
|
||||||
|
</Button>
|
||||||
|
<Button isSmall onPress={this.onRemove}>
|
||||||
|
remove
|
||||||
|
</Button>
|
||||||
|
<Button isCTA isSmall onPress={this.onSave}>save</Button>
|
||||||
|
</View>
|
||||||
|
{shouldShowInfo &&
|
||||||
|
<Segment last>
|
||||||
|
<AppText>{info[symptom].text}</AppText>
|
||||||
|
</Segment>
|
||||||
|
}
|
||||||
|
</ScrollView>
|
||||||
|
</AppModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
buttonsContainer: {
|
||||||
|
...Containers.rowContainer
|
||||||
|
},
|
||||||
|
headerContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
paddingVertical: Sizes.tiny,
|
||||||
|
},
|
||||||
|
modalContainer: {
|
||||||
|
flexGrow: 1,
|
||||||
|
padding: Sizes.small
|
||||||
|
},
|
||||||
|
modalWindow: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 10,
|
||||||
|
marginVertical: Sizes.huge * 2,
|
||||||
|
minHeight: '40%',
|
||||||
|
height: '70%',
|
||||||
|
position: 'absolute'
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: Sizes.subtitle
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return({
|
||||||
|
date: getDate(state),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null,
|
||||||
|
)(SymptomEditView)
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, TouchableOpacity, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppIcon from '../common/app-icon'
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { getDate, setDate } from '../../slices/date'
|
||||||
|
|
||||||
|
import {
|
||||||
|
nextDate,
|
||||||
|
prevDate,
|
||||||
|
isTomorrowInFuture,
|
||||||
|
isYesterdayInFuture
|
||||||
|
} from '../helpers/cycle-day'
|
||||||
|
import { Colors, Containers, Spacing, Typography } from '../../styles/redesign'
|
||||||
|
|
||||||
|
const SymptomPageTitle = ({
|
||||||
|
date,
|
||||||
|
reloadSymptomData,
|
||||||
|
setDate,
|
||||||
|
subtitle,
|
||||||
|
title
|
||||||
|
}) => {
|
||||||
|
const rightArrowColor = isTomorrowInFuture(date) ? Colors.grey : Colors.orange
|
||||||
|
const leftArrowColor = isYesterdayInFuture(date) ? Colors.grey : Colors.orange
|
||||||
|
const navigate = (isForward) => {
|
||||||
|
const nextDay = isForward ? nextDate(date) : prevDate(date)
|
||||||
|
reloadSymptomData(nextDay)
|
||||||
|
setDate(nextDay)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<TouchableOpacity onPress={() => navigate(false)}>
|
||||||
|
<AppIcon name='chevron-left' color={leftArrowColor}/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<AppText style={styles.title}>{title}</AppText>
|
||||||
|
{subtitle && <AppText style={styles.subtitle}>{subtitle}</AppText>}
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity onPress={() => navigate(true)}>
|
||||||
|
<AppIcon name='chevron-right' color={rightArrowColor}/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SymptomPageTitle.propTypes = {
|
||||||
|
date: PropTypes.string.isRequired,
|
||||||
|
reloadSymptomData: PropTypes.func.isRequired,
|
||||||
|
setDate: PropTypes.func.isRequired,
|
||||||
|
subtitle: PropTypes.string,
|
||||||
|
title: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
height: (Spacing.base * 4),
|
||||||
|
marginHorizontal: Spacing.base,
|
||||||
|
marginTop: Spacing.base,
|
||||||
|
...Containers.rowContainer
|
||||||
|
},
|
||||||
|
textContainer: {
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
...Typography.titleWithoutMargin
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return({
|
||||||
|
date: getDate(state),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return({
|
||||||
|
setDate: (date) => dispatch(setDate(date)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(SymptomPageTitle)
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { Switch } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { bleeding } from '../../../i18n/en/cycle-day'
|
|
||||||
import SelectTabGroup from '../select-tab-group'
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
|
|
||||||
import { getLabelsList } from '../../helpers/labels'
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
class Bleeding extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'bleeding'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = {
|
|
||||||
value: null,
|
|
||||||
exclude: false
|
|
||||||
}
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
this.state = { ...symptomData }
|
|
||||||
|
|
||||||
this.bleedingRadioProps = getLabelsList(bleeding.labels)
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
}
|
|
||||||
|
|
||||||
autoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const valuesToSave = { ...this.state }
|
|
||||||
const hasValueToSave = typeof this.state.value === 'number'
|
|
||||||
saveSymptom(this.symptom, date, hasValueToSave ? valuesToSave : null)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.autoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={this.symptom}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection
|
|
||||||
header={bleeding.heaviness.header}
|
|
||||||
explainer={bleeding.heaviness.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.bleedingRadioProps}
|
|
||||||
active={this.state.value}
|
|
||||||
onSelect={val => this.setState({ value: val })}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header={bleeding.exclude.header}
|
|
||||||
explainer={bleeding.exclude.explainer}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<Switch
|
|
||||||
onValueChange={(val) => {
|
|
||||||
this.setState({ exclude: val })
|
|
||||||
}}
|
|
||||||
value={this.state.exclude}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Bleeding
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { Switch } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { cervix as labels } from '../../../i18n/en/cycle-day'
|
|
||||||
import SelectTabGroup from '../select-tab-group'
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
|
|
||||||
import { getLabelsList } from '../../helpers/labels'
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
class Cervix extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'cervix'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = {}
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
this.state = { ...symptomData }
|
|
||||||
|
|
||||||
this.cervixOpeningRadioProps = getLabelsList(labels.opening.categories)
|
|
||||||
this.cervixFirmnessRadioProps = getLabelsList(labels.firmness.categories)
|
|
||||||
this.cervixPositionRadioProps = getLabelsList(labels.position.categories)
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
}
|
|
||||||
|
|
||||||
autoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const { opening, firmness, position, exclude } = this.state
|
|
||||||
const valuesToSave = {
|
|
||||||
opening,
|
|
||||||
firmness,
|
|
||||||
position,
|
|
||||||
exclude: Boolean(exclude)
|
|
||||||
}
|
|
||||||
const nothingEntered = ['opening', 'firmness', 'position'].every(
|
|
||||||
val => typeof this.state[val] !== 'number')
|
|
||||||
saveSymptom(this.symptom, date, nothingEntered ? null : valuesToSave)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.autoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
// TODO saving this info for notice when leaving incomplete data
|
|
||||||
// const mandatoryNotCompleted = typeof this.state.opening != 'number' || typeof this.state.firmness != 'number'
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={this.symptom}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection
|
|
||||||
header="Opening"
|
|
||||||
explainer={labels.opening.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.cervixOpeningRadioProps}
|
|
||||||
active={this.state.opening}
|
|
||||||
onSelect={val => this.setState({ opening: val })}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header="Firmness"
|
|
||||||
explainer={labels.firmness.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.cervixFirmnessRadioProps}
|
|
||||||
active={this.state.firmness}
|
|
||||||
onSelect={val => this.setState({ firmness: val })}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header="Position"
|
|
||||||
explainer={labels.position.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.cervixPositionRadioProps}
|
|
||||||
active={this.state.position}
|
|
||||||
onSelect={val => this.setState({ position: val })}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header="Exclude"
|
|
||||||
explainer="You can exclude this value if you don't want to use it for fertility detection"
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<Switch
|
|
||||||
onValueChange={(val) => {
|
|
||||||
this.setState({ exclude: val })
|
|
||||||
}}
|
|
||||||
value={this.state.exclude}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Cervix
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { intensity, desire } from '../../../i18n/en/cycle-day'
|
|
||||||
import SelectTabGroup from '../select-tab-group'
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
|
|
||||||
import { getLabelsList } from '../../helpers/labels'
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
class Desire extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'desire'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = { value: null }
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
this.state = { ...symptomData }
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
|
|
||||||
this.desireRadioProps = getLabelsList(intensity)
|
|
||||||
}
|
|
||||||
|
|
||||||
autoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const valuesToSave = { ...this.state }
|
|
||||||
const hasValueToSave = typeof this.state.value === 'number'
|
|
||||||
saveSymptom(this.symptom, date, hasValueToSave ? valuesToSave : null)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.autoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={this.symptom}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection
|
|
||||||
header={desire.header}
|
|
||||||
explainer={desire.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.desireRadioProps}
|
|
||||||
active={this.state.value}
|
|
||||||
onSelect={val => this.setState({ value: val })}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Desire
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import BleedingEditView from './bleeding'
|
|
||||||
import TemperatureEditView from './temperature'
|
|
||||||
import MucusEditView from './mucus'
|
|
||||||
import CervixEditView from './cervix'
|
|
||||||
import NoteEditView from './note'
|
|
||||||
import DesireEditView from './desire'
|
|
||||||
import SexEditView from './sex'
|
|
||||||
import PainEditView from './pain'
|
|
||||||
import MoodEditView from './mood'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
BleedingEditView,
|
|
||||||
TemperatureEditView,
|
|
||||||
MucusEditView,
|
|
||||||
CervixEditView,
|
|
||||||
NoteEditView,
|
|
||||||
DesireEditView,
|
|
||||||
SexEditView,
|
|
||||||
PainEditView,
|
|
||||||
MoodEditView
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { ScrollView, View, TouchableOpacity } from 'react-native'
|
|
||||||
import Icon from 'react-native-vector-icons/SimpleLineIcons'
|
|
||||||
import AppText from '../../common/app-text'
|
|
||||||
import labels from '../../../i18n/en/symptom-info.js'
|
|
||||||
import styles, {iconStyles} from '../../../styles/index'
|
|
||||||
|
|
||||||
export default function InfoSymptom({ close, symptom }) {
|
|
||||||
return (
|
|
||||||
<View style={styles.infoPopUpWrapper}>
|
|
||||||
<View style={styles.dimmed}></View>
|
|
||||||
<View style={styles.infoPopUp} testID="symptomInfoPopup">
|
|
||||||
<TouchableOpacity onPress={close} style={styles.infoSymptomClose}>
|
|
||||||
<Icon name='close' {...iconStyles.infoPopUpClose}/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<ScrollView style={styles.infoSymptomText}>
|
|
||||||
<AppText>{labels[symptom].text}</AppText>
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
InfoSymptom.propTypes = {
|
|
||||||
close: PropTypes.func.isRequired,
|
|
||||||
symptom: PropTypes.string.isRequired
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { TextInput } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { mood as labels } from '../../../i18n/en/cycle-day'
|
|
||||||
import SelectBoxGroup from '../select-box-group'
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
class Mood extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'mood'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = {}
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
this.state = { ...symptomData }
|
|
||||||
|
|
||||||
// We make sure other is always true when there is a note,
|
|
||||||
// e.g. when import is messed up.
|
|
||||||
if (this.state.note) this.state.other = true
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
}
|
|
||||||
|
|
||||||
autoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const valuesToSave = Object.assign({}, this.state)
|
|
||||||
if (!valuesToSave.other) {
|
|
||||||
valuesToSave.note = null
|
|
||||||
}
|
|
||||||
const nothingEntered = Object.values(this.state).every(val => !val)
|
|
||||||
|
|
||||||
saveSymptom(this.symptom, date, nothingEntered ? null : valuesToSave)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.autoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleState = (key) => {
|
|
||||||
const curr = this.state[key]
|
|
||||||
this.setState({[key]: !curr})
|
|
||||||
if (key === 'other' && !curr) {
|
|
||||||
this.setState({focusTextArea: true})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={this.symptom}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection
|
|
||||||
explainer={labels.explainer}
|
|
||||||
>
|
|
||||||
<SelectBoxGroup
|
|
||||||
labels={labels.categories}
|
|
||||||
onSelect={this.toggleState}
|
|
||||||
optionsState={this.state}
|
|
||||||
/>
|
|
||||||
{ this.state.other &&
|
|
||||||
<TextInput
|
|
||||||
autoFocus={this.state.focusTextArea}
|
|
||||||
multiline={true}
|
|
||||||
placeholder="Enter"
|
|
||||||
value={this.state.note}
|
|
||||||
onChangeText={(val) => {
|
|
||||||
this.setState({note: val})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</SymptomSection>
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Mood
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { Switch } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { mucus as labels } from '../../../i18n/en/cycle-day'
|
|
||||||
import computeNfpValue from '../../../lib/nfp-mucus'
|
|
||||||
import SelectTabGroup from '../select-tab-group'
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
|
|
||||||
import { getLabelsList } from '../../helpers/labels'
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
class Mucus extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'mucus'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = {}
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
this.state = { ...symptomData }
|
|
||||||
|
|
||||||
this.mucusFeeling = getLabelsList(labels.feeling.categories)
|
|
||||||
this.mucusTexture = getLabelsList(labels.texture.categories)
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldAutoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const nothingEntered = ['feeling', 'texture'].every(
|
|
||||||
val => typeof this.state[val] !== 'number'
|
|
||||||
)
|
|
||||||
const { feeling, texture, exclude} = this.state
|
|
||||||
const valuesToSave = {
|
|
||||||
feeling,
|
|
||||||
texture,
|
|
||||||
value: computeNfpValue(feeling, texture),
|
|
||||||
exclude: Boolean(exclude)
|
|
||||||
}
|
|
||||||
saveSymptom(this.symptom, date, nothingEntered ? null : valuesToSave)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.shouldAutoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
// TODO leaving this info for notice when leaving incomplete data
|
|
||||||
// const mandatoryNotCompletedYet = typeof this.state.feeling != 'number' || typeof this.state.texture != 'number'
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={this.symptom}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection
|
|
||||||
header='Feeling'
|
|
||||||
explainer={labels.feeling.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.mucusFeeling}
|
|
||||||
onSelect={val => this.setState({ feeling: val })}
|
|
||||||
active={this.state.feeling}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header='Texture'
|
|
||||||
explainer={labels.texture.explainer}
|
|
||||||
>
|
|
||||||
<SelectTabGroup
|
|
||||||
buttons={this.mucusTexture}
|
|
||||||
onSelect={val => this.setState({ texture: val })}
|
|
||||||
active={this.state.texture}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header="Exclude"
|
|
||||||
explainer={labels.excludeExplainer}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<Switch
|
|
||||||
onValueChange={(val) => {
|
|
||||||
this.setState({ exclude: val })
|
|
||||||
}}
|
|
||||||
value={this.state.exclude}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Mucus
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { TextInput } from 'react-native'
|
|
||||||
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import { noteExplainer } from '../../../i18n/en/cycle-day'
|
|
||||||
import { shared as sharedLabels } from '../../../i18n/en/labels'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
class Note extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'note'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = { value: '' }
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
this.state = { ...symptomData }
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
}
|
|
||||||
|
|
||||||
autoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const valuesToSave = { ...this.state }
|
|
||||||
saveSymptom(this.symptom, date, this.state.value ? valuesToSave : null)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.autoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={this.symptom}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection explainer={noteExplainer} >
|
|
||||||
<TextInput
|
|
||||||
autoFocus={true}
|
|
||||||
multiline={true}
|
|
||||||
placeholder={sharedLabels.enter}
|
|
||||||
onChangeText={(val) => { this.setState({ value: val })}}
|
|
||||||
value={this.state.value}
|
|
||||||
testID='noteInput'
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Note
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { TextInput } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { pain as labels } from '../../../i18n/en/cycle-day'
|
|
||||||
import { shared as sharedLabels } from '../../../i18n/en/labels'
|
|
||||||
import SelectBoxGroup from '../select-box-group'
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
class Pain extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'pain'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = {}
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
this.state = { ...symptomData }
|
|
||||||
|
|
||||||
// We make sure other is always true when there is a note,
|
|
||||||
// e.g. when import is messed up.
|
|
||||||
if (this.state.note) this.state.other = true
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
}
|
|
||||||
|
|
||||||
autoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const valuesToSave = Object.assign({}, this.state)
|
|
||||||
if (!valuesToSave.other) {
|
|
||||||
valuesToSave.note = null
|
|
||||||
}
|
|
||||||
const nothingEntered = Object.values(this.state).every(val => !val)
|
|
||||||
|
|
||||||
saveSymptom(this.symptom, date, nothingEntered ? null : valuesToSave)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.autoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleState = (key) => {
|
|
||||||
const curr = this.state[key]
|
|
||||||
this.setState({[key]: !curr})
|
|
||||||
if (key === 'other' && !curr) {
|
|
||||||
this.setState({focusTextArea: true})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={this.symptom}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection
|
|
||||||
explainer={labels.explainer}
|
|
||||||
>
|
|
||||||
<SelectBoxGroup
|
|
||||||
labels={labels.categories}
|
|
||||||
onSelect={this.toggleState}
|
|
||||||
optionsState={this.state}
|
|
||||||
/>
|
|
||||||
{ this.state.other &&
|
|
||||||
<TextInput
|
|
||||||
autoFocus={this.state.focusTextArea}
|
|
||||||
multiline={true}
|
|
||||||
placeholder={sharedLabels.enter}
|
|
||||||
value={this.state.note}
|
|
||||||
onChangeText={(val) => {
|
|
||||||
this.setState({note: val})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</SymptomSection>
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Pain
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { TextInput } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { sex as sexLabels, contraceptives as contraceptivesLabels } from '../../../i18n/en/cycle-day'
|
|
||||||
import { shared as sharedLabels } from '../../../i18n/en/labels'
|
|
||||||
import SelectBoxGroup from '../select-box-group'
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
class Sex extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'sex'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = {}
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
this.state = { ...symptomData }
|
|
||||||
|
|
||||||
// We make sure other is always true when there is a note,
|
|
||||||
// e.g. when import is messed up.
|
|
||||||
if (this.state.note) this.state.other = true
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
}
|
|
||||||
|
|
||||||
autoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const valuesToSave = Object.assign({}, this.state)
|
|
||||||
if (!valuesToSave.other) {
|
|
||||||
valuesToSave.note = null
|
|
||||||
}
|
|
||||||
const nothingEntered = Object.values(this.state).every(val => !val)
|
|
||||||
|
|
||||||
saveSymptom(this.symptom, date, nothingEntered ? null : valuesToSave)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.autoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleState = (key) => {
|
|
||||||
const curr = this.state[key]
|
|
||||||
this.setState({[key]: !curr})
|
|
||||||
if (key === 'other'){
|
|
||||||
if (curr){
|
|
||||||
this.setState({note: ""})
|
|
||||||
} else {
|
|
||||||
this.setState({focusTextArea: true})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={this.symptom}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection
|
|
||||||
header={sexLabels.header}
|
|
||||||
explainer={sexLabels.explainer}
|
|
||||||
>
|
|
||||||
<SelectBoxGroup
|
|
||||||
labels={sexLabels.categories}
|
|
||||||
onSelect={this.toggleState}
|
|
||||||
optionsState={this.state}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header={contraceptivesLabels.header}
|
|
||||||
explainer={contraceptivesLabels.explainer}
|
|
||||||
>
|
|
||||||
<SelectBoxGroup
|
|
||||||
labels={contraceptivesLabels.categories}
|
|
||||||
onSelect={this.toggleState}
|
|
||||||
optionsState={this.state}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
|
|
||||||
{this.state.other &&
|
|
||||||
<TextInput
|
|
||||||
autoFocus={this.state.focusTextArea}
|
|
||||||
multiline={true}
|
|
||||||
placeholder={sharedLabels.enter}
|
|
||||||
value={this.state.note}
|
|
||||||
onChangeText={(val) => {
|
|
||||||
this.setState({ note: val })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Sex
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { TouchableOpacity } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import Icon from 'react-native-vector-icons/Entypo'
|
|
||||||
|
|
||||||
import InfoPopUp from './info-symptom'
|
|
||||||
|
|
||||||
import styles, { iconStyles } from '../../../styles'
|
|
||||||
|
|
||||||
export default class SymptomInfo extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
symptom: PropTypes.string
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
this.state = { showInfo: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
showInfo = () => this.setState({ showInfo: true })
|
|
||||||
|
|
||||||
hideInfo = () => this.setState({ showInfo: false })
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={this.showInfo}
|
|
||||||
style={styles.infoButtonSymptomView}
|
|
||||||
testID="symptomInfoButton"
|
|
||||||
>
|
|
||||||
<Icon name="info-with-circle" style={iconStyles.info} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
{ this.state.showInfo &&
|
|
||||||
<InfoPopUp symptom={this.props.symptom} close={this.hideInfo} />
|
|
||||||
}
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { View } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import AppText from '../../common/app-text'
|
|
||||||
import styles from '../../../styles'
|
|
||||||
|
|
||||||
export default class SymptomSection extends Component {
|
|
||||||
render() {
|
|
||||||
const p = this.props
|
|
||||||
let placeHeadingInline
|
|
||||||
if (!p.explainer && p.inline) {
|
|
||||||
placeHeadingInline = {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: "center"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<View style={[placeHeadingInline, styles.symptomSection]}>
|
|
||||||
{ p.header &&
|
|
||||||
<AppText style={styles.symptomViewHeading}>{p.header}</AppText>
|
|
||||||
}
|
|
||||||
<View
|
|
||||||
flexDirection={p.inline ? 'row' : null}
|
|
||||||
flex={1}
|
|
||||||
alignItems={p.inline ? 'center' : null}
|
|
||||||
>
|
|
||||||
{ p.explainer && (
|
|
||||||
<View flex={1}>
|
|
||||||
<AppText>{p.explainer}</AppText>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
{p.children}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SymptomSection.propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
explainer: PropTypes.string,
|
|
||||||
header: PropTypes.string,
|
|
||||||
inline: PropTypes.bool
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { ScrollView, View, Alert } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import { getDate } from '../../../slices/date'
|
|
||||||
import { goBack } from '../../../slices/navigation'
|
|
||||||
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
import formatDate from '../../helpers/format-date'
|
|
||||||
|
|
||||||
import Header from '../../header'
|
|
||||||
import SymptomInfo from './symptom-info'
|
|
||||||
|
|
||||||
import { headerTitles } from '../../../i18n/en/labels'
|
|
||||||
import { sharedDialogs } from '../../../i18n/en/cycle-day'
|
|
||||||
|
|
||||||
import styles from '../../../styles'
|
|
||||||
|
|
||||||
const checkIfHasValues = data => {
|
|
||||||
const isMeaningfulValue = value => value || value === 0
|
|
||||||
return Object.values(data).some(isMeaningfulValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
class SymptomView extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
symptom: PropTypes.string.isRequired,
|
|
||||||
values: PropTypes.object,
|
|
||||||
date: PropTypes.string,
|
|
||||||
children: PropTypes.node,
|
|
||||||
goBack: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super()
|
|
||||||
this.state = {
|
|
||||||
shouldShowDelete: checkIfHasValues(props.values)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
const shouldShowDelete = checkIfHasValues(this.props.values)
|
|
||||||
if (shouldShowDelete !== this.state.shouldShowDelete) {
|
|
||||||
this.setState({ shouldShowDelete })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteSymptomEntry() {
|
|
||||||
const { symptom, date } = this.props
|
|
||||||
saveSymptom(symptom, date, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeleteConfirmation = () => {
|
|
||||||
this.deleteSymptomEntry()
|
|
||||||
this.props.goBack()
|
|
||||||
}
|
|
||||||
|
|
||||||
showConfirmationAlert = () => {
|
|
||||||
|
|
||||||
const cancelButton = {
|
|
||||||
text: sharedDialogs.cancel,
|
|
||||||
style: 'cancel'
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmationButton = {
|
|
||||||
text: sharedDialogs.reallyDeleteData,
|
|
||||||
onPress: this.onDeleteConfirmation
|
|
||||||
}
|
|
||||||
|
|
||||||
return Alert.alert(
|
|
||||||
sharedDialogs.areYouSureTitle,
|
|
||||||
sharedDialogs.areYouSureToDelete,
|
|
||||||
[cancelButton, confirmationButton]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { symptom, date, goBack } = this.props
|
|
||||||
const { shouldShowDelete } = this.state
|
|
||||||
const handleDelete = shouldShowDelete ? this.showConfirmationAlert : null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={{flex: 1}}>
|
|
||||||
<Header
|
|
||||||
title={headerTitles[symptom]}
|
|
||||||
subtitle={formatDate(date)}
|
|
||||||
handleBack={goBack}
|
|
||||||
handleDelete={handleDelete}
|
|
||||||
/>
|
|
||||||
<View flex={1}>
|
|
||||||
<ScrollView style={styles.page}>
|
|
||||||
{this.props.children}
|
|
||||||
</ScrollView>
|
|
||||||
<SymptomInfo symptom={symptom} />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
|
||||||
return({
|
|
||||||
date: getDate(state),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => {
|
|
||||||
return({
|
|
||||||
goBack: () => dispatch(goBack()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
)(SymptomView)
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { View } from 'react-native'
|
|
||||||
import AppText from '../../common/app-text'
|
|
||||||
import AppTextInput from '../../common/app-text-input'
|
|
||||||
|
|
||||||
import { temperature as labels } from '../../../i18n/en/cycle-day'
|
|
||||||
|
|
||||||
import styles from '../../../styles'
|
|
||||||
|
|
||||||
import { getPreviousTemperature } from '../../../db'
|
|
||||||
import { scaleObservable } from '../../../local-storage'
|
|
||||||
import { TEMP_MAX, TEMP_MIN } from '../../../config'
|
|
||||||
|
|
||||||
export default class TemperatureInput extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
temperature: PropTypes.string,
|
|
||||||
handleTemperatureChange: PropTypes.func,
|
|
||||||
date: PropTypes.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
let shouldShowSuggestion = false
|
|
||||||
let suggestedTemperature = null
|
|
||||||
|
|
||||||
if (!props.temperature) {
|
|
||||||
const prevTemp = getPreviousTemperature(props.date)
|
|
||||||
if (prevTemp) {
|
|
||||||
shouldShowSuggestion = true
|
|
||||||
suggestedTemperature = prevTemp.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
temperature: props.temperature,
|
|
||||||
shouldShowSuggestion,
|
|
||||||
suggestedTemperature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setTemperature = (temperature) => {
|
|
||||||
this.setState({
|
|
||||||
shouldShowSuggestion: false,
|
|
||||||
temperature
|
|
||||||
})
|
|
||||||
this.props.handleTemperatureChange(Number(temperature))
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const {
|
|
||||||
shouldShowSuggestion,
|
|
||||||
suggestedTemperature,
|
|
||||||
temperature
|
|
||||||
} = this.state
|
|
||||||
const inputStyle = [
|
|
||||||
styles.temperatureTextInput,
|
|
||||||
shouldShowSuggestion ? styles.temperatureTextInputSuggestion : null
|
|
||||||
]
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<View style={styles.framedSegmentInlineChildren}>
|
|
||||||
<AppTextInput
|
|
||||||
style={inputStyle}
|
|
||||||
autoFocus={true}
|
|
||||||
value={shouldShowSuggestion ? suggestedTemperature : temperature}
|
|
||||||
onChangeText={this.setTemperature}
|
|
||||||
keyboardType='numeric'
|
|
||||||
maxLength={5}
|
|
||||||
testID='temperatureInput'
|
|
||||||
/>
|
|
||||||
<AppText style={{ marginLeft: 5 }}>°C</AppText>
|
|
||||||
</View>
|
|
||||||
<OutOfRangeWarning temperature={this.props.temperature} />
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const OutOfRangeWarning = ({ temperature }) => {
|
|
||||||
if (temperature === '') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = Number(temperature)
|
|
||||||
const range = { min: TEMP_MIN, max: TEMP_MAX }
|
|
||||||
const scale = scaleObservable.value
|
|
||||||
|
|
||||||
let warningMsg
|
|
||||||
|
|
||||||
if (value < range.min || value > range.max) {
|
|
||||||
warningMsg = labels.outOfAbsoluteRangeWarning
|
|
||||||
} else if (value < scale.min || value > scale.max) {
|
|
||||||
warningMsg = labels.outOfRangeWarning
|
|
||||||
} else {
|
|
||||||
warningMsg = null
|
|
||||||
}
|
|
||||||
|
|
||||||
return <AppText style={styles.hint}>{warningMsg}</AppText>
|
|
||||||
}
|
|
||||||
|
|
||||||
OutOfRangeWarning.propTypes = {
|
|
||||||
temperature: PropTypes.string.isRequired
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { Switch } from 'react-native'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { LocalTime, ChronoUnit } from 'js-joda'
|
|
||||||
import { temperature as labels } from '../../../i18n/en/cycle-day'
|
|
||||||
import { shared as sharedLabels } from '../../../i18n/en/labels'
|
|
||||||
|
|
||||||
import AppTextInput from '../../common/app-text-input'
|
|
||||||
import SymptomSection from './symptom-section'
|
|
||||||
import SymptomView from './symptom-view'
|
|
||||||
import TimeInput from './time-input'
|
|
||||||
import TemperatureInput from './temperature-input'
|
|
||||||
|
|
||||||
import { saveSymptom } from '../../../db'
|
|
||||||
|
|
||||||
const minutes = ChronoUnit.MINUTES
|
|
||||||
|
|
||||||
class Temperature extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
cycleDay: PropTypes.object,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
const symptom = 'temperature'
|
|
||||||
const { cycleDay } = props
|
|
||||||
|
|
||||||
const defaultSymptomData = {
|
|
||||||
time: LocalTime.now().truncatedTo(minutes).toString(),
|
|
||||||
temperature: null,
|
|
||||||
note: '',
|
|
||||||
exclude: false
|
|
||||||
}
|
|
||||||
|
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : defaultSymptomData
|
|
||||||
|
|
||||||
const { value, ...restSymptomData } = symptomData
|
|
||||||
this.state = { temperature: value, ...restSymptomData }
|
|
||||||
|
|
||||||
this.symptom = symptom
|
|
||||||
}
|
|
||||||
|
|
||||||
isDeleteIconActive() {
|
|
||||||
return ['temperature', 'note', 'exclude'].some(key => {
|
|
||||||
// the time is always and the suggested temp sometimes prefilled, so they're not relevant for setting
|
|
||||||
// the delete button active.
|
|
||||||
return this.state[key] || this.state[key] === 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
autoSave = () => {
|
|
||||||
const { date } = this.props
|
|
||||||
const { temperature, exclude, time, note } = this.state
|
|
||||||
|
|
||||||
const valuesToSave = {
|
|
||||||
value: temperature,
|
|
||||||
exclude,
|
|
||||||
time,
|
|
||||||
note
|
|
||||||
}
|
|
||||||
|
|
||||||
saveSymptom(this.symptom, date, temperature ? valuesToSave : null)
|
|
||||||
}
|
|
||||||
|
|
||||||
setTime = (time) => {
|
|
||||||
this.setState({ time })
|
|
||||||
}
|
|
||||||
|
|
||||||
setTemperature = (temperature) => {
|
|
||||||
this.setState({ temperature })
|
|
||||||
}
|
|
||||||
|
|
||||||
setNote = (note) => {
|
|
||||||
this.setState({ note })
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.autoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { temperature } = this.state
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SymptomView
|
|
||||||
symptom={'temperature'}
|
|
||||||
values={this.state}
|
|
||||||
date={this.props.date}
|
|
||||||
>
|
|
||||||
<SymptomSection
|
|
||||||
header={labels.temperature.header}
|
|
||||||
explainer={labels.temperature.explainer}
|
|
||||||
>
|
|
||||||
<TemperatureInput
|
|
||||||
temperature={temperature ? temperature.toFixed(2) : ''}
|
|
||||||
date={this.props.date}
|
|
||||||
handleTemperatureChange={this.setTemperature}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection header={labels.time}>
|
|
||||||
<TimeInput
|
|
||||||
time={this.state.time}
|
|
||||||
handleTimeChange={this.setTime}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header={labels.note.header}
|
|
||||||
explainer={labels.note.explainer}
|
|
||||||
>
|
|
||||||
<AppTextInput
|
|
||||||
multiline={true}
|
|
||||||
placeholder={sharedLabels.enter}
|
|
||||||
value={this.state.note}
|
|
||||||
onChangeText={this.setNote}
|
|
||||||
testID='noteInput'
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
<SymptomSection
|
|
||||||
header={labels.exclude.header}
|
|
||||||
explainer={labels.exclude.explainer}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<Switch
|
|
||||||
onValueChange={(val) => {
|
|
||||||
this.setState({ exclude: val })
|
|
||||||
}}
|
|
||||||
value={this.state.exclude}
|
|
||||||
/>
|
|
||||||
</SymptomSection>
|
|
||||||
</SymptomView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Temperature
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { Keyboard } from 'react-native'
|
|
||||||
|
|
||||||
import AppTextInput from '../../common/app-text-input'
|
|
||||||
import styles from '../../../styles'
|
|
||||||
|
|
||||||
import DateTimePicker from 'react-native-modal-datetime-picker-nevo'
|
|
||||||
import moment from 'moment'
|
|
||||||
|
|
||||||
export default class TimeInput extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
time: PropTypes.string,
|
|
||||||
handleTimeChange: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isTimePickerVisible: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showTimePicker = () => {
|
|
||||||
Keyboard.dismiss()
|
|
||||||
this.setState({ isTimePickerVisible: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
hideTimePicker = () => {
|
|
||||||
this.setState({ isTimePickerVisible: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleConfirm = (jsDate) => {
|
|
||||||
// DateTimePicker also when in mode="time" returns full date (with time)
|
|
||||||
const time = moment(jsDate).format('HH:mm')
|
|
||||||
this.props.handleTimeChange(time)
|
|
||||||
this.setState({
|
|
||||||
isTimePickerVisible: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<AppTextInput
|
|
||||||
style={styles.temperatureTextInput}
|
|
||||||
onFocus={this.showTimePicker}
|
|
||||||
value={this.props.time}
|
|
||||||
testID='timeInput'
|
|
||||||
/>
|
|
||||||
<DateTimePicker
|
|
||||||
mode="time"
|
|
||||||
isVisible={this.state.isTimePickerVisible}
|
|
||||||
onConfirm={this.handleConfirm}
|
|
||||||
onCancel={this.hideTimePicker}
|
|
||||||
/>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Keyboard } from 'react-native'
|
||||||
|
import DateTimePicker from 'react-native-modal-datetime-picker-nevo'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
import AppTextInput from '../common/app-text-input'
|
||||||
|
import Segment from '../common/segment'
|
||||||
|
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { getDate } from '../../slices/date'
|
||||||
|
import { isTemperatureOutOfRange, isPreviousTemperature } from '../helpers/cycle-day'
|
||||||
|
|
||||||
|
import { temperature as labels } from '../../i18n/en/cycle-day'
|
||||||
|
|
||||||
|
import { Colors, Containers, Sizes, Spacing } from '../../styles/redesign'
|
||||||
|
|
||||||
|
const formatTemperature = value => value === null
|
||||||
|
? value
|
||||||
|
: Number.parseFloat(value).toFixed(2)
|
||||||
|
|
||||||
|
class Temperature extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
data: PropTypes.object,
|
||||||
|
date: PropTypes.string.isRequired,
|
||||||
|
save: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
const { data, date } = this.props
|
||||||
|
const { value } = data
|
||||||
|
const { shouldShowSuggestion, suggestedTemperature } =
|
||||||
|
isPreviousTemperature(date)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isTimePickerVisible: false,
|
||||||
|
shouldShowSuggestion,
|
||||||
|
suggestedTemperature: formatTemperature(suggestedTemperature),
|
||||||
|
value: formatTemperature(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancelTimePicker = () => {
|
||||||
|
this.setState({ isTimePickerVisible: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeTemperature = (value) => {
|
||||||
|
this.setState({ value, shouldShowSuggestion: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
onShowTimePicker = () => {
|
||||||
|
Keyboard.dismiss()
|
||||||
|
this.setState({ isTimePickerVisible: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
setTemperature = () => {
|
||||||
|
const { value } = this.state
|
||||||
|
this.props.save(value, 'value')
|
||||||
|
}
|
||||||
|
|
||||||
|
setTime = (jsDate) => {
|
||||||
|
const time = moment(jsDate).format('HH:mm')
|
||||||
|
const isTimePickerVisible = false
|
||||||
|
|
||||||
|
this.props.save(time, 'time')
|
||||||
|
this.setState({ isTimePickerVisible })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { shouldShowSuggestion, suggestedTemperature, value } = this.state
|
||||||
|
const { time } = this.props.data
|
||||||
|
|
||||||
|
const inputStyle = (shouldShowSuggestion && value === null)
|
||||||
|
? { color: Colors.grey }
|
||||||
|
: {color: Colors.greyDark}
|
||||||
|
const outOfRangeWarning = isTemperatureOutOfRange(value)
|
||||||
|
let temperatureToShow = null
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
temperatureToShow = value
|
||||||
|
} else if (shouldShowSuggestion) {
|
||||||
|
temperatureToShow = suggestedTemperature
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Segment>
|
||||||
|
<AppText style={styles.title}>{labels.temperature.explainer}</AppText>
|
||||||
|
<View style={styles.container}>
|
||||||
|
<AppTextInput
|
||||||
|
value={temperatureToShow === null ? '' : temperatureToShow}
|
||||||
|
onChangeText={this.onChangeTemperature}
|
||||||
|
onEndEditing={this.setTemperature}
|
||||||
|
keyboardType="numeric"
|
||||||
|
maxLength={5}
|
||||||
|
style={inputStyle}
|
||||||
|
testID="temperatureInput"
|
||||||
|
underlineColorAndroid="transparent"
|
||||||
|
/>
|
||||||
|
<AppText>°C</AppText>
|
||||||
|
</View>
|
||||||
|
{ outOfRangeWarning !== null &&
|
||||||
|
<View style={styles.hintContainer}>
|
||||||
|
<AppText style={styles.hint}>{outOfRangeWarning}</AppText>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
</Segment>
|
||||||
|
<Segment>
|
||||||
|
<AppText style={styles.title}>{labels.time}</AppText>
|
||||||
|
<AppTextInput
|
||||||
|
onFocus={this.onShowTimePicker}
|
||||||
|
testID='timeInput'
|
||||||
|
value={time}
|
||||||
|
/>
|
||||||
|
<DateTimePicker
|
||||||
|
isVisible={this.state.isTimePickerVisible}
|
||||||
|
mode="time"
|
||||||
|
onConfirm={this.setTime}
|
||||||
|
onCancel={this.onCancelTimePicker}
|
||||||
|
/>
|
||||||
|
</Segment>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.rowContainer
|
||||||
|
},
|
||||||
|
hint: {
|
||||||
|
fontStyle: 'italic',
|
||||||
|
fontSize: Sizes.small
|
||||||
|
},
|
||||||
|
hintContainer: {
|
||||||
|
marginVertical: Spacing.tiny
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: Sizes.subtitle
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return({
|
||||||
|
date: getDate(state),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null,
|
||||||
|
)(Temperature)
|
||||||
@@ -0,0 +1,437 @@
|
|||||||
|
import { ChronoUnit, LocalDate, LocalTime } from 'js-joda'
|
||||||
|
|
||||||
|
import { getPreviousTemperature, saveSymptom } from '../../db'
|
||||||
|
import { scaleObservable } from '../../local-storage'
|
||||||
|
|
||||||
|
import * as labels from '../../i18n/en/cycle-day'
|
||||||
|
import { getLabelsList } from './labels'
|
||||||
|
import { TEMP_MAX, TEMP_MIN } from '../../config'
|
||||||
|
|
||||||
|
const bleedingLabels = labels.bleeding.labels
|
||||||
|
const cervixLabels = labels.cervix
|
||||||
|
const contraceptiveLabels = labels.contraceptives.categories
|
||||||
|
const intensityLabels = labels.intensity
|
||||||
|
const moodLabels = labels.mood.categories
|
||||||
|
const mucusLabels = labels.mucus
|
||||||
|
const noteDescription = labels.noteExplainer
|
||||||
|
const painLabels = labels.pain.categories
|
||||||
|
const sexLabels = labels.sex.categories
|
||||||
|
const temperatureLabels = labels.temperature
|
||||||
|
|
||||||
|
const minutes = ChronoUnit.MINUTES
|
||||||
|
|
||||||
|
const isNumber = (value) => typeof value === 'number'
|
||||||
|
export const shouldShow = (value) => value !== null ? true : false
|
||||||
|
|
||||||
|
export const isPreviousTemperature = (temperature) => {
|
||||||
|
const previousTemperature = getPreviousTemperature(temperature)
|
||||||
|
const shouldShowSuggestion = previousTemperature ? true : false
|
||||||
|
const suggestedTemperature = previousTemperature ?
|
||||||
|
previousTemperature.toString() : null
|
||||||
|
|
||||||
|
return { shouldShowSuggestion, suggestedTemperature }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isTemperatureOutOfRange = (temperature) => {
|
||||||
|
if (temperature === '') return null
|
||||||
|
|
||||||
|
const value = Number(temperature)
|
||||||
|
const range = { min: TEMP_MIN, max: TEMP_MAX }
|
||||||
|
const scale = scaleObservable.value
|
||||||
|
|
||||||
|
let warningMsg = null
|
||||||
|
|
||||||
|
if (value < range.min || value > range.max) {
|
||||||
|
warningMsg = labels.temperature.outOfAbsoluteRangeWarning
|
||||||
|
} else if (value < scale.min || value > scale.max) {
|
||||||
|
warningMsg = labels.temperature.outOfRangeWarning
|
||||||
|
}
|
||||||
|
|
||||||
|
return warningMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
export const blank = {
|
||||||
|
bleeding: {
|
||||||
|
exclude: false,
|
||||||
|
value: null
|
||||||
|
},
|
||||||
|
cervix: {
|
||||||
|
exclude: false,
|
||||||
|
firmness: null,
|
||||||
|
opening: null,
|
||||||
|
position: null,
|
||||||
|
},
|
||||||
|
desire: {
|
||||||
|
value: null
|
||||||
|
},
|
||||||
|
mood:{
|
||||||
|
happy: null,
|
||||||
|
sad: null,
|
||||||
|
stressed: null,
|
||||||
|
balanced: null,
|
||||||
|
fine: null,
|
||||||
|
anxious: null,
|
||||||
|
energetic: null,
|
||||||
|
fatigue: null,
|
||||||
|
angry: null,
|
||||||
|
other: null,
|
||||||
|
note: null
|
||||||
|
},
|
||||||
|
mucus: {
|
||||||
|
exclude: false,
|
||||||
|
feeling: null,
|
||||||
|
texture: null,
|
||||||
|
value: null
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
value: null
|
||||||
|
},
|
||||||
|
pain: {
|
||||||
|
cramps: null,
|
||||||
|
ovulationPain: null,
|
||||||
|
headache: null,
|
||||||
|
backache: null,
|
||||||
|
nausea: null,
|
||||||
|
tenderBreasts: null,
|
||||||
|
migraine: null,
|
||||||
|
other: null,
|
||||||
|
note: null
|
||||||
|
},
|
||||||
|
sex: {
|
||||||
|
solo: null,
|
||||||
|
partner: null,
|
||||||
|
condom: null,
|
||||||
|
pill: null,
|
||||||
|
iud: null,
|
||||||
|
patch: null,
|
||||||
|
ring: null,
|
||||||
|
implant: null,
|
||||||
|
diaphragm: null,
|
||||||
|
none: null,
|
||||||
|
other: null,
|
||||||
|
note: null
|
||||||
|
},
|
||||||
|
temperature: {
|
||||||
|
exclude: false,
|
||||||
|
note: null,
|
||||||
|
time: LocalTime.now().truncatedTo(minutes).toString(),
|
||||||
|
value: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const symtomPage = {
|
||||||
|
bleeding: {
|
||||||
|
excludeText: labels.bleeding.exclude.explainer,
|
||||||
|
note: null,
|
||||||
|
selectBoxGroups: null,
|
||||||
|
selectTabGroups: [{
|
||||||
|
key: 'value',
|
||||||
|
options: getLabelsList(bleedingLabels),
|
||||||
|
title: labels.bleeding.heaviness.explainer,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
cervix: {
|
||||||
|
excludeText: cervixLabels.excludeExplainer,
|
||||||
|
note: null,
|
||||||
|
selectBoxGroups: null,
|
||||||
|
selectTabGroups: [
|
||||||
|
{
|
||||||
|
key: 'opening',
|
||||||
|
options: getLabelsList(cervixLabels.opening.categories),
|
||||||
|
title: cervixLabels.opening.explainer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'firmness',
|
||||||
|
options: getLabelsList(cervixLabels.firmness.categories),
|
||||||
|
title: cervixLabels.firmness.explainer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'position',
|
||||||
|
options: getLabelsList(cervixLabels.position.categories),
|
||||||
|
title: cervixLabels.position.explainer,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
desire: {
|
||||||
|
excludeText: null,
|
||||||
|
note: null,
|
||||||
|
selectBoxGroups: null,
|
||||||
|
selectTabGroups: [{
|
||||||
|
key: 'value',
|
||||||
|
options: getLabelsList(intensityLabels),
|
||||||
|
title: labels.desire.explainer
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
mucus: {
|
||||||
|
excludeText: mucusLabels.excludeExplainer,
|
||||||
|
note: null,
|
||||||
|
selectBoxGroups: null,
|
||||||
|
selectTabGroups: [
|
||||||
|
{
|
||||||
|
key: 'feeling',
|
||||||
|
options: getLabelsList(mucusLabels.feeling.categories),
|
||||||
|
title: mucusLabels.feeling.explainer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'texture',
|
||||||
|
options: getLabelsList(mucusLabels.texture.categories),
|
||||||
|
title: mucusLabels.texture.explainer,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
mood: {
|
||||||
|
excludeText: null,
|
||||||
|
note: null,
|
||||||
|
selectBoxGroups: [{
|
||||||
|
key: 'mood',
|
||||||
|
options: moodLabels,
|
||||||
|
title: labels.mood.explainer
|
||||||
|
}],
|
||||||
|
selectTabGroups: null
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
excludeText: null,
|
||||||
|
note: noteDescription,
|
||||||
|
selectBoxGroups: null,
|
||||||
|
selectTabGroups: null
|
||||||
|
},
|
||||||
|
pain: {
|
||||||
|
excludeText: null,
|
||||||
|
note: null,
|
||||||
|
selectBoxGroups: [{
|
||||||
|
key: 'pain',
|
||||||
|
options: painLabels,
|
||||||
|
title: labels.pain.explainer
|
||||||
|
}],
|
||||||
|
selectTabGroups: null
|
||||||
|
},
|
||||||
|
sex: {
|
||||||
|
excludeText: null,
|
||||||
|
note: null,
|
||||||
|
selectBoxGroups: [
|
||||||
|
{
|
||||||
|
key: 'sex',
|
||||||
|
options: sexLabels,
|
||||||
|
title: labels.sex.explainer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'contraceptives',
|
||||||
|
options: contraceptiveLabels,
|
||||||
|
title: labels.contraceptives.explainer,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
selectTabGroups: null
|
||||||
|
},
|
||||||
|
temperature: {
|
||||||
|
excludeText: temperatureLabels.exclude.explainer,
|
||||||
|
note: temperatureLabels.note.explainer,
|
||||||
|
selectBoxGroups: null,
|
||||||
|
selectTabGroups: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const save = {
|
||||||
|
bleeding: (data, date, shouldDeleteData) => {
|
||||||
|
const { exclude, value } = data
|
||||||
|
const isDataEntered = isNumber(value)
|
||||||
|
const valuesToSave = shouldDeleteData || !isDataEntered
|
||||||
|
? null : { value, exclude }
|
||||||
|
|
||||||
|
saveSymptom('bleeding', date, valuesToSave)
|
||||||
|
},
|
||||||
|
cervix: (data, date, shouldDeleteData) => {
|
||||||
|
const { opening, firmness, position, exclude } = data
|
||||||
|
const isDataEntered = ['opening', 'firmness', 'position'].some(
|
||||||
|
value => isNumber(data[value]))
|
||||||
|
const valuesToSave = shouldDeleteData || !isDataEntered
|
||||||
|
? null : { opening, firmness, position, exclude }
|
||||||
|
|
||||||
|
saveSymptom('cervix', date, valuesToSave)
|
||||||
|
},
|
||||||
|
desire: (data, date, shouldDeleteData) => {
|
||||||
|
const { value } = data
|
||||||
|
const valuesToSave = shouldDeleteData || !isNumber(value)
|
||||||
|
? null : { value }
|
||||||
|
|
||||||
|
saveSymptom('desire', date, valuesToSave)
|
||||||
|
},
|
||||||
|
mood: (data, date, shouldDeleteData) => {
|
||||||
|
saveBoxSymptom(data, date, shouldDeleteData, 'mood')
|
||||||
|
},
|
||||||
|
mucus: (data, date, shouldDeleteData) => {
|
||||||
|
const { feeling, texture, exclude } = data
|
||||||
|
const isDataEntered = ['feeling', 'texture'].some(
|
||||||
|
value => isNumber(data[value]))
|
||||||
|
const valuesToSave = shouldDeleteData || !isDataEntered
|
||||||
|
? null : { feeling, texture, exclude }
|
||||||
|
|
||||||
|
saveSymptom('mucus', date, valuesToSave)
|
||||||
|
},
|
||||||
|
note: (data, date, shouldDeleteData) => {
|
||||||
|
const { value } = data
|
||||||
|
const isValidData = value !== null && value !== ''
|
||||||
|
const valuesToSave = shouldDeleteData || !isValidData ? null : { value }
|
||||||
|
|
||||||
|
saveSymptom('note', date, valuesToSave)
|
||||||
|
},
|
||||||
|
pain: (data, date, shouldDeleteData) => {
|
||||||
|
saveBoxSymptom(data, date, shouldDeleteData, 'pain')
|
||||||
|
},
|
||||||
|
sex: (data, date, shouldDeleteData) => {
|
||||||
|
saveBoxSymptom(data, date, shouldDeleteData, 'sex')
|
||||||
|
},
|
||||||
|
temperature: (data, date, shouldDeleteData) => {
|
||||||
|
const { exclude, note, time, value } = data
|
||||||
|
const valuesToSave = {
|
||||||
|
exclude,
|
||||||
|
note,
|
||||||
|
time,
|
||||||
|
value: Number(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSymptom(
|
||||||
|
'temperature',
|
||||||
|
date,
|
||||||
|
(shouldDeleteData || value === null) ? null : valuesToSave
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveBoxSymptom = (data, date, shouldDeleteData, symptom) => {
|
||||||
|
const isDataEntered = Object.keys(data).some(key => data[key] !== null)
|
||||||
|
const valuesToSave = shouldDeleteData || !isDataEntered
|
||||||
|
? null : data
|
||||||
|
|
||||||
|
saveSymptom(symptom, date, valuesToSave)
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = {
|
||||||
|
bleeding: ({ value, exclude }) => {
|
||||||
|
if (isNumber(value)) {
|
||||||
|
const bleedingLabel = bleedingLabels[value]
|
||||||
|
return exclude ? `(${bleedingLabel})` : bleedingLabel
|
||||||
|
}
|
||||||
|
},
|
||||||
|
temperature: ({ value, time, exclude }) => {
|
||||||
|
if (isNumber(value)) {
|
||||||
|
let temperatureLabel = `${value} °C`
|
||||||
|
if (time) {
|
||||||
|
temperatureLabel += ` - ${time}`
|
||||||
|
}
|
||||||
|
if (exclude) {
|
||||||
|
temperatureLabel = `(${temperatureLabel})`
|
||||||
|
}
|
||||||
|
return temperatureLabel
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mucus: mucus => {
|
||||||
|
const filledCategories = ['feeling', 'texture'].filter(c => isNumber(mucus[c]))
|
||||||
|
let label = filledCategories.map(category => {
|
||||||
|
return labels.mucus.subcategories[category] + ': ' + labels.mucus[category].categories[mucus[category]]
|
||||||
|
}).join(', ')
|
||||||
|
|
||||||
|
if (isNumber(mucus.value)) label += `\n => ${labels.mucusNFP[mucus.value]}`
|
||||||
|
if (mucus.exclude) label = `(${label})`
|
||||||
|
|
||||||
|
return label
|
||||||
|
},
|
||||||
|
cervix: cervix => {
|
||||||
|
const filledCategories = ['opening', 'firmness', 'position'].filter(c => isNumber(cervix[c]))
|
||||||
|
let label = filledCategories.map(category => {
|
||||||
|
return labels.cervix.subcategories[category] + ': ' + labels.cervix[category].categories[cervix[category]]
|
||||||
|
}).join(', ')
|
||||||
|
|
||||||
|
if (cervix.exclude) label = `(${label})`
|
||||||
|
|
||||||
|
return label
|
||||||
|
},
|
||||||
|
note: note => note.value,
|
||||||
|
desire: ({ value }) => {
|
||||||
|
if (isNumber(value)) {
|
||||||
|
return intensityLabels[value]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sex: sex => {
|
||||||
|
const sexLabel = []
|
||||||
|
if (sex && Object.values({...sex}).some(val => val)){
|
||||||
|
Object.keys(sex).forEach(key => {
|
||||||
|
if(sex[key] && key !== 'other' && key !== 'note') {
|
||||||
|
sexLabel.push(
|
||||||
|
sexLabels[key] ||
|
||||||
|
contraceptiveLabels[key]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if(key === 'other' && sex.other) {
|
||||||
|
let label = contraceptiveLabels[key]
|
||||||
|
if(sex.note) {
|
||||||
|
label = `${label} (${sex.note})`
|
||||||
|
}
|
||||||
|
sexLabel.push(label)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return sexLabel.join(', ')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pain: pain => {
|
||||||
|
const painLabel = []
|
||||||
|
if (pain && Object.values({...pain}).some(val => val)){
|
||||||
|
Object.keys(pain).forEach(key => {
|
||||||
|
if(pain[key] && key !== 'other' && key !== 'note') {
|
||||||
|
painLabel.push(painLabels[key])
|
||||||
|
}
|
||||||
|
if(key === 'other' && pain.other) {
|
||||||
|
let label = painLabels[key]
|
||||||
|
if(pain.note) {
|
||||||
|
label = `${label} (${pain.note})`
|
||||||
|
}
|
||||||
|
painLabel.push(label)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return painLabel.join(', ')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mood: mood => {
|
||||||
|
const moodLabel = []
|
||||||
|
if (mood && Object.values({...mood}).some(val => val)){
|
||||||
|
Object.keys(mood).forEach(key => {
|
||||||
|
if(mood[key] && key !== 'other' && key !== 'note') {
|
||||||
|
moodLabel.push(moodLabels[key])
|
||||||
|
}
|
||||||
|
if(key === 'other' && mood.other) {
|
||||||
|
let label = moodLabels[key]
|
||||||
|
if(mood.note) {
|
||||||
|
label = `${label} (${mood.note})`
|
||||||
|
}
|
||||||
|
moodLabel.push(label)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return moodLabel.join(', ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getData = (symptom, symptomData) => {
|
||||||
|
return symptomData && label[symptom](symptomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const prevDate = (dateString) => {
|
||||||
|
return LocalDate.parse(dateString).minusDays(1).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const nextDate = (dateString) => {
|
||||||
|
return LocalDate.parse(dateString).plusDays(1).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isDateInFuture = (dateString) => {
|
||||||
|
return LocalDate.now().isBefore(LocalDate.parse(dateString))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isTomorrowInFuture = (dateString) => {
|
||||||
|
const tomorrow = nextDate(dateString)
|
||||||
|
return LocalDate.now().isBefore(LocalDate.parse(tomorrow))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isYesterdayInFuture = (dateString) => {
|
||||||
|
const yesterday = prevDate(dateString)
|
||||||
|
return LocalDate.now().isBefore(LocalDate.parse(yesterday))
|
||||||
|
}
|
||||||
@@ -1,14 +1,24 @@
|
|||||||
import { LocalDate } from 'js-joda'
|
import { LocalDate } from 'js-joda'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
|
import { general as labels } from '../../i18n/en/cycle-day'
|
||||||
|
|
||||||
export default function (date) {
|
export default function (date) {
|
||||||
const today = LocalDate.now()
|
const today = LocalDate.now()
|
||||||
const dateToDisplay = LocalDate.parse(date)
|
const dateToDisplay = LocalDate.parse(date)
|
||||||
return today.equals(dateToDisplay) ?
|
return today.equals(dateToDisplay) ?
|
||||||
'today' :
|
labels.today :
|
||||||
moment(date).format('MMMM Do YYYY')
|
moment(date).format('MMMM Do YYYY')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatDateForShortText (date) {
|
export function formatDateForShortText (date) {
|
||||||
return moment(date.toString()).format('dddd, MMMM Do')
|
return moment(date.toString()).format('dddd, MMMM Do')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function dateToTitle(dateString) {
|
||||||
|
const today = LocalDate.now()
|
||||||
|
const dateToDisplay = LocalDate.parse(dateString)
|
||||||
|
return today.equals(dateToDisplay) ?
|
||||||
|
labels.today :
|
||||||
|
moment(dateString).format('MMMM Do')
|
||||||
|
}
|
||||||
+1
-11
@@ -1,17 +1,8 @@
|
|||||||
import symptomViews from './cycle-day/symptoms'
|
|
||||||
import settingsViews from './settings'
|
import settingsViews from './settings'
|
||||||
|
|
||||||
import settingsLabels from '../i18n/en/settings'
|
import settingsLabels from '../i18n/en/settings'
|
||||||
const labels = settingsLabels.menuItems
|
const labels = settingsLabels.menuItems
|
||||||
|
|
||||||
const symptomsPages = Object.keys(symptomViews).map(symptomView => ({
|
|
||||||
component: symptomView,
|
|
||||||
parent: 'CycleDay',
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const isSymptomView =
|
|
||||||
(page) => Object.keys(symptomViews).includes(page)
|
|
||||||
|
|
||||||
export const isSettingsView =
|
export const isSettingsView =
|
||||||
(page) => Object.keys(settingsViews).includes(page)
|
(page) => Object.keys(settingsViews).includes(page)
|
||||||
|
|
||||||
@@ -82,6 +73,5 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
component: 'CycleDay',
|
component: 'CycleDay',
|
||||||
parent: 'Home',
|
parent: 'Home',
|
||||||
},
|
}
|
||||||
...symptomsPages
|
|
||||||
]
|
]
|
||||||
+1
-3
@@ -1,7 +1,6 @@
|
|||||||
import Home from './home'
|
import Home from './home'
|
||||||
import Calendar from './calendar'
|
import Calendar from './calendar'
|
||||||
import CycleDay from './cycle-day/cycle-day-overview'
|
import CycleDay from './cycle-day/cycle-day-overview'
|
||||||
import symptomViews from './cycle-day/symptoms'
|
|
||||||
import Chart from './chart/chart'
|
import Chart from './chart/chart'
|
||||||
import SettingsMenu from './settings/settings-menu'
|
import SettingsMenu from './settings/settings-menu'
|
||||||
import settingsViews from './settings'
|
import settingsViews from './settings'
|
||||||
@@ -14,6 +13,5 @@ export const viewsList = {
|
|||||||
Chart,
|
Chart,
|
||||||
SettingsMenu,
|
SettingsMenu,
|
||||||
...settingsViews,
|
...settingsViews,
|
||||||
Stats,
|
Stats
|
||||||
...symptomViews
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export const ACTION_IMPORT = 'import'
|
|||||||
|
|
||||||
export const SYMPTOMS = [
|
export const SYMPTOMS = [
|
||||||
'bleeding',
|
'bleeding',
|
||||||
|
'temperature',
|
||||||
'mucus',
|
'mucus',
|
||||||
'cervix',
|
'cervix',
|
||||||
'sex',
|
'sex',
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const cervix = {
|
|||||||
categories: ['low', 'medium', 'high'],
|
categories: ['low', 'medium', 'high'],
|
||||||
explainer: 'How high up in the vagina is the cervix?'
|
explainer: 'How high up in the vagina is the cervix?'
|
||||||
},
|
},
|
||||||
|
excludeExplainer: "You can exclude this value if you don't want to use it for fertility detection.",
|
||||||
actionHint: 'Choose values for at least "Opening" and "Firmness" to save.'
|
actionHint: 'Choose values for at least "Opening" and "Firmness" to save.'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +133,11 @@ export const temperature = {
|
|||||||
|
|
||||||
export const noteExplainer = "Anything you want to add for the day?"
|
export const noteExplainer = "Anything you want to add for the day?"
|
||||||
|
|
||||||
|
export const general = {
|
||||||
|
cycleDayNumber: "Cycle day ",
|
||||||
|
today: "Today"
|
||||||
|
}
|
||||||
|
|
||||||
export const sharedDialogs = {
|
export const sharedDialogs = {
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
areYouSureTitle: 'Are you sure?',
|
areYouSureTitle: 'Are you sure?',
|
||||||
|
|||||||
@@ -1,4 +1,19 @@
|
|||||||
|
import Colors from './colors'
|
||||||
|
import Spacing from './spacing'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
box: {
|
||||||
|
borderColor: Colors.orange,
|
||||||
|
borderRadius: 5,
|
||||||
|
borderWidth: 1,
|
||||||
|
marginTop: Spacing.small,
|
||||||
|
marginRight: Spacing.small,
|
||||||
|
paddingHorizontal: Spacing.small,
|
||||||
|
paddingVertical: Spacing.tiny
|
||||||
|
},
|
||||||
|
boxActive: {
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
},
|
||||||
centerItems: {
|
centerItems: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -8,5 +23,11 @@ export default {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between'
|
justifyContent: 'space-between'
|
||||||
|
},
|
||||||
|
selectGroupContainer: {
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
marginVertical: Spacing.small
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-1
@@ -2,5 +2,7 @@ export default {
|
|||||||
tiny: 4,
|
tiny: 4,
|
||||||
small: 10,
|
small: 10,
|
||||||
base: 16,
|
base: 16,
|
||||||
large: 20
|
large: 20,
|
||||||
|
symptomTileWidth: '48%',
|
||||||
|
textWidth: '70%'
|
||||||
}
|
}
|
||||||
@@ -96,5 +96,12 @@ export default {
|
|||||||
fontSize: sizes.title,
|
fontSize: sizes.title,
|
||||||
marginHorizontal: Spacing.base,
|
marginHorizontal: Spacing.base,
|
||||||
...title
|
...title
|
||||||
|
},
|
||||||
|
titleWithoutMargin: {
|
||||||
|
alignSelf: 'center',
|
||||||
|
color: Colors.purple,
|
||||||
|
fontFamily: fonts.bold,
|
||||||
|
fontWeight: '700',
|
||||||
|
fontSize: sizes.title,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user