Merge branch 'rebased-redesign' into 'master'
Rebased redesign See merge request bloodyhealth/drip!305
@@ -4,10 +4,7 @@
|
|||||||
"mocha": true,
|
"mocha": true,
|
||||||
"es6": true
|
"es6": true
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": ["eslint:recommended", "plugin:react/recommended"],
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:react/recommended"
|
|
||||||
],
|
|
||||||
"parser": "babel-eslint",
|
"parser": "babel-eslint",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
@@ -16,31 +13,18 @@
|
|||||||
},
|
},
|
||||||
"ecmaVersion": 2018
|
"ecmaVersion": 2018
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": ["react"],
|
||||||
"react"
|
|
||||||
],
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"react": {
|
"react": {
|
||||||
"version": require('./package.json').dependencies.react,
|
"version": require("./package.json").dependencies.react
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"indent": [
|
"indent": ["error", 2],
|
||||||
"error",
|
"no-console": ["error", { "allow": ["warn", "error"] }],
|
||||||
2
|
|
||||||
],
|
|
||||||
"no-console": [
|
|
||||||
"error",
|
|
||||||
{ allow: ["warn", "error"] }
|
|
||||||
],
|
|
||||||
"space-before-function-paren": 0,
|
"space-before-function-paren": 0,
|
||||||
"semi": [
|
"semi": ["warn", "never"],
|
||||||
"warn",
|
"space-infix-ops": ["warn"],
|
||||||
"never"
|
|
||||||
],
|
|
||||||
"space-infix-ops": [
|
|
||||||
"warn"
|
|
||||||
],
|
|
||||||
"no-var": "error",
|
"no-var": "error",
|
||||||
"prefer-const": "error",
|
"prefer-const": "error",
|
||||||
"no-trailing-spaces": "error",
|
"no-trailing-spaces": "error",
|
||||||
@@ -53,6 +37,6 @@
|
|||||||
"ignoreTemplateLiterals": true
|
"ignoreTemplateLiterals": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"no-multi-spaces": 2,
|
"no-multi-spaces": 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
#
|
#
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/
|
||||||
|
|
||||||
# Xcode
|
# Xcode
|
||||||
#
|
#
|
||||||
build/
|
build/
|
||||||
@@ -65,6 +68,7 @@ android/app/.project
|
|||||||
|
|
||||||
ios/Podfile.lock
|
ios/Podfile.lock
|
||||||
android/app/src/main/res/drawable-*
|
android/app/src/main/res/drawable-*
|
||||||
|
android/app/src/main/assets/*
|
||||||
|
|
||||||
# nodejs-mobile creates these with every npm install
|
# nodejs-mobile creates these with every npm install
|
||||||
nodejs-assets/nodejs-project/sample-*
|
nodejs-assets/nodejs-project/sample-*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Contributing to drip aka CONDRIBUTING
|
# Contributing to drip aka CONDRIPUTING
|
||||||
So good to see you here, hello :wave\_tone1: :wave\_tone2: :wave\_tone3: :wave\_tone4: :wave\_tone5:
|
So good to see you here, hello :wave\_tone1: :wave\_tone2: :wave\_tone3: :wave\_tone4: :wave\_tone5:
|
||||||
|
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ So good to see you here, hello :wave\_tone1: :wave\_tone2: :wave\_tone3: :wave\_
|
|||||||
|
|
||||||
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
|
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
|
||||||
|
|
||||||
[How can I condribute?](#how-can-i-condribute)
|
[How can I condripute?](#how-can-i-condripute)
|
||||||
|
|
||||||
[Thank you](#thank-you)
|
[Thank you](#thank-you)
|
||||||
|
|
||||||
@@ -23,9 +23,9 @@ We have prepared something for **you**: check out our [README](https://gitlab.co
|
|||||||
|
|
||||||
Let us know if you want to suggest improvements for the README and open a merge request (which is just like Github's pull request)
|
Let us know if you want to suggest improvements for the README and open a merge request (which is just like Github's pull request)
|
||||||
|
|
||||||
## How can I condribute?
|
## How can I condripute?
|
||||||
|
|
||||||
### Your First Code Condribution
|
### Your First Code Condripution
|
||||||
|
|
||||||
We are fans of labels, at least for our issues. You can find a list of `newbie` issues [here](https://gitlab.com/bloodyhealth/drip/issues?label_name%5B%5D=Newbie).
|
We are fans of labels, at least for our issues. You can find a list of `newbie` issues [here](https://gitlab.com/bloodyhealth/drip/issues?label_name%5B%5D=Newbie).
|
||||||
If you decide to work on an issue, please click on `Create branch` based on that issue. You can find this as a dropdown option right under `Create merge request`.
|
If you decide to work on an issue, please click on `Create branch` based on that issue. You can find this as a dropdown option right under `Create merge request`.
|
||||||
@@ -54,5 +54,5 @@ To send us a new issue you can also use our [gitlab email](mailto:incoming+blood
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Thank you for condributing to open source, thank you for condributing to drip!
|
Thank you for condriputing to open source, thank you for condriputing to drip!
|
||||||
Much love from Bloody Health :heart\_exclamation:
|
Much love from Bloody Health :heart\_exclamation:
|
||||||
|
|||||||
@@ -23,13 +23,15 @@
|
|||||||
android:name=".MainApplication"
|
android:name=".MainApplication"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:screenOrientation="sensorPortrait">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 59 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<font
|
||||||
|
android:font="@font/jost400"
|
||||||
|
android:fontStyle="normal"
|
||||||
|
android:fontWeight="400" />
|
||||||
|
</font-family>
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
||||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 430 B After Width: | Height: | Size: 919 B |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 327 B After Width: | Height: | Size: 542 B |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 642 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 879 B After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -9,4 +9,9 @@
|
|||||||
|
|
||||||
<!-- a secondary color for controls like checkboxes and text fields -->
|
<!-- a secondary color for controls like checkboxes and text fields -->
|
||||||
<color name="colorAccent">#4FAFA7</color>
|
<color name="colorAccent">#4FAFA7</color>
|
||||||
|
|
||||||
|
<!-- custom colors -->
|
||||||
|
<color name="grey">#A5A5A5</color>
|
||||||
|
<color name="orange">#F38337</color>
|
||||||
|
<color name="purple">#3A2671</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#000D19</color>
|
<color name="ic_launcher_background">#3A2671</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -3,7 +3,27 @@
|
|||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
<!-- Customize your theme here. -->
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="ButtonBarStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
|
||||||
|
<item name="android:textSize">16sp</item>
|
||||||
|
<item name="android:textColor">@color/grey</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="PositiveButtonStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
|
||||||
|
<item name="android:textSize">16sp</item>
|
||||||
|
<item name="android:textStyle">bold</item>
|
||||||
|
<item name="android:textColor">@color/orange</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="TitleStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
|
||||||
|
<item name="android:gravity">left</item>
|
||||||
|
<item name="android:textColor">@color/purple</item>
|
||||||
|
<item name="android:textSize">22sp</item>
|
||||||
|
<item name="android:textStyle">bold</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,18 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
|
|
||||||
import { View } from 'react-native'
|
|
||||||
|
|
||||||
import AppText from './app-text'
|
|
||||||
import { shared } from '../i18n/en/labels'
|
|
||||||
|
|
||||||
const AppLoadingView = () => {
|
|
||||||
return (
|
|
||||||
<View flex={1}>
|
|
||||||
<View style={{flex:1, justifyContent: 'center'}}>
|
|
||||||
<AppText style={{alignSelf: 'center'}}>{shared.loading}</AppText>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AppLoadingView
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { TextInput } from 'react-native'
|
|
||||||
import styles from '../styles'
|
|
||||||
|
|
||||||
export default function AppTextInput({ style, ...props }) {
|
|
||||||
if (!Array.isArray(style)) style = [style]
|
|
||||||
return (
|
|
||||||
<TextInput
|
|
||||||
style={[styles.textInputField, ...style]}
|
|
||||||
autoFocus={props.autoFocus}
|
|
||||||
onChangeText={props.onChangeText}
|
|
||||||
value={props.value}
|
|
||||||
placeholder={props.placeholder}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AppTextInput.propTypes = {
|
|
||||||
autoFocus: PropTypes.bool,
|
|
||||||
onChangeText: PropTypes.func,
|
|
||||||
placeholder: PropTypes.string,
|
|
||||||
style: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
|
||||||
value: PropTypes.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
AppTextInput.defaultProps = {
|
|
||||||
style: []
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { Text } from 'react-native'
|
|
||||||
import styles from "../styles"
|
|
||||||
import Link from './link'
|
|
||||||
|
|
||||||
export default function AppText({ children, onPress, numberOfLines, style}) {
|
|
||||||
// we parse for links in case the text contains any
|
|
||||||
return (
|
|
||||||
<Link>
|
|
||||||
<Text style={[styles.appText, style]}
|
|
||||||
onPress={onPress}
|
|
||||||
numberOfLines={numberOfLines}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Text>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AppText.propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
onPress: PropTypes.func,
|
|
||||||
numberOfLines: PropTypes.number,
|
|
||||||
style: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ import { openDb } from '../db'
|
|||||||
import App from './app'
|
import App from './app'
|
||||||
import PasswordPrompt from './password-prompt'
|
import PasswordPrompt from './password-prompt'
|
||||||
import License from './license'
|
import License from './license'
|
||||||
import AppLoadingView from './app-loading'
|
import AppLoadingView from './common/app-loading'
|
||||||
|
|
||||||
import store from "../store"
|
import store from "../store"
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
|
|||||||
@@ -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,8 @@ 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 isTemperatureEditView = currentPage === 'TemperatureEditView'
|
||||||
|
|
||||||
const headerProps = {
|
const headerProps = {
|
||||||
title,
|
title,
|
||||||
@@ -76,24 +75,25 @@ class App extends Component {
|
|||||||
const pageProps = {
|
const pageProps = {
|
||||||
cycleDay: date && getCycleDay(date),
|
cycleDay: date && getCycleDay(date),
|
||||||
date,
|
date,
|
||||||
|
isTemperatureEditView,
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={styles.container}>
|
||||||
{
|
<Header { ...headerProps } />
|
||||||
!isSymptomEditView &&
|
|
||||||
!isCycleDayView &&
|
|
||||||
<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),
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { TouchableOpacity } from 'react-native'
|
|
||||||
import AppText from './app-text'
|
|
||||||
import styles from '../styles'
|
|
||||||
|
|
||||||
export default function Button({
|
|
||||||
backgroundColor,
|
|
||||||
children,
|
|
||||||
onPress,
|
|
||||||
style,
|
|
||||||
testID
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={onPress}
|
|
||||||
style={[styles.button, style, { backgroundColor }]}
|
|
||||||
testID={testID}
|
|
||||||
>
|
|
||||||
<AppText style={styles.homeButtonText}>{children}</AppText>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Button.propTypes = {
|
|
||||||
backgroundColor: PropTypes.string,
|
|
||||||
children: PropTypes.node,
|
|
||||||
onPress: PropTypes.func,
|
|
||||||
style: PropTypes.object,
|
|
||||||
testID: PropTypes.string
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,26 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
import { CalendarList } from 'react-native-calendars'
|
import { CalendarList } from 'react-native-calendars'
|
||||||
import { connect } from 'react-redux'
|
|
||||||
|
|
||||||
|
import { connect } from 'react-redux'
|
||||||
import { setDate } from '../slices/date'
|
import { setDate } from '../slices/date'
|
||||||
import { navigate } from '../slices/navigation'
|
import { navigate } from '../slices/navigation'
|
||||||
|
|
||||||
import { LocalDate } from 'js-joda'
|
|
||||||
import { getBleedingDaysSortedByDate } from '../db'
|
import { getBleedingDaysSortedByDate } from '../db'
|
||||||
import cycleModule from '../lib/cycle'
|
import cycleModule from '../lib/cycle'
|
||||||
import { shadesOfRed, calendarTheme } from '../styles/index'
|
|
||||||
import styles from '../styles/index'
|
|
||||||
import nothingChanged from '../db/db-unchanged'
|
import nothingChanged from '../db/db-unchanged'
|
||||||
|
import {
|
||||||
|
calendarTheme,
|
||||||
|
predictionToCalFormat,
|
||||||
|
toCalFormat,
|
||||||
|
todayToCalFormat,
|
||||||
|
} from './helpers/calendar'
|
||||||
|
|
||||||
class CalendarView extends Component {
|
class CalendarView extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
setDate: PropTypes.func.isRequired,
|
setDate: PropTypes.func.isRequired,
|
||||||
navigate: PropTypes.func.isRequired
|
navigate: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -26,7 +30,7 @@ class CalendarView extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
bleedingDaysInCalFormat: toCalFormat(this.bleedingDays),
|
bleedingDaysInCalFormat: toCalFormat(this.bleedingDays),
|
||||||
predictedBleedingDaysInCalFormat: predictionToCalFormat(predictedMenses),
|
predictedBleedingDaysInCalFormat: predictionToCalFormat(predictedMenses),
|
||||||
todayInCalFormat: todayToCalFormat()
|
todayInCalFormat: todayToCalFormat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bleedingDays.addListener(this.setStateWithCalFormattedDays)
|
this.bleedingDays.addListener(this.setStateWithCalFormattedDays)
|
||||||
@@ -38,7 +42,7 @@ class CalendarView extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
bleedingDaysInCalFormat: toCalFormat(this.bleedingDays),
|
bleedingDaysInCalFormat: toCalFormat(this.bleedingDays),
|
||||||
predictedBleedingDaysInCalFormat: predictionToCalFormat(predictedMenses),
|
predictedBleedingDaysInCalFormat: predictionToCalFormat(predictedMenses),
|
||||||
todayInCalFormat: todayToCalFormat()
|
todayInCalFormat: todayToCalFormat(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,93 +56,44 @@ class CalendarView extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {
|
||||||
|
todayInCalFormat,
|
||||||
|
bleedingDaysInCalFormat,
|
||||||
|
predictedBleedingDaysInCalFormat,
|
||||||
|
} = this.state
|
||||||
|
const markedDates = Object.assign(
|
||||||
|
{},
|
||||||
|
todayInCalFormat,
|
||||||
|
bleedingDaysInCalFormat,
|
||||||
|
predictedBleedingDaysInCalFormat
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CalendarList
|
<View style={styles.container}>
|
||||||
onDayPress={this.passDateToDayView.bind(this)}
|
<CalendarList
|
||||||
markedDates={
|
// If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
|
||||||
Object.assign(
|
firstDay={1}
|
||||||
{},
|
onDayPress={this.passDateToDayView.bind(this)}
|
||||||
this.state.todayInCalFormat,
|
markedDates={markedDates}
|
||||||
this.state.bleedingDaysInCalFormat,
|
markingType='custom'
|
||||||
this.state.predictedBleedingDaysInCalFormat
|
theme={calendarTheme}
|
||||||
)
|
// Max amount of months allowed to scroll to the past.
|
||||||
}
|
pastScrollRange={120}
|
||||||
markingType={'custom'}
|
/>
|
||||||
// If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
|
</View>
|
||||||
firstDay={1}
|
|
||||||
theme={calendarTheme}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: { flex: 1 },
|
||||||
|
})
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => {
|
const mapDispatchToProps = (dispatch) => {
|
||||||
return({
|
return {
|
||||||
setDate: (date) => dispatch(setDate(date)),
|
setDate: (date) => dispatch(setDate(date)),
|
||||||
navigate: (page) => dispatch(navigate(page)),
|
navigate: (page) => dispatch(navigate(page)),
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
null,
|
|
||||||
mapDispatchToProps,
|
|
||||||
)(CalendarView)
|
|
||||||
|
|
||||||
|
|
||||||
function toCalFormat(bleedingDaysSortedByDate) {
|
|
||||||
const todayDateString = LocalDate.now().toString()
|
|
||||||
return bleedingDaysSortedByDate.reduce((acc, day) => {
|
|
||||||
acc[day.date] = {
|
|
||||||
customStyles: {
|
|
||||||
container: {
|
|
||||||
backgroundColor: shadesOfRed[day.bleeding.value],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (day.date === todayDateString) {
|
|
||||||
acc[day.date].customStyles.text = styles.calendarToday
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
function predictionToCalFormat(predictedDays) {
|
|
||||||
if (!predictedDays.length) return {}
|
|
||||||
const todayDateString = LocalDate.now().toString()
|
|
||||||
const middleIndex = (predictedDays[0].length - 1) / 2
|
|
||||||
return predictedDays.reduce((acc, setOfDays) => {
|
|
||||||
setOfDays.reduce((accSet, day, i) => {
|
|
||||||
accSet[day] = {
|
|
||||||
customStyles: {
|
|
||||||
container: {
|
|
||||||
borderColor: (i === middleIndex) ? shadesOfRed[3] : shadesOfRed[2],
|
|
||||||
borderWidth: 3,
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
marginTop: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (day === todayDateString) {
|
|
||||||
accSet[day].customStyles.text = Object.assign(
|
|
||||||
{},
|
|
||||||
styles.calendarToday,
|
|
||||||
{marginTop: -2}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return accSet
|
|
||||||
}, acc)
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
function todayToCalFormat() {
|
|
||||||
const todayDateString = LocalDate.now().toString()
|
|
||||||
const todayFormated = {}
|
|
||||||
todayFormated[todayDateString] = {
|
|
||||||
customStyles: {
|
|
||||||
text: styles.calendarToday
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return todayFormated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(CalendarView)
|
||||||
|
|||||||
@@ -1,32 +1,38 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import AppText from '../app-text'
|
import AppText from '../common/app-text'
|
||||||
import DripHomeIcon from '../../assets/drip-home-icons'
|
|
||||||
|
|
||||||
import styles from './styles'
|
|
||||||
import { cycleDayColor } from '../../styles'
|
|
||||||
|
|
||||||
|
import { Typography } from '../../styles'
|
||||||
|
import { CHART_YAXIS_WIDTH } from '../../config'
|
||||||
import { shared as labels } from '../../i18n/en/labels'
|
import { shared as labels } from '../../i18n/en/labels'
|
||||||
|
|
||||||
const ChartLegend = ({ xAxisHeight }) => {
|
const ChartLegend = ({ height }) => {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.yAxis, styles.chartLegend, {height: xAxisHeight}]}>
|
<View style={[styles.container, { height }]}>
|
||||||
<DripHomeIcon
|
<AppText style={styles.textBold}>#</AppText>
|
||||||
name="circle"
|
<AppText style={styles.text}>{labels.date}</AppText>
|
||||||
size={styles.yAxis.width - 7}
|
|
||||||
color={cycleDayColor}
|
|
||||||
/>
|
|
||||||
<AppText style={styles.yAxisLabels.dateLabel}>
|
|
||||||
{labels.date.toLowerCase()}
|
|
||||||
</AppText>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ChartLegend.propTypes = {
|
ChartLegend.propTypes = {
|
||||||
xAxisHeight: PropTypes.number.isRequired
|
height: PropTypes.number.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
width: CHART_YAXIS_WIDTH
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
...Typography.label,
|
||||||
|
},
|
||||||
|
textBold: {
|
||||||
|
...Typography.labelBold
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default ChartLegend
|
export default ChartLegend
|
||||||
|
|||||||
@@ -3,20 +3,16 @@ import PropTypes from 'prop-types'
|
|||||||
|
|
||||||
import { Shape } from 'react-native/Libraries/ART/ReactNativeART'
|
import { Shape } from 'react-native/Libraries/ART/ReactNativeART'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors } from '../../styles'
|
||||||
|
import { CHART_STROKE_WIDTH, CHART_GRID_LINE_HORIZONTAL_WIDTH } from '../../config'
|
||||||
|
|
||||||
const ChartLine = ({ path, isNfpLine = false }) => {
|
const ChartLine = ({ path, isNfpLine }) => {
|
||||||
const strokeStyle =
|
const color = isNfpLine ? Colors.orange : Colors.grey
|
||||||
isNfpLine ? styles.nfpLine.stroke : styles.column.stroke.color
|
const width = isNfpLine
|
||||||
const strokeWidth =
|
? CHART_STROKE_WIDTH : CHART_GRID_LINE_HORIZONTAL_WIDTH * 2.5
|
||||||
isNfpLine ? styles.nfpLine.strokeWidth : styles.column.stroke.width
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Shape
|
<Shape d={path} stroke={color} strokeWidth={width} />
|
||||||
stroke={strokeStyle}
|
|
||||||
strokeWidth={strokeWidth}
|
|
||||||
d={path}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,4 +21,8 @@ ChartLine.propTypes = {
|
|||||||
isNfpLine: PropTypes.bool,
|
isNfpLine: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChartLine.defaultProps = {
|
||||||
|
isNfpLine: false
|
||||||
|
}
|
||||||
|
|
||||||
export default ChartLine
|
export default ChartLine
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View, FlatList, ActivityIndicator } from 'react-native'
|
import { ActivityIndicator, FlatList, Dimensions, StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppLoadingView from '../common/app-loading'
|
||||||
|
import AppPage from '../common/app-page'
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import NoData from './no-data'
|
|
||||||
import AppLoadingView from '../app-loading'
|
|
||||||
import YAxis from './y-axis'
|
|
||||||
import nfpLines from './nfp-lines'
|
|
||||||
import DayColumn from './day-column'
|
import DayColumn from './day-column'
|
||||||
import HorizontalGrid from './horizontal-grid'
|
import HorizontalGrid from './horizontal-grid'
|
||||||
import AppText from '../app-text'
|
import NoData from './no-data'
|
||||||
|
import Tutorial from './tutorial'
|
||||||
|
import YAxis from './y-axis'
|
||||||
|
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { navigate } from '../../slices/navigation'
|
import { navigate } from '../../slices/navigation'
|
||||||
|
|
||||||
import { getCycleDaysSortedByDate } from '../../db'
|
import { getCycleDaysSortedByDate } from '../../db'
|
||||||
import nothingChanged from '../../db/db-unchanged'
|
import nothingChanged from '../../db/db-unchanged'
|
||||||
import { scaleObservable } from '../../local-storage'
|
import { getChartFlag, scaleObservable, setChartFlag } from '../../local-storage'
|
||||||
import { makeColumnInfo } from '../helpers/chart'
|
import { makeColumnInfo, nfpLines } from '../helpers/chart'
|
||||||
|
|
||||||
import config from '../../config'
|
import {
|
||||||
|
CHART_COLUMN_WIDTH,
|
||||||
|
SYMPTOMS,
|
||||||
|
CHART_SYMPTOM_HEIGHT_RATIO,
|
||||||
|
CHART_XAXIS_HEIGHT_RATIO
|
||||||
|
} from '../../config'
|
||||||
import { shared } from '../../i18n/en/labels'
|
import { shared } from '../../i18n/en/labels'
|
||||||
import styles from './styles'
|
import { Colors, Spacing } from '../../styles'
|
||||||
|
|
||||||
class CycleChart extends Component {
|
class CycleChart extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -36,13 +43,37 @@ class CycleChart extends Component {
|
|||||||
this.getFhmAndLtlInfo = nfpLines()
|
this.getFhmAndLtlInfo = nfpLines()
|
||||||
this.shouldShowTemperatureColumn = false
|
this.shouldShowTemperatureColumn = false
|
||||||
|
|
||||||
|
this.checkShouldShowHint()
|
||||||
this.prepareSymptomData()
|
this.prepareSymptomData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.cycleDaysSortedByDate.removeListener(this.handleDbChange)
|
||||||
|
this.removeObvListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
checkShouldShowHint = async () => {
|
||||||
|
const flag = await getChartFlag()
|
||||||
|
const shouldShowHint = flag === 'true' ? true : false
|
||||||
|
this.setState({ shouldShowHint })
|
||||||
|
}
|
||||||
|
|
||||||
|
setShouldShowHint = async () => {
|
||||||
|
await setChartFlag()
|
||||||
|
this.setState({ shouldShowHint: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
onLayout = () => {
|
||||||
|
if (this.state.chartHeight) return false
|
||||||
|
|
||||||
|
this.reCalculateChartInfo()
|
||||||
|
this.updateListeners(this.reCalculateChartInfo)
|
||||||
|
}
|
||||||
|
|
||||||
prepareSymptomData = () => {
|
prepareSymptomData = () => {
|
||||||
this.symptomRowSymptoms = config.symptoms.filter((symptomName) => {
|
this.symptomRowSymptoms = SYMPTOMS.filter((symptomName) => {
|
||||||
return this.cycleDaysSortedByDate.some(cycleDay => {
|
return this.cycleDaysSortedByDate.some(cycleDay => {
|
||||||
return cycleDay[symptomName]
|
return (symptomName !== 'temperature') && cycleDay[symptomName]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
this.chartSymptoms = [...this.symptomRowSymptoms]
|
this.chartSymptoms = [...this.symptomRowSymptoms]
|
||||||
@@ -69,35 +100,23 @@ class CycleChart extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
reCalculateChartInfo = (nativeEvent) => {
|
reCalculateChartInfo = () => {
|
||||||
const { height, width } = nativeEvent.layout
|
const { width, height } = Dimensions.get('window')
|
||||||
const xAxisCoefficient = this.shouldShowTemperatureColumn ?
|
|
||||||
config.xAxisHeightPercentage : config.xAxisHeightPercentageLarge
|
|
||||||
const symptomCoefficient = this.shouldShowTemperatureColumn ?
|
|
||||||
config.symptomHeightPercentage : config.symptomHeightPercentageLarge
|
|
||||||
|
|
||||||
this.xAxisHeight = height * xAxisCoefficient
|
this.xAxisHeight = height * 0.7 * CHART_XAXIS_HEIGHT_RATIO
|
||||||
const remainingHeight = height - this.xAxisHeight
|
const remainingHeight = height * 0.7 - this.xAxisHeight
|
||||||
this.symptomHeight = remainingHeight * symptomCoefficient
|
this.symptomHeight = remainingHeight * CHART_SYMPTOM_HEIGHT_RATIO
|
||||||
this.symptomRowHeight = this.symptomRowSymptoms.length *
|
this.symptomRowHeight = this.symptomRowSymptoms.length *
|
||||||
this.symptomHeight
|
this.symptomHeight
|
||||||
this.columnHeight = remainingHeight - this.symptomRowHeight
|
this.columnHeight = remainingHeight - this.symptomRowHeight
|
||||||
const chartHeight = this.shouldShowTemperatureColumn ?
|
const chartHeight = this.shouldShowTemperatureColumn ?
|
||||||
height : (this.symptomRowHeight + this.xAxisHeight)
|
height * 0.7 : (this.symptomRowHeight + this.xAxisHeight)
|
||||||
|
const numberOfColumnsToRender = Math.round(width / CHART_COLUMN_WIDTH)
|
||||||
const numberOfColumnsToRender = Math.round(width / config.columnWidth)
|
|
||||||
const columns = makeColumnInfo()
|
const columns = makeColumnInfo()
|
||||||
|
|
||||||
this.setState({ columns, chartHeight, numberOfColumnsToRender })
|
this.setState({ columns, chartHeight, numberOfColumnsToRender })
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayout = ({ nativeEvent }) => {
|
|
||||||
if (this.state.chartHeight) return
|
|
||||||
|
|
||||||
this.reCalculateChartInfo(nativeEvent)
|
|
||||||
this.updateListeners(this.reCalculateChartInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateListeners(dataUpdateHandler) {
|
updateListeners(dataUpdateHandler) {
|
||||||
// remove existing listeners
|
// remove existing listeners
|
||||||
if(this.handleDbChange) {
|
if(this.handleDbChange) {
|
||||||
@@ -114,38 +133,47 @@ class CycleChart extends Component {
|
|||||||
this.removeObvListener = scaleObservable(dataUpdateHandler, false)
|
this.removeObvListener = scaleObservable(dataUpdateHandler, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.cycleDaysSortedByDate.removeListener(this.handleDbChange)
|
|
||||||
this.removeObvListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { chartHeight, chartLoaded, numberOfColumnsToRender } = this.state
|
const {
|
||||||
const shouldShowChart = this.chartSymptoms.length > 0 ? true : false
|
chartHeight,
|
||||||
|
chartLoaded,
|
||||||
|
shouldShowHint,
|
||||||
|
numberOfColumnsToRender
|
||||||
|
} = this.state
|
||||||
|
const hasDataToDisplay = this.chartSymptoms.length > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View onLayout={this.onLayout} style={styles.container}>
|
<AppPage
|
||||||
{!shouldShowChart && <NoData navigate={this.props.navigate}/>}
|
contentContainerStyle={styles.pageContainer}
|
||||||
{shouldShowChart && !chartHeight && !chartLoaded && <AppLoadingView />}
|
onLayout={this.onLayout}
|
||||||
|
scrollViewStyle={styles.page}
|
||||||
|
>
|
||||||
|
{!hasDataToDisplay && <NoData />}
|
||||||
|
{hasDataToDisplay && !chartHeight && !chartLoaded && <AppLoadingView />}
|
||||||
<View style={styles.chartContainer}>
|
<View style={styles.chartContainer}>
|
||||||
{shouldShowChart && (
|
{shouldShowHint && chartLoaded &&
|
||||||
|
<Tutorial onClose={this.setShouldShowHint} />
|
||||||
|
}
|
||||||
|
{hasDataToDisplay && chartLoaded &&
|
||||||
|
!this.shouldShowTemperatureColumn &&
|
||||||
|
<View style={styles.centerItem}>
|
||||||
|
<AppText style={styles.warning}>
|
||||||
|
{shared.noTemperatureWarning}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
{hasDataToDisplay && (
|
||||||
<View style={styles.chartArea}>
|
<View style={styles.chartArea}>
|
||||||
|
|
||||||
{chartHeight && chartLoaded && (
|
{chartHeight && chartLoaded && (
|
||||||
<React.Fragment>
|
<YAxis
|
||||||
<YAxis
|
height={this.columnHeight}
|
||||||
height={this.columnHeight}
|
symptomsToDisplay={this.symptomRowSymptoms}
|
||||||
symptomsToDisplay={this.symptomRowSymptoms}
|
symptomsSectionHeight={this.symptomRowHeight}
|
||||||
symptomsSectionHeight={this.symptomRowHeight}
|
shouldShowTemperatureColumn=
|
||||||
shouldShowTemperatureColumn=
|
{this.shouldShowTemperatureColumn}
|
||||||
{this.shouldShowTemperatureColumn}
|
xAxisHeight={this.xAxisHeight}
|
||||||
xAxisHeight={this.xAxisHeight}
|
/>
|
||||||
/>
|
|
||||||
{this.shouldShowTemperatureColumn && (<HorizontalGrid
|
|
||||||
height={this.columnHeight}
|
|
||||||
startPosition={this.symptomRowHeight}
|
|
||||||
/>)}
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{chartHeight &&
|
{chartHeight &&
|
||||||
@@ -162,27 +190,28 @@ class CycleChart extends Component {
|
|||||||
onEndReached={() => this.setState({end: true})}
|
onEndReached={() => this.setState({end: true})}
|
||||||
ListFooterComponent={<LoadingMoreView end={this.state.end}/>}
|
ListFooterComponent={<LoadingMoreView end={this.state.end}/>}
|
||||||
updateCellsBatchingPeriod={800}
|
updateCellsBatchingPeriod={800}
|
||||||
contentContainerStyle={{height: chartHeight}}
|
contentContainerStyle={{ height: chartHeight }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
{chartHeight && chartLoaded && (
|
||||||
|
<React.Fragment>
|
||||||
|
{this.shouldShowTemperatureColumn &&
|
||||||
|
<HorizontalGrid height={this.columnHeight} />
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{shouldShowChart && chartLoaded && !this.shouldShowTemperatureColumn
|
</AppPage>
|
||||||
&& (
|
|
||||||
<View style={styles.centerItem}>
|
|
||||||
<AppText style={{textAlign: 'center'}}>{shared.noTemperatureWarning}</AppText>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function LoadingMoreView({ end }) {
|
function LoadingMoreView({ end }) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.loadingMore}>
|
<View style={styles.loadingContainer}>
|
||||||
{!end && <ActivityIndicator size={'large'} color={'white'}/>}
|
{!end && <ActivityIndicator size={'large'} color={Colors.orange}/>}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -191,6 +220,29 @@ LoadingMoreView.propTypes = {
|
|||||||
end: PropTypes.bool
|
end: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
chartArea: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
chartContainer: {
|
||||||
|
flexDirection: 'column'
|
||||||
|
},
|
||||||
|
loadingContainer: {
|
||||||
|
height: '100%',
|
||||||
|
backgroundColor: Colors.turquoiseLight,
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
marginVertical: Spacing.small
|
||||||
|
},
|
||||||
|
pageContainer: {
|
||||||
|
paddingHorizontal: Spacing.base
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
padding: Spacing.large
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => {
|
const mapDispatchToProps = (dispatch) => {
|
||||||
return({
|
return({
|
||||||
navigate: (page) => dispatch(navigate(page)),
|
navigate: (page) => dispatch(navigate(page)),
|
||||||
|
|||||||
@@ -1,32 +1,35 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
import { Text, View } from 'react-native'
|
|
||||||
|
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { LocalDate } from 'js-joda'
|
|
||||||
|
|
||||||
import styles from './styles'
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import cycleModule from '../../lib/cycle'
|
import cycleModule from '../../lib/cycle'
|
||||||
|
import { getOrdinalSuffix } from '../helpers/home'
|
||||||
|
import { Containers, Typography, Sizes } from '../../styles'
|
||||||
|
|
||||||
const CycleDayLabel = ({ height, date }) => {
|
const CycleDayLabel = ({ height, date }) => {
|
||||||
const { label } = styles.column
|
|
||||||
const dayDate = LocalDate.parse(date)
|
|
||||||
const cycleDayNumber = cycleModule().getCycleDayNumber(date)
|
const cycleDayNumber = cycleModule().getCycleDayNumber(date)
|
||||||
|
const cycleDayLabel = cycleDayNumber ? cycleDayNumber : ' '
|
||||||
|
|
||||||
const isFirstDayOfMonth = dayDate.dayOfMonth() === 1
|
const momentDate = moment(date)
|
||||||
const dateFormatting = isFirstDayOfMonth ? 'MMM' : 'Do'
|
const dayOfMonth = momentDate.date()
|
||||||
const shortDate = moment(date, "YYYY-MM-DD").format(dateFormatting)
|
const isFirstDayOfMonth = dayOfMonth === 1
|
||||||
const boldDateLabel = isFirstDayOfMonth ? {fontWeight: 'bold'} : {}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.chartLegend, { height }]}>
|
<View style={[styles.container, { height }]}>
|
||||||
<Text style={label.number}>
|
<AppText style={styles.textBold}>{cycleDayLabel}</AppText>
|
||||||
{cycleDayNumber ? cycleDayNumber : ' '}
|
<View style={styles.dateLabel}>
|
||||||
</Text>
|
<AppText style={styles.text}>
|
||||||
<Text style={[label.date, boldDateLabel]}>
|
{isFirstDayOfMonth ? momentDate.format('MMM') : dayOfMonth}
|
||||||
{shortDate}
|
</AppText>
|
||||||
</Text>
|
{!isFirstDayOfMonth &&
|
||||||
|
<AppText style={styles.textLight}>
|
||||||
|
{getOrdinalSuffix(dayOfMonth)}
|
||||||
|
</AppText>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -36,4 +39,30 @@ CycleDayLabel.propTypes = {
|
|||||||
date: PropTypes.string,
|
date: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
left: 4,
|
||||||
|
},
|
||||||
|
containerRow: {
|
||||||
|
...Containers.rowContainer
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
...Typography.label,
|
||||||
|
fontSize: Sizes.small,
|
||||||
|
},
|
||||||
|
textBold: {
|
||||||
|
...Typography.labelBold
|
||||||
|
},
|
||||||
|
textLight: {
|
||||||
|
...Typography.labelLight,
|
||||||
|
},
|
||||||
|
dateLabel: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
alignItems: 'center',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default CycleDayLabel
|
export default CycleDayLabel
|
||||||
|
|||||||
@@ -92,21 +92,6 @@ class DayColumn extends Component {
|
|||||||
activeOpacity={1}
|
activeOpacity={1}
|
||||||
>
|
>
|
||||||
|
|
||||||
{ symptomRowSymptoms.map(symptom => {
|
|
||||||
const hasSymptomData = this.data.hasOwnProperty(symptom)
|
|
||||||
return (
|
|
||||||
<SymptomCell
|
|
||||||
key={symptom}
|
|
||||||
symptom={symptom}
|
|
||||||
symptomValue={hasSymptomData && this.data[symptom]}
|
|
||||||
isSymptomDataComplete={
|
|
||||||
hasSymptomData && isSymptomDataComplete(symptom, dateString)
|
|
||||||
}
|
|
||||||
height={symptomHeight}
|
|
||||||
/>)
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
|
|
||||||
{shouldShowTemperatureColumn && <TemperatureColumn
|
{shouldShowTemperatureColumn && <TemperatureColumn
|
||||||
horizontalLinePosition={this.fhmAndLtl.drawLtlAt}
|
horizontalLinePosition={this.fhmAndLtl.drawLtlAt}
|
||||||
isVerticalLine={this.fhmAndLtl.drawFhmLine}
|
isVerticalLine={this.fhmAndLtl.drawFhmLine}
|
||||||
@@ -119,6 +104,22 @@ class DayColumn extends Component {
|
|||||||
date={dateString}
|
date={dateString}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{ symptomRowSymptoms.map((symptom, i) => {
|
||||||
|
const hasSymptomData = this.data.hasOwnProperty(symptom)
|
||||||
|
return (
|
||||||
|
<SymptomCell
|
||||||
|
index={i}
|
||||||
|
key={symptom}
|
||||||
|
symptom={symptom}
|
||||||
|
symptomValue={hasSymptomData && this.data[symptom]}
|
||||||
|
isSymptomDataComplete={
|
||||||
|
hasSymptomData && isSymptomDataComplete(symptom, dateString)
|
||||||
|
}
|
||||||
|
height={symptomHeight}
|
||||||
|
/>)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,14 @@ import React, { Component } from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Path, Shape } from 'react-native/Libraries/ART/ReactNativeART'
|
import { Path, Shape } from 'react-native/Libraries/ART/ReactNativeART'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors } from '../../styles'
|
||||||
import config from '../../config'
|
|
||||||
|
import {
|
||||||
|
CHART_COLUMN_WIDTH,
|
||||||
|
CHART_COLUMN_MIDDLE,
|
||||||
|
CHART_DOT_RADIUS,
|
||||||
|
CHART_STROKE_WIDTH
|
||||||
|
} from '../../config'
|
||||||
|
|
||||||
export default class DotAndLine extends Component {
|
export default class DotAndLine extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -20,48 +26,62 @@ export default class DotAndLine extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const y = this.props.y
|
const {
|
||||||
const exclude = this.props.exclude
|
exclude,
|
||||||
let lineToRight
|
leftTemperatureExclude,
|
||||||
let lineToLeft
|
leftY,
|
||||||
|
rightTemperatureExclude,
|
||||||
|
rightY,
|
||||||
|
y
|
||||||
|
} = this.props
|
||||||
|
let excludeLeftLine, excludeRightLine, lineLeft, lineRight
|
||||||
|
|
||||||
if (this.props.leftY) {
|
if (leftY) {
|
||||||
const middleY = ((this.props.leftY - y) / 2) + y
|
const middleY = ((leftY - y) / 2) + y
|
||||||
const excludedLine = this.props.leftTemperatureExclude || exclude
|
excludeLeftLine = leftTemperatureExclude || exclude
|
||||||
lineToLeft = makeLine(y, middleY, 0, excludedLine)
|
lineLeft = new Path()
|
||||||
|
.moveTo(CHART_COLUMN_MIDDLE, y)
|
||||||
|
.lineTo(0, middleY)
|
||||||
}
|
}
|
||||||
if (this.props.rightY) {
|
if (rightY) {
|
||||||
const middleY = ((y - this.props.rightY) / 2) + this.props.rightY
|
const middleY = ((y - rightY) / 2) + rightY
|
||||||
const excludedLine = this.props.rightTemperatureExclude || exclude
|
excludeRightLine = rightTemperatureExclude || exclude
|
||||||
lineToRight = makeLine(y, middleY, config.columnWidth, excludedLine)
|
lineRight = new Path()
|
||||||
|
.moveTo(CHART_COLUMN_MIDDLE, y)
|
||||||
|
.lineTo(CHART_COLUMN_WIDTH, middleY)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dotStyle = exclude ? styles.curveDotsExcluded : styles.curveDots
|
const dot = new Path().moveTo(CHART_COLUMN_MIDDLE , y - CHART_DOT_RADIUS)
|
||||||
const radius = dotStyle.r
|
.arc(0, CHART_DOT_RADIUS * 2, CHART_DOT_RADIUS)
|
||||||
const dot = (
|
.arc(0, CHART_DOT_RADIUS * -2, CHART_DOT_RADIUS)
|
||||||
<Shape
|
const dotColor = exclude ? Colors.turquoise : Colors.turquoiseDark
|
||||||
d={new Path()
|
const lineColorLeft = excludeLeftLine ?
|
||||||
.moveTo(config.columnMiddle, y - radius)
|
Colors.turquoise : Colors.turquoiseDark
|
||||||
.arc(0, radius * 2, radius)
|
const lineColorRight = excludeRightLine ?
|
||||||
.arc(0, radius * -2, radius)
|
Colors.turquoise : Colors.turquoiseDark
|
||||||
}
|
|
||||||
fill={dotStyle.fill}
|
return(
|
||||||
key='dot'
|
<React.Fragment>
|
||||||
/>
|
<Shape
|
||||||
|
d={lineLeft}
|
||||||
|
stroke={lineColorLeft}
|
||||||
|
strokeWidth={CHART_STROKE_WIDTH}
|
||||||
|
key={y}
|
||||||
|
/>
|
||||||
|
<Shape
|
||||||
|
d={lineRight}
|
||||||
|
stroke={lineColorRight}
|
||||||
|
strokeWidth={CHART_STROKE_WIDTH}
|
||||||
|
key={y + CHART_DOT_RADIUS}
|
||||||
|
/>
|
||||||
|
<Shape
|
||||||
|
d={dot}
|
||||||
|
stroke={dotColor}
|
||||||
|
strokeWidth={CHART_STROKE_WIDTH}
|
||||||
|
fill="white"
|
||||||
|
key='dot'
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
return [lineToLeft, lineToRight, dot]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeLine(currY, middleY, x, excludeLine) {
|
|
||||||
const lineStyle = excludeLine ? styles.curveExcluded : styles.curve
|
|
||||||
|
|
||||||
return <Shape
|
|
||||||
stroke={lineStyle.stroke}
|
|
||||||
d={new Path()
|
|
||||||
.moveTo(config.columnMiddle, currY)
|
|
||||||
.lineTo(x, middleY)
|
|
||||||
}
|
|
||||||
key={x.toString()}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,33 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import { getTickPositions } from '../helpers/chart'
|
import { getTickPositions } from '../helpers/chart'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors } from '../../styles'
|
||||||
|
import { CHART_GRID_LINE_HORIZONTAL_WIDTH, CHART_YAXIS_WIDTH } from '../../config'
|
||||||
|
|
||||||
const HorizontalGrid = ({ height, startPosition }) => {
|
const HorizontalGrid = ({ height }) => {
|
||||||
return getTickPositions(height).map(tick => {
|
return getTickPositions(height).map(tick => {
|
||||||
return (
|
return (
|
||||||
<View
|
<View key={tick} top={tick} {...styles.line}/>
|
||||||
top={startPosition + tick}
|
|
||||||
{...styles.horizontalGrid}
|
|
||||||
key={tick}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalGrid.propTypes = {
|
HorizontalGrid.propTypes = {
|
||||||
height: PropTypes.number,
|
height: PropTypes.number
|
||||||
startPosition: PropTypes.number,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
line: {
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderBottomColor: Colors.grey,
|
||||||
|
borderBottomWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
|
||||||
|
left: CHART_YAXIS_WIDTH,
|
||||||
|
position:'absolute',
|
||||||
|
right: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default HorizontalGrid
|
export default HorizontalGrid
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import { getCycleStatusForDay } from '../../lib/sympto-adapter'
|
|
||||||
import { normalizeToScale } from '../helpers/chart'
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
const cycle = {
|
|
||||||
status: null
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCurrentCycle(dateString) {
|
|
||||||
// for the NFP lines, we don't care about potentially extending the
|
|
||||||
// preOvu phase, so we don't include all earlier cycles, as that is
|
|
||||||
// an expensive db operation at the moment
|
|
||||||
cycle.status = getCycleStatusForDay(
|
|
||||||
dateString, { excludeEarlierCycles: true }
|
|
||||||
)
|
|
||||||
if(!cycle.status) {
|
|
||||||
cycle.noMoreCycles = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (cycle.status.phases.preOvulatory) {
|
|
||||||
cycle.startDate = cycle.status.phases.preOvulatory.start.date
|
|
||||||
} else {
|
|
||||||
cycle.startDate = cycle.status.phases.periOvulatory.start.date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dateIsInPeriOrPostPhase(dateString) {
|
|
||||||
return (
|
|
||||||
dateString >= cycle.status.phases.periOvulatory.start.date
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function precededByAnotherTempValue(dateString) {
|
|
||||||
return (
|
|
||||||
// we are only interested in days that have a preceding
|
|
||||||
// temp
|
|
||||||
Object.keys(cycle.status.phases).some(phaseName => {
|
|
||||||
return cycle.status.phases[phaseName].cycleDays.some(day => {
|
|
||||||
return day.temperature && day.date < dateString
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// and also a following temp, so we don't draw the line
|
|
||||||
// longer than necessary
|
|
||||||
&&
|
|
||||||
cycle.status.phases.postOvulatory.cycleDays.some(day => {
|
|
||||||
return day.temperature && day.date > dateString
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInTempMeasuringPhase(temperature, dateString) {
|
|
||||||
return (
|
|
||||||
temperature || precededByAnotherTempValue(dateString)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(dateString, temperature, columnHeight) {
|
|
||||||
const ret = {
|
|
||||||
drawLtlAt: null,
|
|
||||||
drawFhmLine: false
|
|
||||||
}
|
|
||||||
if (!cycle.status && !cycle.noMoreCycles) updateCurrentCycle(dateString)
|
|
||||||
if (cycle.noMoreCycles) return ret
|
|
||||||
|
|
||||||
if (dateString < cycle.startDate) updateCurrentCycle(dateString)
|
|
||||||
if (cycle.noMoreCycles) return ret
|
|
||||||
|
|
||||||
const tempShift = cycle.status.temperatureShift
|
|
||||||
|
|
||||||
if (tempShift) {
|
|
||||||
if (tempShift.firstHighMeasurementDay.date === dateString) {
|
|
||||||
ret.drawFhmLine = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
dateIsInPeriOrPostPhase(dateString) &&
|
|
||||||
isInTempMeasuringPhase(temperature, dateString)
|
|
||||||
) {
|
|
||||||
ret.drawLtlAt = normalizeToScale(tempShift.ltl, columnHeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +1,44 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
|
||||||
|
|
||||||
import AppText from '../app-text'
|
import AppText from '../common/app-text'
|
||||||
import SettingsButton from '../settings/shared/settings-button'
|
import Button from '../common/button'
|
||||||
|
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { navigate } from '../../slices/navigation'
|
||||||
|
|
||||||
|
import { Containers } from '../../styles'
|
||||||
import { shared } from '../../i18n/en/labels'
|
import { shared } from '../../i18n/en/labels'
|
||||||
import styles from './styles'
|
|
||||||
|
|
||||||
const NoData = ({ navigate }) => {
|
const NoData = ({ navigate }) => {
|
||||||
return (
|
return (
|
||||||
<View flex={1}>
|
<View style={styles.container}>
|
||||||
<View style={styles.centerItem}>
|
<AppText>{shared.noDataWarning}</AppText>
|
||||||
<AppText>{shared.noDataWarning}</AppText>
|
<Button isCTA onPress={() => {navigate('CycleDay')}}>
|
||||||
<SettingsButton
|
{shared.noDataButtonText}
|
||||||
onPress={() => {navigate('CycleDay')}}
|
</Button>
|
||||||
style={{marginHorizontal: 40}}
|
|
||||||
>
|
|
||||||
{shared.noDataButtonText}
|
|
||||||
</SettingsButton>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
NoData.propTypes = {
|
NoData.propTypes = {
|
||||||
navigate: PropTypes.func,
|
navigate: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NoData
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.centerItems
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return({
|
||||||
|
navigate: (page) => dispatch(navigate(page)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(NoData)
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
import config from '../../config'
|
|
||||||
import { shadesOfRed, cycleDayColor } from '../../styles/index'
|
|
||||||
|
|
||||||
const colorTemperature = '#765285'
|
|
||||||
const colorTemperatureLight = '#a67fb5'
|
|
||||||
export const dotRadius = 5
|
|
||||||
const lineWidth = 1.5
|
|
||||||
const colorLtl = '#feb47b'
|
|
||||||
const gridColor = '#d3d3d3'
|
|
||||||
const gridLineWidthVertical = 0.6
|
|
||||||
const gridLineWidthHorizontal = 0.3
|
|
||||||
const numberLabelFontSize = 13
|
|
||||||
|
|
||||||
const redColor = '#c3000d'
|
|
||||||
const violetColor = '#6a7b98'
|
|
||||||
const shadesOfViolet = ['#e3e7ed', '#c8cfdc', '#acb8cb', '#91a0ba', '#7689a9', violetColor] // light to dark
|
|
||||||
const yellowColor = '#dbb40c'
|
|
||||||
const shadesOfYellow = ['#f0e19d', '#e9d26d', '#e2c33c', yellowColor] // light to dark
|
|
||||||
const magentaColor = '#6f2565'
|
|
||||||
const shadesOfMagenta = ['#a87ca2', '#8b5083', magentaColor] // light to dark
|
|
||||||
const pinkColor = '#9e346c'
|
|
||||||
const shadesOfPink = ['#c485a6', '#b15c89', pinkColor] // light to dark
|
|
||||||
const lightGreenColor = '#bccd67'
|
|
||||||
const orangeColor = '#bc6642'
|
|
||||||
const mintColor = '#6ca299'
|
|
||||||
|
|
||||||
const styles = {
|
|
||||||
container: { flex: 1 },
|
|
||||||
chartContainer: { flexDirection: 'column' },
|
|
||||||
chartArea: { flexDirection: 'row' },
|
|
||||||
centerItem: {
|
|
||||||
flex:1,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
marginHorizontal: 25,
|
|
||||||
},
|
|
||||||
curve: {
|
|
||||||
stroke: colorTemperature,
|
|
||||||
strokeWidth: lineWidth,
|
|
||||||
},
|
|
||||||
curveExcluded: {
|
|
||||||
stroke: colorTemperatureLight,
|
|
||||||
strokeWidth: lineWidth
|
|
||||||
},
|
|
||||||
curveDots: {
|
|
||||||
fill: colorTemperature,
|
|
||||||
r: dotRadius
|
|
||||||
},
|
|
||||||
curveDotsExcluded: {
|
|
||||||
fill: colorTemperatureLight,
|
|
||||||
r: dotRadius
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
label: {
|
|
||||||
date: {
|
|
||||||
color: 'grey',
|
|
||||||
fontSize: 9,
|
|
||||||
fontWeight: '100',
|
|
||||||
textAlign: 'center',
|
|
||||||
paddingTop: 2.5
|
|
||||||
},
|
|
||||||
number: {
|
|
||||||
color: cycleDayColor,
|
|
||||||
fontSize: numberLabelFontSize,
|
|
||||||
textAlign: 'center',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stroke: {
|
|
||||||
color: gridColor,
|
|
||||||
width: gridLineWidthVertical,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
symptomDot: {
|
|
||||||
width: 12,
|
|
||||||
height: 12,
|
|
||||||
borderRadius: 50,
|
|
||||||
},
|
|
||||||
iconColors: {
|
|
||||||
'bleeding': {
|
|
||||||
color: redColor,
|
|
||||||
shades: shadesOfRed,
|
|
||||||
},
|
|
||||||
'mucus': {
|
|
||||||
color: violetColor,
|
|
||||||
shades: shadesOfViolet,
|
|
||||||
},
|
|
||||||
'cervix': {
|
|
||||||
color: yellowColor,
|
|
||||||
shades: shadesOfYellow,
|
|
||||||
},
|
|
||||||
'sex': {
|
|
||||||
color: magentaColor,
|
|
||||||
shades: shadesOfMagenta,
|
|
||||||
},
|
|
||||||
'desire': {
|
|
||||||
color: pinkColor,
|
|
||||||
shades: shadesOfPink,
|
|
||||||
},
|
|
||||||
'pain': {
|
|
||||||
color: lightGreenColor,
|
|
||||||
shades: [lightGreenColor],
|
|
||||||
},
|
|
||||||
'mood': {
|
|
||||||
color: orangeColor,
|
|
||||||
shades: [orangeColor],
|
|
||||||
},
|
|
||||||
'note': {
|
|
||||||
color: mintColor,
|
|
||||||
shades: [mintColor],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
width: 27,
|
|
||||||
borderRightWidth: 1,
|
|
||||||
borderColor: 'lightgrey',
|
|
||||||
borderStyle: 'solid'
|
|
||||||
},
|
|
||||||
yAxisLabels: {
|
|
||||||
tempScale: {
|
|
||||||
position: 'absolute',
|
|
||||||
right: 2,
|
|
||||||
color: 'grey',
|
|
||||||
fontSize: 9,
|
|
||||||
textAlign: 'left'
|
|
||||||
},
|
|
||||||
cycleDayLabel: {
|
|
||||||
textAlign: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
fontSize: Math.ceil(numberLabelFontSize / 2)
|
|
||||||
},
|
|
||||||
dateLabel: {
|
|
||||||
textAlign: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
color: 'grey',
|
|
||||||
fontSize: 9,
|
|
||||||
fontWeight: '100',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
symptomIcon: {
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
chartLegend: {
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
},
|
|
||||||
boldTick: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
fontSize: 11,
|
|
||||||
},
|
|
||||||
horizontalGrid: {
|
|
||||||
position:'absolute',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderBottomColor: gridColor,
|
|
||||||
borderBottomWidth: gridLineWidthHorizontal,
|
|
||||||
width: '100%',
|
|
||||||
left: config.columnWidth
|
|
||||||
},
|
|
||||||
nfpLine: {
|
|
||||||
stroke: colorLtl,
|
|
||||||
strokeWidth: lineWidth,
|
|
||||||
},
|
|
||||||
symptomRow: {
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
loadingMore: {
|
|
||||||
height: '100%',
|
|
||||||
backgroundColor: 'lightgrey',
|
|
||||||
justifyContent: 'center'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default styles
|
|
||||||
@@ -1,47 +1,49 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors, Containers } from '../../styles'
|
||||||
import config from '../../config'
|
import {
|
||||||
|
CHART_COLUMN_WIDTH,
|
||||||
|
CHART_DOT_RADIUS,
|
||||||
|
CHART_GRID_LINE_HORIZONTAL_WIDTH
|
||||||
|
} from '../../config'
|
||||||
|
|
||||||
const SymptomCell = ({
|
const SymptomCell = ({
|
||||||
height,
|
height,
|
||||||
|
index,
|
||||||
symptom,
|
symptom,
|
||||||
symptomValue,
|
symptomValue,
|
||||||
isSymptomDataComplete
|
isSymptomDataComplete
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const shouldDrawDot = symptomValue !== false
|
const shouldDrawDot = symptomValue !== false
|
||||||
const styleParent = [styles.symptomRow, { height, width: config.columnWidth }]
|
const styleCell = index !== 0
|
||||||
let styleChild
|
? [styles.cell, { height, width: CHART_COLUMN_WIDTH }]
|
||||||
|
: [styles.cell, { height, width: CHART_COLUMN_WIDTH }, styles.topBorder]
|
||||||
|
let styleDot
|
||||||
|
|
||||||
if (shouldDrawDot) {
|
if (shouldDrawDot) {
|
||||||
const styleSymptom = styles.iconColors[symptom]
|
const styleSymptom = Colors.iconColors[symptom]
|
||||||
const symptomColor = styleSymptom.shades[symptomValue]
|
const symptomColor = styleSymptom.shades[symptomValue]
|
||||||
|
|
||||||
const isMucusOrCervix = (symptom === 'mucus') || (symptom === 'cervix')
|
const isMucusOrCervix = (symptom === 'mucus') || (symptom === 'cervix')
|
||||||
|
|
||||||
const backgroundColor = (isMucusOrCervix && !isSymptomDataComplete) ?
|
const backgroundColor = (isMucusOrCervix && !isSymptomDataComplete) ?
|
||||||
'white' : symptomColor
|
'white' : symptomColor
|
||||||
const borderWidth = (isMucusOrCervix && !isSymptomDataComplete) ? 2 : 0
|
const borderWidth = (isMucusOrCervix && !isSymptomDataComplete) ? 2 : 0
|
||||||
const borderColor = symptomColor
|
const borderColor = symptomColor
|
||||||
styleChild = [styles.symptomDot, {
|
styleDot = [styles.dot, { backgroundColor, borderColor, borderWidth }]
|
||||||
backgroundColor,
|
|
||||||
borderColor,
|
|
||||||
borderWidth
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styleParent} key={symptom}>
|
<View style={styleCell} key={symptom}>
|
||||||
{shouldDrawDot && <View style={styleChild} />}
|
{shouldDrawDot && <View style={styleDot} />}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SymptomCell.propTypes = {
|
SymptomCell.propTypes = {
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
|
index: PropTypes.number.isRequired,
|
||||||
symptom: PropTypes.string,
|
symptom: PropTypes.string,
|
||||||
symptomValue: PropTypes.oneOfType([
|
symptomValue: PropTypes.oneOfType([
|
||||||
PropTypes.bool,
|
PropTypes.bool,
|
||||||
@@ -50,4 +52,23 @@ SymptomCell.propTypes = {
|
|||||||
isSymptomDataComplete: PropTypes.bool,
|
isSymptomDataComplete: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
cell: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderBottomColor: Colors.grey,
|
||||||
|
borderBottomWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
|
||||||
|
borderLeftColor: Colors.grey,
|
||||||
|
borderLeftWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
|
||||||
|
...Containers.centerItems
|
||||||
|
},
|
||||||
|
topBorder: {
|
||||||
|
borderTopColor: Colors.grey,
|
||||||
|
borderTopWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
|
||||||
|
},
|
||||||
|
dot: {
|
||||||
|
width: CHART_DOT_RADIUS * 2,
|
||||||
|
height: CHART_DOT_RADIUS * 2,
|
||||||
|
borderRadius: 50
|
||||||
|
}
|
||||||
|
})
|
||||||
export default SymptomCell
|
export default SymptomCell
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet , View } from 'react-native'
|
||||||
|
|
||||||
import DripIcon from '../../assets/drip-icons'
|
import DripIcon from '../../assets/drip-icons'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Colors, Containers } from '../../styles'
|
||||||
|
import { CHART_YAXIS_WIDTH, CHART_ICON_SIZE } from '../../config'
|
||||||
|
|
||||||
const SymptomIcon = ({ symptom, height }) => {
|
const SymptomIcon = ({ symptom, height }) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.symptomIcon} width={styles.yAxis.width} height={height}>
|
<View style={styles.container} width={CHART_YAXIS_WIDTH} height={height}>
|
||||||
<DripIcon
|
<DripIcon
|
||||||
size={16}
|
size={CHART_ICON_SIZE}
|
||||||
name={`drip-icon-${symptom}`}
|
name={`drip-icon-${symptom}`}
|
||||||
color={styles.iconColors[symptom].color}
|
color={Colors.iconColors[symptom].color}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
@@ -23,4 +24,10 @@ SymptomIcon.propTypes = {
|
|||||||
symptom: PropTypes.string,
|
symptom: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.centerItems
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default SymptomIcon
|
export default SymptomIcon
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet } from 'react-native'
|
||||||
|
|
||||||
import { Surface , Path } from 'react-native/Libraries/ART/ReactNativeART'
|
import { Surface , Path } from 'react-native/Libraries/ART/ReactNativeART'
|
||||||
|
|
||||||
import ChartLine from './chart-line'
|
import ChartLine from './chart-line'
|
||||||
import DotAndLine from './dot-and-line'
|
import DotAndLine from './dot-and-line'
|
||||||
|
|
||||||
import styles from './styles'
|
import { CHART_COLUMN_WIDTH, CHART_STROKE_WIDTH } from '../../config'
|
||||||
import config from '../../config'
|
|
||||||
|
|
||||||
const TemperatureColumn = ({
|
const TemperatureColumn = ({
|
||||||
horizontalLinePosition,
|
horizontalLinePosition,
|
||||||
@@ -15,20 +15,21 @@ const TemperatureColumn = ({
|
|||||||
data,
|
data,
|
||||||
columnHeight
|
columnHeight
|
||||||
}) => {
|
}) => {
|
||||||
|
const x = CHART_STROKE_WIDTH / 2
|
||||||
const x = styles.nfpLine.strokeWidth / 2
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Surface width={config.columnWidth} height={columnHeight}>
|
<Surface
|
||||||
|
width={CHART_COLUMN_WIDTH}
|
||||||
|
height={columnHeight}
|
||||||
|
style={styles.container}
|
||||||
|
>
|
||||||
|
|
||||||
<ChartLine
|
<ChartLine path={new Path().lineTo(0, columnHeight)}/>
|
||||||
path={new Path().lineTo(0, columnHeight)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{horizontalLinePosition && <ChartLine
|
{horizontalLinePosition && <ChartLine
|
||||||
path={new Path()
|
path={new Path()
|
||||||
.moveTo(0, horizontalLinePosition)
|
.moveTo(0, horizontalLinePosition)
|
||||||
.lineTo(config.columnWidth, horizontalLinePosition)
|
.lineTo(CHART_COLUMN_WIDTH, horizontalLinePosition)
|
||||||
}
|
}
|
||||||
isNfpLine={true}
|
isNfpLine={true}
|
||||||
key='ltl'
|
key='ltl'
|
||||||
@@ -40,7 +41,7 @@ const TemperatureColumn = ({
|
|||||||
key='fhm'
|
key='fhm'
|
||||||
/>}
|
/>}
|
||||||
|
|
||||||
{data && data.y && <DotAndLine
|
{data && typeof(data.y) !== 'undefined' && <DotAndLine
|
||||||
y={data.y}
|
y={data.y}
|
||||||
exclude={data.temperatureExclude}
|
exclude={data.temperatureExclude}
|
||||||
rightY={data.rightY}
|
rightY={data.rightY}
|
||||||
@@ -61,4 +62,10 @@ TemperatureColumn.propTypes = {
|
|||||||
columnHeight: PropTypes.number,
|
columnHeight: PropTypes.number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default TemperatureColumn
|
export default TemperatureColumn
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import Tick from './tick'
|
import Tick from './tick'
|
||||||
|
|
||||||
import { getTickList } from '../helpers/chart'
|
import { getTickList } from '../helpers/chart'
|
||||||
|
|
||||||
import styles from './styles'
|
|
||||||
|
|
||||||
const TickList = ({ height }) => {
|
const TickList = ({ height }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.yAxis, { height }]}>{
|
<View style={[styles.container, height]}>
|
||||||
getTickList(height)
|
{
|
||||||
.map(({ label, position, isBold, shouldShowLabel}) => {
|
getTickList(height)
|
||||||
return (
|
.map(({ isBold, label, position, shouldShowLabel, tickHeight}) => {
|
||||||
<Tick
|
return (
|
||||||
key={label}
|
<Tick
|
||||||
yPosition={position}
|
height={tickHeight}
|
||||||
isBold={isBold}
|
isBold={isBold}
|
||||||
shouldShowLabel={shouldShowLabel}
|
key={label}
|
||||||
label={label}
|
label={label}
|
||||||
/>
|
shouldShowLabel={shouldShowLabel}
|
||||||
)
|
yPosition={position}
|
||||||
})
|
/>
|
||||||
}</View>
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,4 +33,10 @@ TickList.propTypes = {
|
|||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default TickList
|
export default TickList
|
||||||
|
|||||||
@@ -1,29 +1,53 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import AppText from '../app-text'
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import styles from './styles'
|
import { Sizes } from '../../styles'
|
||||||
|
|
||||||
const Tick = ({ yPosition, isBold, shouldShowLabel, label }) => {
|
const Tick = ({ yPosition, height, isBold, shouldShowLabel, label }) => {
|
||||||
// this eyeballing is sadly necessary because RN does not
|
const top = yPosition - height / 2
|
||||||
// support percentage values for transforms, which we'd need
|
const containerStyle = [ styles.container, { flexBasis: height, height, top }]
|
||||||
// to reliably place the label vertically centered to the grid
|
const textStyle = isBold ? styles.textBold : styles.textNormal
|
||||||
const topPosition = yPosition - 8
|
|
||||||
const style = [
|
|
||||||
styles.yAxisLabels.tempScale,
|
|
||||||
{top: topPosition},
|
|
||||||
isBold && styles.boldTick
|
|
||||||
]
|
|
||||||
|
|
||||||
return <AppText style={style}>{shouldShowLabel && label}</AppText>
|
return(
|
||||||
|
<View style={containerStyle}>
|
||||||
|
<AppText style={textStyle}>{shouldShowLabel && label}</AppText>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Tick.propTypes = {
|
Tick.propTypes = {
|
||||||
yPosition: PropTypes.number,
|
yPosition: PropTypes.number,
|
||||||
|
height: PropTypes.number.isRequired,
|
||||||
isBold: PropTypes.bool,
|
isBold: PropTypes.bool,
|
||||||
shouldShowLabel: PropTypes.bool,
|
shouldShowLabel: PropTypes.bool,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const text = {
|
||||||
|
lineHeight: Sizes.base,
|
||||||
|
right: 4,
|
||||||
|
textAlign: 'right'
|
||||||
|
}
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
width: 40
|
||||||
|
},
|
||||||
|
textBold: {
|
||||||
|
fontSize: Sizes.base,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
...text
|
||||||
|
},
|
||||||
|
textNormal: {
|
||||||
|
fontSize: Sizes.small,
|
||||||
|
...text
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default Tick
|
export default Tick
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Image, StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
import CloseIcon from '../common/close-icon'
|
||||||
|
|
||||||
|
import { Containers, Spacing } from '../../styles'
|
||||||
|
import { chart } from '../../i18n/en/labels'
|
||||||
|
|
||||||
|
const image = require('../../assets/swipe.png')
|
||||||
|
|
||||||
|
const Tutorial = ({ onClose }) => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Image resizeMode='contain' source={image} style={styles.image} />
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<AppText>{chart.tutorial}</AppText>
|
||||||
|
</View>
|
||||||
|
<CloseIcon onClose={onClose} />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Tutorial.propTypes = {
|
||||||
|
onClose: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.rowContainer,
|
||||||
|
padding: Spacing.large
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
height: 40
|
||||||
|
},
|
||||||
|
textContainer: {
|
||||||
|
width: '70%'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Tutorial
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
import SymptomIcon from './symptom-icon'
|
import SymptomIcon from './symptom-icon'
|
||||||
import TickList from './tick-list'
|
import TickList from './tick-list'
|
||||||
import ChartLegend from './chart-legend'
|
import ChartLegend from './chart-legend'
|
||||||
|
|
||||||
import styles from './styles'
|
import { CHART_YAXIS_WIDTH } from '../../config'
|
||||||
|
|
||||||
const YAxis = ({
|
const YAxis = ({
|
||||||
height,
|
height,
|
||||||
@@ -19,6 +19,8 @@ const YAxis = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
|
{shouldShowTemperatureColumn && <TickList height={height} />}
|
||||||
|
<ChartLegend height={xAxisHeight} />
|
||||||
<View style={[styles.yAxis, {height: symptomsSectionHeight}]}>
|
<View style={[styles.yAxis, {height: symptomsSectionHeight}]}>
|
||||||
{symptomsToDisplay.map(symptom => (
|
{symptomsToDisplay.map(symptom => (
|
||||||
<SymptomIcon
|
<SymptomIcon
|
||||||
@@ -29,8 +31,6 @@ const YAxis = ({
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{shouldShowTemperatureColumn && <TickList height={height} />}
|
|
||||||
<ChartLegend xAxisHeight={xAxisHeight} />
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -43,4 +43,10 @@ YAxis.propTypes = {
|
|||||||
xAxisHeight: PropTypes.number.isRequired
|
xAxisHeight: PropTypes.number.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
yAxis: {
|
||||||
|
width: CHART_YAXIS_WIDTH
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default YAxis
|
export default YAxis
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet } from 'react-native'
|
||||||
|
import Icon from 'react-native-vector-icons/Entypo'
|
||||||
|
|
||||||
|
import { Sizes } from '../../styles'
|
||||||
|
|
||||||
|
const AppIcon = ({ color, name, style, ...props }) => {
|
||||||
|
const iconStyle = [styles.icon, style, { color }]
|
||||||
|
|
||||||
|
return <Icon name={name} style={iconStyle} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
AppIcon.propTypes = {
|
||||||
|
color: PropTypes.string,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
style: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
AppIcon.defaultProps = {
|
||||||
|
color: 'black'
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
icon: {
|
||||||
|
fontSize: Sizes.subtitle
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppIcon
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppText from './app-text'
|
||||||
|
|
||||||
|
import { Containers } from '../../styles'
|
||||||
|
|
||||||
|
import { shared } from '../../i18n/en/labels'
|
||||||
|
|
||||||
|
const AppLoadingView = () => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<AppText>{shared.loading}</AppText>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.centerItems
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppLoadingView
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { ScrollView, StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
|
import { Colors, Typography } from '../../styles'
|
||||||
|
|
||||||
|
const AppPage = ({
|
||||||
|
children,
|
||||||
|
contentContainerStyle,
|
||||||
|
scrollViewStyle,
|
||||||
|
title,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return(
|
||||||
|
<View style={styles.container}>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={[styles.scrollView, contentContainerStyle]}
|
||||||
|
style={scrollViewStyle}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{title && <AppText style={styles.title}>{title}</AppText>}
|
||||||
|
{children}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppPage.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
contentContainerStyle: PropTypes.object,
|
||||||
|
scrollViewStyle: PropTypes.object,
|
||||||
|
title: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
backgroundColor: Colors.turquoiseLight,
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
scrollView: {
|
||||||
|
backgroundColor: Colors.turquoiseLight,
|
||||||
|
flexGrow: 1
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
...Typography.title
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppPage
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { StyleSheet, Switch, View } from 'react-native'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import AppText from './app-text'
|
||||||
|
|
||||||
|
import { Containers } from '../../styles'
|
||||||
|
|
||||||
|
const AppSwitch = ({ onToggle, text, value }) => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<AppText>{text}</AppText>
|
||||||
|
</View>
|
||||||
|
<Switch onValueChange={onToggle} style={styles.switch} value={value} />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSwitch.propTypes = {
|
||||||
|
onToggle: PropTypes.func.isRequired,
|
||||||
|
text: PropTypes.string,
|
||||||
|
value: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
...Containers.rowContainer
|
||||||
|
},
|
||||||
|
switch: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
textContainer: {
|
||||||
|
flex: 4,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppSwitch
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { KeyboardAvoidingView, StyleSheet, TextInput } from 'react-native'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import { Colors, Spacing, Typography } from '../../styles'
|
||||||
|
|
||||||
|
const AppTextInput = ({ style, ...props }) => {
|
||||||
|
return (
|
||||||
|
<KeyboardAvoidingView
|
||||||
|
behavior="padding"
|
||||||
|
keyboardVerticalOffset={300}
|
||||||
|
>
|
||||||
|
<TextInput style={[styles.input, style]} {...props} />
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppTextInput.propTypes = {
|
||||||
|
style: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
input: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderColor: Colors.grey,
|
||||||
|
borderRadius: 5,
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
color: Colors.greyDark,
|
||||||
|
marginTop: Spacing.base,
|
||||||
|
minWidth: '80%',
|
||||||
|
paddingHorizontal: Spacing.base,
|
||||||
|
...Typography.mainText
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppTextInput
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, Text } from 'react-native'
|
||||||
|
|
||||||
|
import Link from './link'
|
||||||
|
|
||||||
|
import { Colors, Typography } from '../../styles'
|
||||||
|
|
||||||
|
const AppText = ({ children, linkStyle, style, ...props }) => {
|
||||||
|
// we parse for links in case the text contains any
|
||||||
|
return (
|
||||||
|
<Link style={linkStyle}>
|
||||||
|
<Text style={[styles.text, style]} {...props}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppText.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
linkStyle: PropTypes.object,
|
||||||
|
style: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
text: {
|
||||||
|
color: Colors.greyDark,
|
||||||
|
...Typography.mainText
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppText
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, TouchableOpacity } from 'react-native'
|
||||||
|
|
||||||
|
import AppIcon from './app-icon'
|
||||||
|
import AppText from './app-text'
|
||||||
|
|
||||||
|
import { Colors, Fonts, Spacing } from '../../styles'
|
||||||
|
|
||||||
|
const Button = ({
|
||||||
|
children,
|
||||||
|
iconName,
|
||||||
|
isCTA,
|
||||||
|
isSmall,
|
||||||
|
onPress,
|
||||||
|
testID,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const buttonStyle = isCTA ? styles.cta : styles.regular
|
||||||
|
const textCTA = isCTA ? styles.buttonTextBold : styles.buttonTextRegular
|
||||||
|
const textStyle = [textCTA, isSmall ? textSmall : text]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onPress}
|
||||||
|
style={buttonStyle}
|
||||||
|
testID={testID}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<AppText style={textStyle}>{children}</AppText>
|
||||||
|
{iconName && <AppIcon color={Colors.orange} name={iconName} />}
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
iconName: PropTypes.string,
|
||||||
|
isCTA: PropTypes.bool,
|
||||||
|
isSmall: PropTypes.bool,
|
||||||
|
onPress: PropTypes.func,
|
||||||
|
testID: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.defaultProps = {
|
||||||
|
isSmall: true
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = {
|
||||||
|
padding: Spacing.base,
|
||||||
|
textTransform: 'uppercase'
|
||||||
|
}
|
||||||
|
|
||||||
|
const textSmall = {
|
||||||
|
fontSize: Fonts.small,
|
||||||
|
padding: Spacing.small,
|
||||||
|
textTransform: 'uppercase'
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = {
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
margin: Spacing.base,
|
||||||
|
minWidth: '15%'
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
regular: {
|
||||||
|
...button
|
||||||
|
},
|
||||||
|
cta: {
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
borderRadius: 25,
|
||||||
|
...button
|
||||||
|
},
|
||||||
|
buttonTextBold: {
|
||||||
|
color: 'white',
|
||||||
|
fontFamily: Fonts.bold
|
||||||
|
},
|
||||||
|
buttonTextRegular: {
|
||||||
|
color: Colors.greyDark,
|
||||||
|
fontFamily: Fonts.main
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Button
|
||||||
@@ -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'
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import Hyperlink from 'react-native-hyperlink'
|
||||||
|
import { StyleSheet } from 'react-native'
|
||||||
|
|
||||||
|
import { Colors, Typography } from '../../styles'
|
||||||
|
|
||||||
|
import links from '../../i18n/en/links'
|
||||||
|
|
||||||
|
const Link = ({ children, style }) => {
|
||||||
|
return (
|
||||||
|
<Hyperlink
|
||||||
|
linkStyle={[styles.link, style]}
|
||||||
|
linkText={replaceUrlWithText}
|
||||||
|
linkDefault
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Hyperlink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Link.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
style: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
link: {
|
||||||
|
color: Colors.purple,
|
||||||
|
textDecorationLine: 'underline',
|
||||||
|
...Typography.mainText,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function replaceUrlWithText(url) {
|
||||||
|
const link = Object.values(links).find(l => l.url === url)
|
||||||
|
return (link && link.text) || url
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Link
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { createIconSetFromIcoMoon } from 'react-native-vector-icons'
|
||||||
|
import iconConfig from '../../selection.json'
|
||||||
|
|
||||||
|
import { Colors, Sizes } from '../../styles'
|
||||||
|
|
||||||
|
const Icon = createIconSetFromIcoMoon(iconConfig, '', 'Menu')
|
||||||
|
|
||||||
|
const MenuIcon = ({ isActive, name }) => {
|
||||||
|
const color = isActive ? Colors.greyDark : Colors.grey
|
||||||
|
|
||||||
|
return <Icon name={name} size={Sizes.icon} color={color} />
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuIcon.propTypes = {
|
||||||
|
isActive: PropTypes.bool,
|
||||||
|
name: PropTypes.string.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MenuIcon
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
import AppText from './app-text'
|
||||||
|
|
||||||
|
import { Colors, Spacing, Typography } from '../../styles'
|
||||||
|
|
||||||
|
const Segment = ({ children, last, title }) => {
|
||||||
|
const containerStyle = last ? styles.containerLast : styles.container
|
||||||
|
const commonStyle = Object.assign({}, containerStyle)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={commonStyle}>
|
||||||
|
{title && <AppText style={styles.title}>{title}</AppText>}
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Segment.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
last: PropTypes.bool,
|
||||||
|
style: PropTypes.object,
|
||||||
|
title: PropTypes.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const segmentContainer = {
|
||||||
|
marginHorizontal: Spacing.base,
|
||||||
|
marginBottom: Spacing.base,
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: Colors.greyLight,
|
||||||
|
paddingBottom: Spacing.base,
|
||||||
|
...segmentContainer,
|
||||||
|
},
|
||||||
|
containerLast: {
|
||||||
|
...segmentContainer,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
...Typography.subtitle,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Segment
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import AppText from './app-text'
|
||||||
|
|
||||||
|
import { Sizes, Spacing, Typography } from '../../styles'
|
||||||
|
|
||||||
|
const Table = ({ tableContent }) => {
|
||||||
|
return (
|
||||||
|
tableContent.map((rowContent, i) => <Row key={i} rowContent={rowContent} />)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Table.propTypes = {
|
||||||
|
tableContent: PropTypes.array.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
const Row = ({ rowContent }) => {
|
||||||
|
return(
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Cell content={rowContent[0]} isLeft />
|
||||||
|
<Cell content={rowContent[1]} />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row.propTypes = {
|
||||||
|
rowContent: PropTypes.array.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
const Cell = ({ content, isLeft }) => {
|
||||||
|
const styleContainer = isLeft ? styles.cellLeft : styles.cellRight
|
||||||
|
const styleText = isLeft ? styles.accentPurpleBig : styles.accentOrange
|
||||||
|
const numberOfLines = isLeft ? 1 : 2
|
||||||
|
const ellipsizeMode = isLeft ? 'clip' : 'tail'
|
||||||
|
|
||||||
|
return(
|
||||||
|
<View style={styleContainer}>
|
||||||
|
<AppText
|
||||||
|
numberOfLines={numberOfLines}
|
||||||
|
ellipsizeMode={ellipsizeMode}
|
||||||
|
style={styleText}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Cell.propTypes = {
|
||||||
|
content: PropTypes.node.isRequired,
|
||||||
|
isLeft: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
accentOrange: {
|
||||||
|
...Typography.accentOrange,
|
||||||
|
fontSize: Sizes.small,
|
||||||
|
},
|
||||||
|
accentPurpleBig: {
|
||||||
|
...Typography.accentPurpleBig,
|
||||||
|
marginRight: Spacing.small,
|
||||||
|
},
|
||||||
|
cellLeft: {
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
flex: 5,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
cellRight: {
|
||||||
|
flex: 6,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginBottom: Spacing.tiny,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Table
|
||||||
@@ -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 '../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,98 @@
|
|||||||
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 { LocalDate } from 'js-joda'
|
||||||
|
|
||||||
|
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'
|
||||||
|
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,
|
||||||
|
isTemperatureEditView: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
|
||||||
cycleDay: getCycleDay(props.date)
|
this.state = { cycleDay: getCycleDay(props.date), data: null }
|
||||||
|
if (props.isTemperatureEditView) {
|
||||||
|
const todayDateString = LocalDate.now().toString()
|
||||||
|
props.setDate(todayDateString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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, isTemperatureEditView } = 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 isSymptomEdited = isTemperatureEditView && symptom === 'temperature'
|
||||||
const symptomData =
|
|
||||||
cycleDay && cycleDay[symptom] ? cycleDay[symptom] : null
|
return(
|
||||||
return(
|
<SymptomBox
|
||||||
<SymptomBox
|
key={symptom}
|
||||||
key={symptom}
|
symptom={symptom}
|
||||||
symptom={symptom}
|
symptomData={symptomData}
|
||||||
symptomData={symptomData}
|
symptomDataToDisplay={getData(symptom, symptomData)}
|
||||||
onPress={() => this.props.navigate(symptomEditView)}
|
updateCycleDayData={this.updateCycleDay}
|
||||||
disabled={dateInFuture && symptom !== 'note'}
|
isSymptomEdited={isSymptomEdited}
|
||||||
/>)
|
/>
|
||||||
})
|
)
|
||||||
}
|
})}
|
||||||
{
|
</View>
|
||||||
// this is just to make the last row adhere to the grid
|
</AppPage>
|
||||||
// (and) because there are no pseudo properties in RN
|
|
||||||
}
|
|
||||||
<FillerBoxes />
|
|
||||||
</View>
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 '../app-text'
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import styles from '../../styles'
|
import { Colors, Containers } from '../../styles'
|
||||||
|
|
||||||
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 '../app-text'
|
import AppText from '../common/app-text'
|
||||||
|
|
||||||
import styles from '../../styles'
|
import { Colors, Containers } from '../../styles'
|
||||||
|
|
||||||
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,168 @@
|
|||||||
|
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'
|
||||||
|
import { headerTitles as symptomTitles } from '../../i18n/en/labels'
|
||||||
|
|
||||||
|
class SymptomBox extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
date: PropTypes.string.isRequired,
|
||||||
|
isSymptomEdited: PropTypes.bool,
|
||||||
|
symptom: PropTypes.string.isRequired,
|
||||||
|
symptomData: PropTypes.object,
|
||||||
|
symptomDataToDisplay: PropTypes.string,
|
||||||
|
updateCycleDayData: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
isSymptomEdited: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isSymptomEdited: props.isSymptomEdited
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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} numberOfLines={4}>
|
||||||
|
{symptomDataToDisplay}
|
||||||
|
</AppText>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
textContainer: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
marginLeft: Spacing.small,
|
||||||
|
maxWidth: Spacing.textWidth
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
...hint
|
||||||
|
},
|
||||||
|
textDisabled: {
|
||||||
|
color: Colors.greyLight
|
||||||
|
},
|
||||||
|
textExcluded: {
|
||||||
|
color: Colors.grey,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return({
|
||||||
|
date: getDate(state),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null,
|
||||||
|
)(SymptomBox)
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Dimensions, 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 { Colors, Containers, Sizes, Spacing } from '../../styles'
|
||||||
|
|
||||||
|
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}
|
||||||
|
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} style={styles.segmentBorder}>
|
||||||
|
<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} style={styles.segmentBorder} >
|
||||||
|
<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 style={styles.segmentBorder} >
|
||||||
|
<AppSwitch
|
||||||
|
onToggle={this.onExcludeToggle}
|
||||||
|
text={symtomPage[symptom].excludeText}
|
||||||
|
value={data.exclude}
|
||||||
|
/>
|
||||||
|
</Segment>
|
||||||
|
}
|
||||||
|
{shouldShowNote &&
|
||||||
|
<Segment style={styles.segmentBorder} >
|
||||||
|
<AppText>{symtomPage[symptom].note}</AppText>
|
||||||
|
<AppTextInput
|
||||||
|
multiline={true}
|
||||||
|
numberOfLines={3}
|
||||||
|
onChangeText={this.onEditNote}
|
||||||
|
placeholder={sharedLabels.enter}
|
||||||
|
testID='noteInput'
|
||||||
|
value={noteText !== null ? noteText : ''}
|
||||||
|
/>
|
||||||
|
</Segment>
|
||||||
|
}
|
||||||
|
<View style={styles.buttonsContainer}>
|
||||||
|
<Button iconName={iconName} isSmall onPress={this.onPressLearnMore}>
|
||||||
|
{sharedLabels.learnMore}
|
||||||
|
</Button>
|
||||||
|
<Button isSmall onPress={this.onRemove}>
|
||||||
|
{sharedLabels.remove}
|
||||||
|
</Button>
|
||||||
|
<Button isCTA isSmall onPress={this.onSave}>
|
||||||
|
{sharedLabels.save}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
{shouldShowInfo &&
|
||||||
|
<Segment last style={styles.segmentBorder} >
|
||||||
|
<AppText>{info[symptom].text}</AppText>
|
||||||
|
</Segment>
|
||||||
|
}
|
||||||
|
</ScrollView>
|
||||||
|
</AppModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
buttonsContainer: {
|
||||||
|
...Containers.rowContainer
|
||||||
|
},
|
||||||
|
headerContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
paddingVertical: Spacing.tiny,
|
||||||
|
},
|
||||||
|
modalContainer: {
|
||||||
|
flex: 1,
|
||||||
|
padding: Spacing.small,
|
||||||
|
paddingBottom: Spacing.base
|
||||||
|
},
|
||||||
|
modalWindow: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 10,
|
||||||
|
marginVertical: Sizes.huge * 2,
|
||||||
|
position: 'absolute',
|
||||||
|
minHeight: '40%',
|
||||||
|
maxHeight: Dimensions.get('window').height * 0.7
|
||||||
|
},
|
||||||
|
segmentBorder: {
|
||||||
|
borderBottomColor: Colors.greyLight
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: Sizes.subtitle
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return({
|
||||||
|
date: getDate(state),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null,
|
||||||
|
)(SymptomEditView)
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
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 } from '../helpers/cycle-day'
|
||||||
|
import { Colors, Containers, Spacing, Typography } from '../../styles'
|
||||||
|
import { HIT_SLOP } from '../../config'
|
||||||
|
|
||||||
|
const SymptomPageTitle = ({
|
||||||
|
date,
|
||||||
|
reloadSymptomData,
|
||||||
|
setDate,
|
||||||
|
subtitle,
|
||||||
|
title,
|
||||||
|
}) => {
|
||||||
|
const navigate = (isForward) => {
|
||||||
|
const nextDay = isForward ? nextDate(date) : prevDate(date)
|
||||||
|
reloadSymptomData(nextDay)
|
||||||
|
setDate(nextDay)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<TouchableOpacity onPress={() => navigate(false)} hitSlop={HIT_SLOP}>
|
||||||
|
<AppIcon name='chevron-left' color={Colors.orange}/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<AppText style={styles.title}>{title}</AppText>
|
||||||
|
{subtitle && <AppText style={styles.subtitle}>{subtitle}</AppText>}
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity onPress={() => navigate(true)} hitSlop={HIT_SLOP}>
|
||||||
|
<AppIcon name='chevron-right' color={Colors.orange}/>
|
||||||
|
</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 '../../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,36 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import { View } from 'react-native'
|
|
||||||
import AppText from '../../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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,108 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import { View } from 'react-native'
|
|
||||||
import AppText from '../../app-text'
|
|
||||||
import AppTextInput from '../../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 config 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 { min, max } = config.temperatureScale
|
|
||||||
const range = { min, 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 '../../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 '../../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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||